From 70ae12b5121633e7e63ff6811fdd2abbb5b886f6 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 3 Aug 2014 07:45:03 +0400 Subject: [PATCH] Added tabline support No tests for it yet --- docs/source/configuration/reference.rst | 11 +- powerline/config_files/config.json | 2 + .../config_files/themes/vim/tabline.json | 32 ++++++ powerline/lint/__init__.py | 61 +++++----- powerline/renderer.py | 2 +- powerline/renderers/vim.py | 56 +++++---- powerline/segment.py | 108 +++++++++++++++++- powerline/segments/vim.py | 75 ++++++++++++ powerline/theme.py | 50 +------- powerline/vim.py | 7 +- 10 files changed, 301 insertions(+), 103 deletions(-) create mode 100644 powerline/config_files/themes/vim/tabline.json diff --git a/docs/source/configuration/reference.rst b/docs/source/configuration/reference.rst index 1c3ee800..28c11863 100644 --- a/docs/source/configuration/reference.rst +++ b/docs/source/configuration/reference.rst @@ -272,6 +272,12 @@ Themes highlighting group is defined in the :ref:`highlight_group option `. + ``segments_list`` + Sub-list of segments. This list only allows :ref:`name + `, :ref:`segments + ` and :ref:`args + ` options. + ``module`` .. _config-themes-seg-module: @@ -282,7 +288,7 @@ Themes ``name`` .. _config-themes-seg-name: - Function name, only required for function segments. + Function name, only required for function and list segments. ``highlight_group`` .. _config-themes-seg-highlight_group: @@ -366,3 +372,6 @@ Themes Boolean. If false disables displaying of the segment. Defaults to ``True``. + + ``segments`` + A list of subsegments. diff --git a/powerline/config_files/config.json b/powerline/config_files/config.json index b918838d..f64bdfc1 100644 --- a/powerline/config_files/config.json +++ b/powerline/config_files/config.json @@ -39,6 +39,8 @@ "colorscheme": "default", "theme": "default", "local_themes": { + "__tabline__": "tabline", + "cmdwin": "cmdwin", "help": "help", "quickfix": "quickfix", diff --git a/powerline/config_files/themes/vim/tabline.json b/powerline/config_files/themes/vim/tabline.json new file mode 100644 index 00000000..be07eaec --- /dev/null +++ b/powerline/config_files/themes/vim/tabline.json @@ -0,0 +1,32 @@ +{ + "default_module": "powerline.segments.vim", + "segments": { + "left": [ + { + "type": "segment_list", + "name": "tablister", + "segments": [ + { + "name": "file_directory", + "draw_soft_divider": false, + "priority": 40 + }, + { + "name": "file_name", + "args": { + "display_no_file": true + }, + "priority": 10 + } + ] + }, + { + "type": "string", + "highlight_group": ["background"], + "draw_soft_divider": false, + "draw_hard_divider": false, + "width": "auto" + } + ] + } +} diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index 7882497f..2d5dc292 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -385,6 +385,9 @@ class WithPath(object): def check_matcher_func(ext, match_name, data, context, echoerr): import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])] + if match_name == '__tabline__': + return True, False + match_module, separator, match_function = match_name.rpartition('.') if not separator: match_module = 'powerline.matchers.{0}'.format(ext) @@ -687,11 +690,13 @@ type_keys = { 'function': set(('args', 'module', 'draw_inner_divider')), 'string': set(('contents', 'type', 'highlight_group', 'divider_highlight_group')), 'filler': set(('type', 'highlight_group', 'divider_highlight_group')), + 'segment_list': set(('segments', 'module', 'args', 'type')), } required_keys = { 'function': set(('name',)), 'string': set(('contents',)), 'filler': set(), + 'segment_list': set(('name', 'segments',)), } highlight_keys = set(('highlight_group', 'name')) @@ -867,7 +872,7 @@ def check_segment_name(name, data, context, echoerr): hadproblem = True return True, False, hadproblem - else: + elif context[-2][1].get('type') != 'segment_list': if name not in context[0][1].get('segment_data', {}): top_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None) if data['theme'] == top_theme_name: @@ -1075,32 +1080,34 @@ args_spec = Spec( ).unknown_spec(Spec(), Spec()).optional().copy highlight_group_spec = Spec().type(unicode).copy segment_module_spec = Spec().type(unicode).func(check_segment_module).optional().copy -segments_spec = Spec().optional().list( - Spec( - type=Spec().oneof(type_keys).optional(), - name=Spec().re('^[a-zA-Z_]\w+$').func(check_segment_name).optional(), - exclude_modes=Spec().list(vim_mode_spec()).optional(), - include_modes=Spec().list(vim_mode_spec()).optional(), - draw_hard_divider=Spec().type(bool).optional(), - draw_soft_divider=Spec().type(bool).optional(), - draw_inner_divider=Spec().type(bool).optional(), - display=Spec().type(bool).optional(), - module=segment_module_spec(), - priority=Spec().type(int, float, type(None)).optional(), - after=Spec().type(unicode).optional(), - before=Spec().type(unicode).optional(), - width=Spec().either(Spec().unsigned(), Spec().cmp('eq', 'auto')).optional(), - align=Spec().oneof(set('lr')).optional(), - args=args_spec().func(lambda *args, **kwargs: check_args(get_one_segment_variant, *args, **kwargs)), - contents=Spec().type(unicode).optional(), - highlight_group=Spec().list( - highlight_group_spec().re('^(?:(?!:divider$).)+$', - lambda value: 'it is recommended that only divider highlight group names end with ":divider"') - ).func(check_highlight_groups).optional(), - divider_highlight_group=highlight_group_spec().func(check_highlight_group).re(':divider$', - lambda value: 'it is recommended that divider highlight group names end with ":divider"').optional(), - ).func(check_full_segment_data), -).copy +sub_segments_spec = Spec() +segment_spec = Spec( + type=Spec().oneof(type_keys).optional(), + name=Spec().re('^[a-zA-Z_]\w+$').func(check_segment_name).optional(), + exclude_modes=Spec().list(vim_mode_spec()).optional(), + include_modes=Spec().list(vim_mode_spec()).optional(), + draw_hard_divider=Spec().type(bool).optional(), + draw_soft_divider=Spec().type(bool).optional(), + draw_inner_divider=Spec().type(bool).optional(), + display=Spec().type(bool).optional(), + module=segment_module_spec(), + priority=Spec().type(int, float, type(None)).optional(), + after=Spec().type(unicode).optional(), + before=Spec().type(unicode).optional(), + width=Spec().either(Spec().unsigned(), Spec().cmp('eq', 'auto')).optional(), + align=Spec().oneof(set('lr')).optional(), + args=args_spec().func(lambda *args, **kwargs: check_args(get_one_segment_variant, *args, **kwargs)), + contents=Spec().type(unicode).optional(), + highlight_group=Spec().list( + highlight_group_spec().re('^(?:(?!:divider$).)+$', + lambda value: 'it is recommended that only divider highlight group names end with ":divider"') + ).func(check_highlight_groups).optional(), + divider_highlight_group=highlight_group_spec().func(check_highlight_group).re(':divider$', + lambda value: 'it is recommended that divider highlight group names end with ":divider"').optional(), + segments=sub_segments_spec, +).func(check_full_segment_data) +sub_segments_spec.optional().list(segment_spec) +segments_spec = Spec().optional().list(segment_spec).copy segdict_spec=Spec( left=segments_spec().context_message('Error while loading segments from left side (key {key})'), right=segments_spec().context_message('Error while loading segments from right side (key {key})'), diff --git a/powerline/renderer.py b/powerline/renderer.py index 002f777c..2def0725 100644 --- a/powerline/renderer.py +++ b/powerline/renderer.py @@ -222,7 +222,7 @@ class Renderer(object): segments = theme.get_segments(side, line, self.get_segment_info(segment_info, mode)) # Handle excluded/included segments for the current mode - segments = [self._get_highlighting(segment, mode) for segment in segments + segments = [self._get_highlighting(segment, segment['mode'] or mode) for segment in segments if mode not in segment['exclude_modes'] and (not segment['include_modes'] or mode in segment['include_modes'])] segments = [segment for segment in self._render_segments(theme, segments)] diff --git a/powerline/renderers/vim.py b/powerline/renderers/vim.py index b948dc0e..e788bdd2 100644 --- a/powerline/renderers/vim.py +++ b/powerline/renderers/vim.py @@ -53,15 +53,19 @@ class VimRenderer(Renderer): raise KeyError('There is already a local theme with given matcher') self.local_themes[matcher] = theme + def get_matched_theme(self, match): + try: + return match['theme'] + except KeyError: + match['theme'] = Theme(theme_config=match['config'], top_theme_config=self.theme_config, **self.theme_kwargs) + return match['theme'] + def get_theme(self, matcher_info): + if matcher_info is None: + return self.get_matched_theme(self.local_themes[None]) for matcher in self.local_themes.keys(): - if matcher(matcher_info): - match = self.local_themes[matcher] - try: - return match['theme'] - except KeyError: - match['theme'] = Theme(theme_config=match['config'], top_theme_config=self.theme_config, **self.theme_kwargs) - return match['theme'] + if matcher and matcher(matcher_info): + return self.get_matched_theme(self.local_themes[matcher]) else: return self.theme @@ -80,28 +84,34 @@ class VimRenderer(Renderer): def get_segment_info(self, segment_info, mode): return segment_info or self.segment_info - def render(self, window, window_id, winnr): + def render(self, window=None, window_id=None, winnr=None): '''Render all segments.''' - if window is vim.current.window: - mode = vim_mode(1) - mode = mode_translations.get(mode, mode) - else: - mode = 'nc' segment_info = self.segment_info.copy() - segment_info.update({ - 'window': window, - 'mode': mode, - 'window_id': window_id, - 'winnr': winnr, - }) - segment_info['buffer'] = segment_info['window'].buffer - segment_info['bufnr'] = segment_info['buffer'].number - winwidth = segment_info['window'].width + if window is not None: + if window is vim.current.window: + mode = vim_mode(1) + mode = mode_translations.get(mode, mode) + else: + mode = 'nc' + segment_info.update( + window=window, + mode=mode, + window_id=window_id, + winnr=winnr, + buffer=window.buffer, + ) + segment_info['bufnr'] = segment_info['buffer'].number + winwidth = segment_info['window'].width + matcher_info = segment_info + else: + mode = None + winwidth = int(vim.eval('&columns')) + matcher_info = None statusline = super(VimRenderer, self).render( mode=mode, width=winwidth, segment_info=segment_info, - matcher_info=segment_info, + matcher_info=matcher_info, ) return statusline diff --git a/powerline/segment.py b/powerline/segment.py index 836a5739..de85a7e4 100644 --- a/powerline/segment.py +++ b/powerline/segment.py @@ -44,6 +44,7 @@ segment_getters = { "function": get_function, "string": get_string, "filler": get_filler, + "segment_list": get_function, } @@ -59,6 +60,69 @@ def get_attr_func(contents_func, key, args): return lambda pl, shutdown_event: func(pl=pl, shutdown_event=shutdown_event, **args) +def process_segment_lister(pl, segment_info, parsed_segments, side, lister, subsegments, patcher_args): + for subsegment_info, subsegment_update in lister(pl=pl, segment_info=segment_info, **patcher_args): + for subsegment in subsegments: + if subsegment_update: + subsegment = subsegment.copy() + subsegment.update(subsegment_update) + if subsegment_update['priority_multiplier'] and subsegment['priority']: + subsegment['priority'] *= subsegment_update['priority_multiplier'] + process_segment(pl, side, subsegment_info, parsed_segments, subsegment) + return None + + +def process_segment(pl, side, segment_info, parsed_segments, segment): + if segment['type'] in ('function', 'segment_list'): + pl.prefix = segment['name'] + try: + if segment['type'] == 'function': + contents = segment['contents_func'](pl, segment_info) + else: + contents = segment['contents_func'](pl, segment_info, parsed_segments, side) + except Exception as e: + pl.exception('Exception while computing segment: {0}', str(e)) + return + + if contents is None: + return + if isinstance(contents, list): + segment_base = segment.copy() + if contents: + draw_divider_position = -1 if side == 'left' else 0 + for key, i, newval in ( + ('before', 0, ''), + ('after', -1, ''), + ('draw_soft_divider', draw_divider_position, True), + ('draw_hard_divider', draw_divider_position, True), + ): + try: + contents[i][key] = segment_base.pop(key) + segment_base[key] = newval + except KeyError: + pass + + draw_inner_divider = None + if side == 'right': + append = parsed_segments.append + else: + pslen = len(parsed_segments) + append = lambda item: parsed_segments.insert(pslen, item) + + for subsegment in (contents if side == 'right' else reversed(contents)): + segment_copy = segment_base.copy() + segment_copy.update(subsegment) + if draw_inner_divider is not None: + segment_copy['draw_soft_divider'] = draw_inner_divider + draw_inner_divider = segment_copy.pop('draw_inner_divider', None) + append(segment_copy) + else: + segment['contents'] = contents + parsed_segments.append(segment) + elif segment['width'] == 'auto' or (segment['type'] == 'string' and segment['contents'] is not None): + parsed_segments.append(segment) + + def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=None): data = { 'default_module': default_module or 'powerline.segments.' + ext, @@ -90,8 +154,48 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=Non else: highlight_group = segment.get('highlight_group') or segment.get('name') - if segment_type == 'function': + if segment_type in ('function', 'segment_list'): args = dict(((str(k), v) for k, v in get_key(segment, module, 'args', {}).items())) + + if segment_type == 'segment_list': + # Handle startup and shutdown of _contents_func? + subsegments = [ + get(subsegment, side) + for subsegment in segment['segments'] + ] + return { + 'name': segment.get('name'), + 'type': segment_type, + 'highlight_group': None, + 'divider_highlight_group': None, + 'before': None, + 'after': None, + 'contents_func': lambda pl, segment_info, parsed_segments, side: process_segment_lister( + pl, segment_info, parsed_segments, side, + patcher_args=args, + subsegments=subsegments, + lister=_contents_func, + ), + 'contents': None, + 'priority': None, + 'draw_soft_divider': None, + 'draw_hard_divider': None, + 'side': side, + 'exclude_modes': segment.get('exclude_modes', []), + 'include_modes': segment.get('include_modes', []), + 'width': None, + 'align': None, + 'startup': None, + 'shutdown': None, + 'mode': None, + '_rendered_raw': '', + '_rendered_hl': '', + '_len': 0, + '_space_left': 0, + '_space_right': 0, + } + + if segment_type == 'function': startup_func = get_attr_func(_contents_func, 'startup', args) shutdown_func = get_attr_func(_contents_func, 'shutdown', None) @@ -117,7 +221,6 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=Non 'after': get_key(segment, module, 'after', ''), 'contents_func': contents_func, 'contents': contents, - 'args': args if segment_type == 'function' else {}, 'priority': segment.get('priority', None), 'draw_hard_divider': segment.get('draw_hard_divider', True), 'draw_soft_divider': segment.get('draw_soft_divider', True), @@ -129,6 +232,7 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=Non 'align': segment.get('align', 'l'), 'startup': startup_func, 'shutdown': shutdown_func, + 'mode': None, '_rendered_raw': '', '_rendered_hl': '', '_len': 0, diff --git a/powerline/segments/vim.py b/powerline/segments/vim.py index 5f55bb7b..03e0f3b0 100644 --- a/powerline/segments/vim.py +++ b/powerline/segments/vim.py @@ -485,3 +485,78 @@ def trailing_whitespace(pl, segment_info): ret = None trailing_whitespace_cache[bufnr] = (changedtick, ret) return ret + + +if hasattr(vim, 'vvars') and vim.vvars['version'] >= 704: + def updated_segment_info(segment_info, tabpage): + segment_info = segment_info.copy() + window = tabpage.window + buffer = window.buffer + segment_info.update( + tabpage=tabpage, + tabnr=tabpage.number, + window=window, + winnr=window.number, + window_id=window.vars.get('powerline_window_id'), + buffer=buffer, + bufnr=buffer.number, + ) + return segment_info + + list_tabpages = lambda: vim.tabpages + current_tabpage = lambda: vim.current.tabpage + tabpage_nr = lambda tabpage: tabpage.number +else: + def updated_segment_info(segment_info, tabnr): # NOQA + segment_info = segment_info.copy() + winnr = int(vim.eval('tabpagewinnr({0})'.format(tabnr))) + bufnr = int(vim.eval('tabpagebuflist({0})[{1}]'.format(tabnr, winnr - 1))) + buffer = None + for buffer in vim.buffers: + if buffer.number == bufnr: + break + window_id = vim.eval('gettabwinvar({0}, {1}, "powerline_window_id")'.format(tabnr, winnr)) + window_id = int(window_id) if window_id else None + segment_info.update( + tabpage=None, + tabnr=tabnr, + window=None, + winnr=winnr, + window_id=window_id, + buffer=buffer, + bufnr=bufnr, + ) + + list_tabpages = lambda: range(1, int(vim.eval('tabpagenr("$")')) + 1) # NOQA + current_tabpage = lambda: int(vim.eval('tabpagenr()')) # NOQA + tabpage_nr = lambda tabnr: tabnr # NOQA + + +@requires_segment_info +def tablister(pl, segment_info): + '''List all tab pages in segment_info format + + Specifically generates a list of segment info dictionaries with ``window``, + ``winnr``, ``window_id``, ``buffer`` and ``bufnr`` keys set to tab-local + ones and additional ``tabpage`` and ``tabnr`` keys. + + Sets segment ``mode`` to either ``tab`` (for current tab page) or ``nc`` + (for all other tab pages). + + Works best with vim-7.4 or later: earlier versions miss tabpage object and + thus window objects are not available as well. + ''' + cur_tabpage = current_tabpage() + cur_tabnr = tabpage_nr(cur_tabpage) + + def add_multiplier(tabpage, dct): + dct['priority_multiplier'] = 1 + (0.001 * abs(tabpage_nr(tabpage) - cur_tabnr)) + return dct + + return [ + ( + updated_segment_info(segment_info, tabpage), + add_multiplier(tabpage, {'mode': ('tab' if tabpage == cur_tabpage else 'nc')}) + ) + for tabpage in list_tabpages() + ] diff --git a/powerline/theme.py b/powerline/theme.py index 9de033a9..2d382d64 100644 --- a/powerline/theme.py +++ b/powerline/theme.py @@ -1,6 +1,6 @@ # vim:fileencoding=utf-8:noet -from powerline.segment import gen_segment_getter +from powerline.segment import gen_segment_getter, process_segment from powerline.lib.unicode import u import itertools @@ -91,53 +91,7 @@ class Theme(object): for side in [side] if side else ['left', 'right']: parsed_segments = [] for segment in self.segments[line][side]: - if segment['type'] == 'function': - self.pl.prefix = segment['name'] - try: - contents = segment['contents_func'](self.pl, segment_info) - except Exception as e: - self.pl.exception('Exception while computing segment: {0}', str(e)) - continue - - if contents is None: - continue - if isinstance(contents, list): - segment_base = segment.copy() - if contents: - draw_divider_position = -1 if side == 'left' else 0 - for key, i, newval in ( - ('before', 0, ''), - ('after', -1, ''), - ('draw_soft_divider', draw_divider_position, True), - ('draw_hard_divider', draw_divider_position, True), - ): - try: - contents[i][key] = segment_base.pop(key) - segment_base[key] = newval - except KeyError: - pass - - draw_inner_divider = None - if side == 'right': - append = parsed_segments.append - else: - pslen = len(parsed_segments) - append = lambda item: parsed_segments.insert(pslen, item) - - for subsegment in (contents if side == 'right' else reversed(contents)): - segment_copy = segment_base.copy() - segment_copy.update(subsegment) - if draw_inner_divider is not None: - segment_copy['draw_soft_divider'] = draw_inner_divider - draw_inner_divider = segment_copy.pop('draw_inner_divider', None) - append(segment_copy) - else: - segment['contents'] = contents - parsed_segments.append(segment) - elif segment['width'] == 'auto' or (segment['type'] == 'string' and segment['contents'] is not None): - parsed_segments.append(segment) - else: - continue + process_segment(self.pl, side, segment_info, parsed_segments, segment) for segment in parsed_segments: segment['contents'] = segment['before'] + u(segment['contents'] if segment['contents'] is not None else '') + segment['after'] # Align segment contents diff --git a/powerline/vim.py b/powerline/vim.py index 05d42de4..8afae555 100644 --- a/powerline/vim.py +++ b/powerline/vim.py @@ -72,7 +72,8 @@ class VimPowerline(Powerline): return {} self.get_matcher = gen_matcher_getter(self.ext, self.import_paths) - return dict(((self.get_matcher(key), {'config': self.load_theme_config(val)}) + return dict(((None if key == '__tabline__' else self.get_matcher(key), + {'config': self.load_theme_config(val)}) for key, val in local_themes.items())) def get_config_paths(self): @@ -141,6 +142,9 @@ class VimPowerline(Powerline): return 'No window {0}'.format(window_id) return self.render(window, window_id, winnr) + def tabline(self): + return self.render() + def new_window(self): window, window_id, winnr = self.win_idx(None) return self.render(window, window_id, winnr) @@ -202,3 +206,4 @@ def setup(pyeval=None, pycmd=None, can_replace_pyeval=True): # Is immediately changed after new_window function is run. Good for global # value. vim.command('set statusline=%!{pyeval}(\'powerline.new_window()\')'.format(pyeval=pyeval)) + vim.command('set tabline=%!{pyeval}(\'powerline.tabline()\')'.format(pyeval=pyeval))