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/bindings/vim/__init__.py b/powerline/bindings/vim/__init__.py index 6830365c..76c40ffe 100644 --- a/powerline/bindings/vim/__init__.py +++ b/powerline/bindings/vim/__init__.py @@ -84,10 +84,10 @@ else: def bufvar_exists(buffer, varname): # NOQA if not buffer or buffer.number == vim.current.buffer.number: - return vim.eval('exists("b:{0}")'.format(varname)) + return int(vim.eval('exists("b:{0}")'.format(varname))) else: - return vim.eval('has_key(getbufvar({0}, ""), {1})' - .format(buffer.number, varname)) + return int(vim.eval('has_key(getbufvar({0}, ""), {1})' + .format(buffer.number, varname))) def vim_getwinvar(segment_info, varname): # NOQA result = vim.eval('getwinvar({0}, "{1}")'.format(segment_info['winnr'], varname)) @@ -104,6 +104,79 @@ else: return getbufvar(info['bufnr'], '&' + option) +if hasattr(vim, 'tabpages'): + current_tabpage = lambda: vim.current.tabpage + list_tabpages = lambda: vim.tabpages +else: + class FalseObject(object): + @staticmethod + def __nonzero__(): + return False + + __bool__ = __nonzero__ + + def get_buffer(number): + for buffer in vim.buffers: + if buffer.number == number: + return buffer + raise KeyError(number) + + class WindowVars(object): + __slots__ = ('tabnr', 'winnr') + + def __init__(self, window): + self.tabnr = window.tabnr + self.winnr = window.number + + def __getitem__(self, key): + has_key = vim.eval('has_key(gettabwinvar({0}, {1}, ""), "{2}")'.format(self.tabnr, self.winnr, key)) + if has_key == '0': + raise KeyError + return vim.eval('gettabwinvar({0}, {1}, "{2}")'.format(self.tabnr, self.winnr, key)) + + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default + + class Window(FalseObject): + __slots__ = ('tabnr', 'number', '_vars') + + def __init__(self, tabnr, number): + self.tabnr = tabnr + self.number = number + self.vars = WindowVars(self) + + @property + def buffer(self): + return get_buffer(int(vim.eval('tabpagebuflist({0})[{1}]'.format(self.tabnr, self.number - 1)))) + + class Tabpage(FalseObject): + __slots__ = ('number',) + + def __init__(self, number): + self.number = number + + def __eq__(self, tabpage): + if not isinstance(tabpage, Tabpage): + raise NotImplementedError + return self.number == tabpage.number + + @property + def window(self): + return Window(self.number, int(vim.eval('tabpagewinnr({0})'.format(self.number)))) + + def _last_tab_nr(): + return int(vim.eval('tabpagenr("$")')) + + def current_tabpage(): # NOQA + return Tabpage(int(vim.eval('tabpagenr()'))) + + def list_tabpages(): # NOQA + return [Tabpage(nr) for nr in range(1, _last_tab_nr() + 1)] + + if sys.version_info < (3,) or not hasattr(vim, 'bindeval'): getbufvar = vim_get_func('getbufvar') else: diff --git a/powerline/config_files/colorschemes/vim/__main__.json b/powerline/config_files/colorschemes/vim/__main__.json index 16c5ac71..03f51ca2 100644 --- a/powerline/config_files/colorschemes/vim/__main__.json +++ b/powerline/config_files/colorschemes/vim/__main__.json @@ -11,6 +11,9 @@ "file_name_empty": "file_directory", "line_percent": "information:additional", "line_count": "line_current", - "position": "information:additional" + "position": "information:additional", + "single_tab": "line_current", + "many_tabs": "line_current", + "tabnr": "file_directory" } } 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..a51da75f --- /dev/null +++ b/powerline/config_files/themes/vim/tabline.json @@ -0,0 +1,44 @@ +{ + "default_module": "powerline.segments.vim", + "segments": { + "left": [ + { + "type": "segment_list", + "name": "tabbuflister", + "segments": [ + { + "name": "tabnr", + "after": " ", + "draw_soft_divider": false, + "exclude_modes": ["tab", "buf"], + "priority": 5 + }, + { + "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" + } + ], + "right": [ + { + "name": "single_tab" + } + ] + } +} diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index 1976e876..9b3661e7 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -71,6 +71,8 @@ class DelayedEchoErr(EchoErr): def __nonzero__(self): return not not self.errs + __bool__ = __nonzero__ + class Spec(object): def __init__(self, **keys): @@ -93,15 +95,21 @@ class Spec(object): self.did_type = True return self - def copy(self): - return self.__class__()._update(self.__dict__) + def copy(self, copied=None): + copied = copied or {} + try: + return copied[id(self)] + except KeyError: + instance = self.__class__() + copied[id(self)] = instance + return self.__class__()._update(self.__dict__, copied) - def _update(self, d): + def _update(self, d, copied): self.__dict__.update(d) self.keys = copy(self.keys) self.checks = copy(self.checks) self.uspecs = copy(self.uspecs) - self.specs = [spec.copy() for spec in self.specs] + self.specs = [spec.copy(copied) for spec in self.specs] return self def unknown_spec(self, keyfunc, spec): @@ -379,6 +387,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) @@ -681,13 +692,14 @@ 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(), + 'function': set(('name',)), 'string': set(('contents',)), 'filler': set(), + 'segment_list': set(('name', 'segments',)), } -function_keys = set(('args', 'module')) highlight_keys = set(('highlight_group', 'name')) @@ -712,7 +724,7 @@ def check_key_compatibility(segment, data, context, echoerr): problem_mark=list(unknown_keys)[0].mark) hadproblem = True - if not (keys > required_keys[segment_type]): + if not (keys >= required_keys[segment_type]): missing_keys = required_keys[segment_type] - keys echoerr(context='Error while checking segments (key {key})'.format(key=context_key(context)), context_mark=context[-1][1].mark, @@ -862,7 +874,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: @@ -1070,32 +1082,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..6ee245aa 100644 --- a/powerline/renderer.py +++ b/powerline/renderer.py @@ -222,20 +222,44 @@ 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 - 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)] + 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'] + ) + ) + ] if not width: # No width specified, so we don't need to crop or pad anything - return construct_returned_value(''.join([segment['_rendered_hl'] for segment in segments]) + self.hlstyle(), segments, output_raw) + return construct_returned_value(''.join([ + segment['_rendered_hl'] + for segment in self._render_segments(theme, segments) + ]) + self.hlstyle(), segments, output_raw) + + divider_lengths = { + 'left': { + 'hard': self.strwidth(theme.get_divider('left', 'hard')), + 'soft': self.strwidth(theme.get_divider('left', 'soft')), + }, + 'right': { + 'hard': self.strwidth(theme.get_divider('right', 'hard')), + 'soft': self.strwidth(theme.get_divider('right', 'soft')), + }, + } + + length = self._render_length(theme, segments, divider_lengths) # Create an ordered list of segments that can be dropped segments_priority = sorted((segment for segment in segments if segment['priority'] is not None), key=lambda segment: segment['priority'], reverse=True) - while sum([segment['_len'] for segment in segments]) > width and len(segments_priority): - segments.remove(segments_priority[0]) - segments_priority.pop(0) + for segment in segments_priority: + if self._render_length(theme, segments, divider_lengths) <= width: + break + segments.remove(segment) # Distribute the remaining space on spacer segments segments_spacers = [segment for segment in segments if segment['width'] == 'auto'] @@ -256,6 +280,38 @@ class Renderer(object): return construct_returned_value(rendered_highlighted, segments, output_raw) + def _render_length(self, theme, segments, divider_lengths): + '''Update segments lengths and return them + ''' + segments_len = len(segments) + ret = 0 + divider_spaces = theme.get_spaces() + for index, segment in enumerate(segments): + side = segment['side'] + if segment['_contents_len'] is None: + segment_len = segment['_contents_len'] = self.strwidth(segment['contents']) + else: + segment_len = segment['_contents_len'] + + prev_segment = segments[index - 1] if index > 0 else theme.EMPTY_SEGMENT + next_segment = segments[index + 1] if index < segments_len - 1 else theme.EMPTY_SEGMENT + compare_segment = next_segment if side == 'left' else prev_segment + divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard' + + outer_padding = int(bool( + (index == 0 and side == 'left') or + (index == segments_len - 1 and side == 'right') + )) + + draw_divider = segment['draw_' + divider_type + '_divider'] + segment_len += segment['_space_left'] + segment['_space_right'] + outer_padding + if draw_divider: + segment_len += divider_lengths[side][divider_type] + divider_spaces + + segment['_len'] = segment_len + ret += segment_len + return ret + def _render_segments(self, theme, segments, render_highlighted=True): '''Internal segment rendering method. @@ -268,19 +324,19 @@ class Renderer(object): statusline if render_highlighted is True. ''' segments_len = len(segments) + divider_spaces = theme.get_spaces() for index, segment in enumerate(segments): - segment['_rendered_raw'] = '' - segment['_rendered_hl'] = '' - + side = segment['side'] prev_segment = segments[index - 1] if index > 0 else theme.EMPTY_SEGMENT next_segment = segments[index + 1] if index < segments_len - 1 else theme.EMPTY_SEGMENT - compare_segment = next_segment if segment['side'] == 'left' else prev_segment - outer_padding = ' ' if (index == 0 and segment['side'] == 'left') or (index == segments_len - 1 and segment['side'] == 'right') else '' + compare_segment = next_segment if side == 'left' else prev_segment + outer_padding = int(bool( + (index == 0 and side == 'left') or + (index == segments_len - 1 and side == 'right') + )) * ' ' divider_type = 'soft' if compare_segment['highlight']['bg'] == segment['highlight']['bg'] else 'hard' - divider_raw = theme.get_divider(segment['side'], divider_type) - divider_spaces = theme.get_spaces() divider_highlighted = '' contents_raw = segment['contents'] contents_highlighted = '' @@ -288,48 +344,64 @@ class Renderer(object): # Pad segments first if draw_divider: - if segment['side'] == 'left': - contents_raw = outer_padding + (segment['_space_left'] * ' ') + contents_raw + ((divider_spaces + segment['_space_right']) * ' ') + divider_raw = theme.get_divider(side, divider_type).replace(' ', NBSP) + if side == 'left': + contents_raw = ( + outer_padding + (segment['_space_left'] * ' ') + + contents_raw + + ((divider_spaces + segment['_space_right']) * ' ') + ) else: - contents_raw = ((divider_spaces + segment['_space_left']) * ' ') + contents_raw + (segment['_space_right'] * ' ') + outer_padding + contents_raw = ( + ((divider_spaces + segment['_space_left']) * ' ') + + contents_raw + + (segment['_space_right'] * ' ') + outer_padding + ) else: - if segment['side'] == 'left': - contents_raw = outer_padding + (segment['_space_left'] * ' ') + contents_raw + (segment['_space_right'] * ' ') + if side == 'left': + contents_raw = ( + outer_padding + (segment['_space_left'] * ' ') + + contents_raw + + (segment['_space_right'] * ' ') + ) else: - contents_raw = (segment['_space_left'] * ' ') + contents_raw + (segment['_space_right'] * ' ') + outer_padding + contents_raw = ( + (segment['_space_left'] * ' ') + + contents_raw + + (segment['_space_right'] * ' ') + outer_padding + ) # Replace spaces with no-break spaces - divider_raw = divider_raw.replace(' ', NBSP) contents_raw = contents_raw.translate(self.np_character_translations) # Apply highlighting to padded dividers and contents if render_highlighted: - if divider_type == 'soft': - divider_highlight_group_key = 'highlight' if segment['divider_highlight_group'] is None else 'divider_highlight' - divider_fg = segment[divider_highlight_group_key]['fg'] - divider_bg = segment[divider_highlight_group_key]['bg'] - else: - divider_fg = segment['highlight']['bg'] - divider_bg = compare_segment['highlight']['bg'] - divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False) + if draw_divider: + if divider_type == 'soft': + divider_highlight_group_key = 'highlight' if segment['divider_highlight_group'] is None else 'divider_highlight' + divider_fg = segment[divider_highlight_group_key]['fg'] + divider_bg = segment[divider_highlight_group_key]['bg'] + else: + divider_fg = segment['highlight']['bg'] + divider_bg = compare_segment['highlight']['bg'] + divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False) contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) # Append padded raw and highlighted segments to the rendered segment variables if draw_divider: - if segment['side'] == 'left': - segment['_rendered_raw'] += contents_raw + divider_raw - segment['_rendered_hl'] += contents_highlighted + divider_highlighted + if side == 'left': + segment['_rendered_raw'] = contents_raw + divider_raw + segment['_rendered_hl'] = contents_highlighted + divider_highlighted else: - segment['_rendered_raw'] += divider_raw + contents_raw - segment['_rendered_hl'] += divider_highlighted + contents_highlighted + segment['_rendered_raw'] = divider_raw + contents_raw + segment['_rendered_hl'] = divider_highlighted + contents_highlighted else: - if segment['side'] == 'left': - segment['_rendered_raw'] += contents_raw - segment['_rendered_hl'] += contents_highlighted + if side == 'left': + segment['_rendered_raw'] = contents_raw + segment['_rendered_hl'] = contents_highlighted else: - segment['_rendered_raw'] += contents_raw - segment['_rendered_hl'] += contents_highlighted - segment['_len'] = self.strwidth(segment['_rendered_raw']) + segment['_rendered_raw'] = contents_raw + segment['_rendered_hl'] = contents_highlighted yield segment @classmethod diff --git a/powerline/renderers/vim.py b/powerline/renderers/vim.py index b948dc0e..28ff2202 100644 --- a/powerline/renderers/vim.py +++ b/powerline/renderers/vim.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals -from powerline.bindings.vim import vim_get_func, environ +from powerline.bindings.vim import vim_get_func, environ, current_tabpage from powerline.renderer import Renderer from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE from powerline.theme import Theme @@ -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,36 @@ 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, + tabpage=current_tabpage(), + ) + segment_info['tabnr'] = segment_info['tabpage'].number + 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..9ae5f0fe 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,50 @@ 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, + 'draw_inner_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': None, + '_contents_len': None, + '_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 +223,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,9 +234,11 @@ 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, + '_len': None, + '_contents_len': None, '_space_left': 0, '_space_right': 0, } diff --git a/powerline/segments/vim.py b/powerline/segments/vim.py index 5f55bb7b..964f78f1 100644 --- a/powerline/segments/vim.py +++ b/powerline/segments/vim.py @@ -15,7 +15,8 @@ except ImportError: from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption, buffer_name, vim_getwinvar, - register_buffer_cache) + register_buffer_cache, current_tabpage, + list_tabpages) from powerline.theme import requires_segment_info, requires_filesystem_watcher from powerline.lib import add_divider_highlight_group from powerline.lib.vcs import guess, tree_status @@ -485,3 +486,138 @@ def trailing_whitespace(pl, segment_info): ret = None trailing_whitespace_cache[bufnr] = (changedtick, ret) return ret + + +@requires_segment_info +def tabnr(pl, segment_info, show_current=False): + '''Show tabpage number + + :param bool show_current: + If False do not show current tabpage number. This is default because + tabnr is by default only present in tabline. + ''' + try: + tabnr = segment_info['tabnr'] + except KeyError: + return None + if show_current or tabnr != current_tabpage().number: + return str(tabnr) + + +def single_tab(pl, single_text='Bufs', multiple_text='Tabs'): + '''Show one text if there is only one tab and another if there are many + + Mostly useful for tabline to indicate what kind of data is shown there. + + :param str single_text: + Text displayed when there is only one tabpage. May be None if you do not + want to display anything. + :param str multiple_text: + Text displayed when there is more then one tabpage. May be None if you + do not want to display anything. + + Highlight groups used: ``single_tab``, ``many_tabs``. + ''' + if len(list_tabpages()) == 1: + return single_text and [{ + 'contents': single_text, + 'highlight_group': ['single_tab'], + }] + else: + return multiple_text and [{ + 'contents': multiple_text, + 'highlight_group': ['many_tabs'], + }] + + +def tabpage_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=int(window.vars.get('powerline_window_id', -1)), + buffer=buffer, + bufnr=buffer.number, + ) + return segment_info + + +@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 = cur_tabpage.number + + def add_multiplier(tabpage, dct): + dct['priority_multiplier'] = 1 + (0.001 * abs(tabpage.number - cur_tabnr)) + return dct + + return [ + ( + tabpage_updated_segment_info(segment_info, tabpage), + add_multiplier(tabpage, {'mode': ('tab' if tabpage == cur_tabpage else 'nc')}) + ) + for tabpage in list_tabpages() + ] + + +def buffer_updated_segment_info(segment_info, buffer): + segment_info = segment_info.copy() + segment_info.update( + window=None, + winnr=None, + window_id=None, + buffer=buffer, + bufnr=buffer.number, + ) + return segment_info + + +@requires_segment_info +def bufferlister(pl, segment_info): + '''List all buffers in segment_info format + + Specifically generates a list of segment info dictionaries with ``buffer`` + and ``bufnr`` keys set to buffer-specific ones, ``window``, ``winnr`` and + ``window_id`` keys unset. + + Sets segment ``mode`` to either ``buf`` (for current buffer) or ``nc`` + (for all other buffers). + ''' + cur_buffer = vim.current.buffer + cur_bufnr = cur_buffer.number + + def add_multiplier(buffer, dct): + dct['priority_multiplier'] = 1 + (0.001 * abs(buffer.number - cur_bufnr)) + return dct + + return [ + ( + buffer_updated_segment_info(segment_info, buffer), + add_multiplier(buffer, {'mode': ('tab' if buffer == cur_buffer else 'nc')}) + ) + for buffer in vim.buffers + ] + + +@requires_segment_info +def tabbuflister(*args, **kwargs): + if len(list_tabpages()) == 1: + return bufferlister(*args, **kwargs) + else: + return tablister(*args, **kwargs) diff --git a/powerline/theme.py b/powerline/theme.py index 9de033a9..25fa7e45 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 @@ -32,6 +32,11 @@ class Theme(object): run_once=False, shutdown_event=None): self.dividers = theme_config.get('dividers', common_config['dividers']) + self.dividers = dict(( + (key, dict((k, u(v)) + for k, v in val.items())) + for key, val in self.dividers.items() + )) self.spaces = theme_config.get('spaces', common_config['spaces']) self.segments = [] self.EMPTY_SEGMENT = { @@ -91,53 +96,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)) diff --git a/tests/test_plugin_file.vim b/tests/test_plugin_file.vim index 02d916fd..76e08bd8 100755 --- a/tests/test_plugin_file.vim +++ b/tests/test_plugin_file.vim @@ -1,5 +1,7 @@ #!/usr/bin/vim -S set nocompatible +tabedit abc +tabedit def try source powerline/bindings/vim/plugin/powerline.vim catch @@ -16,4 +18,4 @@ if len(mess)>1 call writefile(mess, 'message.fail') cquit endif -quit! +qall! diff --git a/tests/test_provided_config_files.py b/tests/test_provided_config_files.py index 64e9d6a6..a6371588 100644 --- a/tests/test_provided_config_files.py +++ b/tests/test_provided_config_files.py @@ -32,7 +32,7 @@ class TestConfig(TestCase): local_themes_raw = json.load(f)['ext']['vim']['local_themes'] # Don't run tests on external/plugin segments local_themes = dict((k, v) for (k, v) in local_themes_raw.items()) - self.assertEqual(len(buffers), len(local_themes)) + self.assertEqual(len(buffers), len(local_themes) - 1) outputs = {} i = 0 @@ -53,6 +53,8 @@ class TestConfig(TestCase): outputs[out] = (i, (args, kwargs), mode) with vim_module._with('bufname', '/tmp/foo.txt'): + out = powerline.render() + outputs[out] = (-1, (None, None), 'tab') with vim_module._with('globals', powerline_config_path=cfg_path): exclude = set(('no', 'v', 'V', VBLOCK, 's', 'S', SBLOCK, 'R', 'Rv', 'c', 'cv', 'ce', 'r', 'rm', 'r?', '!')) try: diff --git a/tests/test_segments.py b/tests/test_segments.py index 03ccba24..eb86bf09 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -787,6 +787,25 @@ class TestVim(TestCase): self.assertEqual(trailing_whitespace(), None) self.assertEqual(trailing_whitespace(), None) + def test_tabnr(self): + pl = Pl() + segment_info = vim_module._get_segment_info() + self.assertEqual(vim.tabnr(pl=pl, segment_info=segment_info, show_current=True), '1') + self.assertEqual(vim.tabnr(pl=pl, segment_info=segment_info, show_current=False), None) + + def test_single_tab(self): + pl = Pl() + single_tab = partial(vim.single_tab, pl=pl) + with vim_module._with('tabpage'): + self.assertEqual(single_tab(), [{'highlight_group': ['many_tabs'], 'contents': 'Tabs'}]) + self.assertEqual(single_tab(single_text='s', multiple_text='m'), [{'highlight_group': ['many_tabs'], 'contents': 'm'}]) + self.assertEqual(single_tab(multiple_text='m'), [{'highlight_group': ['many_tabs'], 'contents': 'm'}]) + self.assertEqual(single_tab(single_text='s'), [{'highlight_group': ['many_tabs'], 'contents': 'Tabs'}]) + self.assertEqual(single_tab(), [{'highlight_group': ['single_tab'], 'contents': 'Bufs'}]) + self.assertEqual(single_tab(single_text='s', multiple_text='m'), [{'highlight_group': ['single_tab'], 'contents': 's'}]) + self.assertEqual(single_tab(multiple_text='m'), [{'highlight_group': ['single_tab'], 'contents': 'Bufs'}]) + self.assertEqual(single_tab(single_text='s'), [{'highlight_group': ['single_tab'], 'contents': 's'}]) + old_cwd = None diff --git a/tests/test_tabline.vim b/tests/test_tabline.vim new file mode 100755 index 00000000..7289c0ce --- /dev/null +++ b/tests/test_tabline.vim @@ -0,0 +1,34 @@ +#!/usr/bin/vim -S +source powerline/bindings/vim/plugin/powerline.vim +edit abc +tabedit def +tabedit ghi + +try + let &columns = 80 + let result = eval(&tabline[2:]) +catch + call writefile(['Exception while evaluating &tabline', v:exception], 'message.fail') + cquit +endtry + +if result isnot# '%#Pl_240_5789784_235_2500134_NONE# 1 %#Pl_240_5789784_235_2500134_NONE#./%#Pl_244_8421504_235_2500134_bold#abc %#Pl_244_8421504_235_2500134_NONE# %#Pl_240_5789784_235_2500134_NONE#2 %#Pl_240_5789784_235_2500134_NONE#./%#Pl_244_8421504_235_2500134_bold#def %#Pl_235_2500134_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                           %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs ' + call writefile(['Unexpected result', result], 'message.fail') + cquit +endif + +tabonly! + +try + let result = eval(&tabline[2:]) +catch + call writefile(['Exception while evaluating &tabline', v:exception], 'message.fail') + cquit +endtry + +if result isnot# '%#Pl_240_5789784_235_2500134_NONE# ./%#Pl_244_8421504_235_2500134_bold#abc %#Pl_244_8421504_235_2500134_NONE# %#Pl_240_5789784_235_2500134_NONE#./%#Pl_244_8421504_235_2500134_bold#def %#Pl_235_2500134_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                               %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs ' + call writefile(['Unexpected result (2)', result], 'message.fail') + cquit +endif + +qall! diff --git a/tests/vim.py b/tests/vim.py index 0c0cefaa..a0db754a 100644 --- a/tests/vim.py +++ b/tests/vim.py @@ -2,12 +2,13 @@ _log = [] vars = {} vvars = {'version': 703} -_window = 0 +_tabpage = 0 _mode = 'n' _buf_purge_events = set() options = { 'paste': 0, 'ambiwidth': 'single', + 'columns': 80, } _last_bufnr = 0 _highlights = {} @@ -29,6 +30,12 @@ def _set_thread_id(): _set_thread_id() +def _print_log(): + for item in _log: + print (item) + _log[:] = () + + def _vim(func): from functools import wraps from threading import current_thread @@ -67,6 +74,10 @@ class _Buffers(object): def __init__(self): self.d = {} + @_vim + def __len__(self): + return len(self.d) + @_vim def __getitem__(self, item): return self.d[item] @@ -75,38 +86,35 @@ class _Buffers(object): def __setitem__(self, item, value): self.d[item] = value + @_vim + def __iter__(self): + return iter(self.d.values()) + @_vim def __contains__(self, item): return item in self.d @_vim - def __nonzero__(self): - return bool(self.d) - - @_vim - def keys(self): + def _keys(self): return self.d.keys() @_vim - def pop(self, *args, **kwargs): + def _pop(self, *args, **kwargs): return self.d.pop(*args, **kwargs) buffers = _Buffers() -class _Windows(object): +class _ObjList(object): @_vim - def __init__(self): + def __init__(self, objtype): self.l = [] + self.objtype = objtype @_vim def __getitem__(self, item): - return self.l[item] - - @_vim - def __setitem__(self, item, value): - self.l[item] = value + return self.l[item - int(item > 0)] @_vim def __len__(self): @@ -117,24 +125,22 @@ class _Windows(object): return iter(self.l) @_vim - def __nonzero__(self): - return not not self.l - - @_vim - def _pop(self, *args, **kwargs): - return self.l.pop(*args, **kwargs) + def _pop(self, idx): + obj = self.l.pop(idx - 1) + for moved_obj in self.l[idx - 1:]: + moved_obj.number -= 1 + return obj @_vim def _append(self, *args, **kwargs): return self.l.append(*args, **kwargs) - -windows = _Windows() - - -@_vim -def _buffer(): - return windows[_window - 1].buffer.number + @_vim + def _new(self, *args, **kwargs): + number = len(self) + 1 + new_obj = self.objtype(number, *args, **kwargs) + self._append(new_obj) + return new_obj def _construct_result(r): @@ -227,23 +233,50 @@ def eval(expr): import re match = re.match(r'^getwinvar\((\d+), "(\w+)"\)$', expr) if not match: - raise NotImplementedError + raise NotImplementedError(expr) winnr = int(match.group(1)) varname = match.group(2) return _emul_getwinvar(winnr, varname) elif expr.startswith('has_key('): import re match = re.match(r'^has_key\(getwinvar\((\d+), ""\), "(\w+)"\)$', expr) - if not match: - raise NotImplementedError - winnr = int(match.group(1)) - varname = match.group(2) - return 0 + (varname in windows[winnr - 1].vars) + if match: + winnr = int(match.group(1)) + varname = match.group(2) + return 0 + (varname in current.tabpage.windows[winnr].vars) + else: + match = re.match(r'^has_key\(gettabwinvar\((\d+), (\d+), ""\), "(\w+)"\)$', expr) + if not match: + raise NotImplementedError(expr) + tabnr = int(match.group(1)) + winnr = int(match.group(2)) + varname = match.group(3) + return 0 + (varname in tabpages[tabnr].windows[winnr].vars) elif expr == 'getbufvar("%", "NERDTreeRoot").path.str()': import os - assert os.path.basename(buffers[_buffer()].name).startswith('NERD_tree_') + assert os.path.basename(current.buffer.name).startswith('NERD_tree_') return '/usr/include' - raise NotImplementedError + elif expr == 'tabpagenr()': + return current.tabpage.number + elif expr == 'tabpagenr("$")': + return len(tabpages) + elif expr.startswith('tabpagewinnr('): + tabnr = int(expr[len('tabpagewinnr('):-1]) + return tabpages[tabnr].window.number + elif expr.startswith('tabpagebuflist('): + import re + match = re.match(r'tabpagebuflist\((\d+)\)\[(\d+)\]', expr) + tabnr = int(match.group(1)) + winnr = int(match.group(2)) + 1 + return tabpages[tabnr].windows[winnr].buffer.number + elif expr.startswith('gettabwinvar('): + import re + match = re.match(r'gettabwinvar\((\d+), (\d+), "(\w+)"\)', expr) + tabnr = int(match.group(1)) + winnr = int(match.group(2)) + varname = match.group(3) + return tabpages[tabnr].windows[winnr].vars[varname] + raise NotImplementedError(expr) @_vim @@ -277,7 +310,7 @@ def _emul_getbufvar(bufnr, varname): import re if varname[0] == '&': if bufnr == '%': - bufnr = buffers[_buffer()].number + bufnr = current.buffer.number if bufnr not in buffers: return '' try: @@ -289,7 +322,7 @@ def _emul_getbufvar(bufnr, varname): return '' elif re.match('^[a-zA-Z_]+$', varname): if bufnr == '%': - bufnr = buffers[_buffer()].number + bufnr = current.buffer.number if bufnr not in buffers: return '' return buffers[bufnr].vars[varname] @@ -299,25 +332,25 @@ def _emul_getbufvar(bufnr, varname): @_vim @_str_func def _emul_getwinvar(winnr, varname): - return windows[winnr - 1].vars.get(varname, '') + return current.tabpage.windows[winnr].vars.get(varname, '') @_vim def _emul_setwinvar(winnr, varname, value): - windows[winnr - 1].vars[varname] = value + current.tabpage.windows[winnr].vars[varname] = value @_vim def _emul_virtcol(expr): if expr == '.' or isinstance(expr, list): - return windows[_window - 1].cursor[1] + 1 + return current.window.cursor[1] + 1 raise NotImplementedError @_vim def _emul_getpos(expr): if expr == '.' or expr == 'v': - return [0, windows[_window - 1].cursor[0] + 1, windows[_window - 1].cursor[1] + 1, 0] + return [0, current.window.cursor[0] + 1, current.window.cursor[1] + 1, 0] raise NotImplementedError @@ -342,7 +375,7 @@ def _emul_fnamemodify(path, modstring): def _emul_expand(expr): global _abuf if expr == '': - return _abuf or _buffer() + return _abuf or current.buffer.number raise NotImplementedError @@ -362,7 +395,7 @@ def _emul_exists(varname): @_vim def _emul_line2byte(line): - buflines = _buf_lines[_buffer()] + buflines = current.buffer._buf_lines if line == len(buflines) + 1: return sum((len(s) for s in buflines)) + 1 raise NotImplementedError @@ -370,8 +403,8 @@ def _emul_line2byte(line): @_vim def _emul_line(expr): - cursorline = windows[_window - 1].cursor[0] + 1 - numlines = len(_buf_lines[_buffer()]) + cursorline = current.window.cursor[0] + 1 + numlines = len(current.buffer._buf_lines) if expr == 'w0': return max(cursorline - 5, 1) if expr == 'w$': @@ -395,16 +428,15 @@ def _emul_bufname(bufnr): return b'' -_window_ids = [None] _window_id = 0 class _Window(object): - def __init__(self, buffer=None, cursor=(1, 0), width=80): + def __init__(self, number, buffer=None, cursor=(1, 0), width=80): global _window_id self.cursor = cursor self.width = width - self.number = len(windows) + 1 + self.number = number if buffer: if type(buffer) is _Buffer: self.buffer = buffer @@ -412,19 +444,45 @@ class _Window(object): self.buffer = _Buffer(**buffer) else: self.buffer = _Buffer() - windows._append(self) _window_id += 1 - _window_ids.append(_window_id) + self._window_id = _window_id self.options = {} - self.vars = {} + self.vars = { + 'powerline_window_id': self._window_id, + } def __repr__(self): return '' -_buf_lines = {} -_undostate = {} -_undo_written = {} +class _Tabpage(object): + def __init__(self, number): + self.windows = _ObjList(_Window) + self.number = number + + def _new_window(self, **kwargs): + self.window = self.windows._new(**kwargs) + return self.window + + def _close_window(self, winnr, open_window=True): + curwinnr = self.window.number + win = self.windows._pop(winnr) + if self.windows and winnr == curwinnr: + self.window = self.windows[-1] + elif open_window: + current.tabpage._new_window() + return win + + def _close(self): + while self.windows: + self._close_window(1, False) + tabpages._pop(self.number) + _tabpage = len(tabpages) + + +tabpages = _ObjList(_Tabpage) + + _abuf = None @@ -446,10 +504,9 @@ class _Buffer(object): 'fileencoding': 'utf-8', 'textwidth': 80, } - _buf_lines[bufnr] = [''] - from copy import copy - _undostate[bufnr] = [copy(_buf_lines[bufnr])] - _undo_written[bufnr] = len(_undostate[bufnr]) + self._buf_lines = [''] + self._undostate = [self._buf_lines[:]] + self._undo_written = len(self._undostate) buffers[bufnr] = self @property @@ -471,27 +528,27 @@ class _Buffer(object): self._name = os.path.abspath(name) def __getitem__(self, line): - return _buf_lines[self.number][line] + return self._buf_lines[line] def __setitem__(self, line, value): self.options['modified'] = 1 self.vars['changedtick'] += 1 - _buf_lines[self.number][line] = value + self._buf_lines[line] = value from copy import copy - _undostate[self.number].append(copy(_buf_lines[self.number])) + self._undostate.append(copy(self._buf_lines)) def __setslice__(self, *args): self.options['modified'] = 1 self.vars['changedtick'] += 1 - _buf_lines[self.number].__setslice__(*args) + self._buf_lines.__setslice__(*args) from copy import copy - _undostate[self.number].append(copy(_buf_lines[self.number])) + self._undostate.append(copy(self._buf_lines)) def __getslice__(self, *args): - return _buf_lines[self.number].__getslice__(*args) + return self._buf_lines.__getslice__(*args) def __len__(self): - return len(_buf_lines[self.number]) + return len(self._buf_lines) def __repr__(self): return '' @@ -514,22 +571,20 @@ class _Buffer(object): exec(event, __main__.__dict__) finally: _abuf = None - if _buf_lines: - _buf_lines.pop(bufnr) - if _undostate: - _undostate.pop(bufnr) - if _undo_written: - _undo_written.pop(bufnr) class _Current(object): @property def buffer(self): - return buffers[_buffer()] + return self.window.buffer @property def window(self): - return windows[_window - 1] + return self.tabpage.window + + @property + def tabpage(self): + return tabpages[_tabpage - 1] current = _Current() @@ -549,7 +604,7 @@ def _init(): for varname, value in globals().items(): if varname[0] != '_': _dict[varname] = value - _new() + _tabnew() return _dict @@ -561,12 +616,17 @@ def _get_segment_info(): } mode = _mode mode = mode_translations.get(mode, mode) + window = current.window + buffer = current.buffer + tabpage = current.tabpage return { - 'window': windows[_window - 1], - 'winnr': _window, - 'buffer': buffers[_buffer()], - 'bufnr': _buffer(), - 'window_id': _window_ids[_window], + 'window': window, + 'winnr': window.number, + 'buffer': buffer, + 'bufnr': buffer.number, + 'tabpage': tabpage, + 'tabnr': tabpage.number, + 'window_id': window._window_id, 'mode': mode, } @@ -588,85 +648,78 @@ def _start_mode(mode): @_vim def _undo(): - if len(_undostate[_buffer()]) == 1: + if len(current.buffer._undostate) == 1: return - _undostate[_buffer()].pop(-1) - _buf_lines[_buffer()] = _undostate[_buffer()][-1] - buf = current.buffer - if _undo_written[_buffer()] == len(_undostate[_buffer()]): - buf.options['modified'] = 0 + buffer = current.buffer + buffer._undostate.pop(-1) + buffer._buf_lines = buffer._undostate[-1] + if buffer._undo_written == len(buffer._undostate): + buffer.options['modified'] = 0 @_vim def _edit(name=None): - global _last_bufnr - if _buffer() and buffers[_buffer()].name is None: - buf = buffers[_buffer()] - buf.name = name + if current.buffer.name is None: + buffer = current.buffer + buffer.name = name else: - buf = _Buffer(name) - windows[_window - 1].buffer = buf + buffer = _Buffer(name) + current.window.buffer = buffer + + +@_vim +def _tabnew(name=None): + global windows + tabpage = tabpages._new() + windows = tabpage.windows + _tabpage = len(tabpages) + _new(name) + return tabpage @_vim def _new(name=None): - global _window - _Window(buffer={'name': name}) - _window = len(windows) + current.tabpage._new_window(buffer={'name': name}) @_vim def _split(): - global _window - _Window(buffer=buffers[_buffer()]) - _window = len(windows) - - -@_vim -def _del_window(winnr): - win = windows._pop(winnr - 1) - _window_ids.pop(winnr) - return win + current.tabpage._new_window(buffer=current.buffer) @_vim def _close(winnr, wipe=True): - global _window - win = _del_window(winnr) - if _window == winnr: - _window = len(windows) + win = current.tabpage._close_window(winnr) if wipe: - for w in windows: + for w in current.tabpage.windows: if w.buffer.number == win.buffer.number: break else: _bw(win.buffer.number) - if not windows: - _Window() @_vim def _bw(bufnr=None): - bufnr = bufnr or _buffer() + bufnr = bufnr or current.buffer.number winnr = 1 - for win in windows: + for win in current.tabpage.windows: if win.buffer.number == bufnr: _close(winnr, wipe=False) winnr += 1 - buffers.pop(bufnr) + buffers._pop(bufnr) if not buffers: _Buffer() - _b(max(buffers.keys())) + _b(max(buffers._keys())) @_vim def _b(bufnr): - windows[_window - 1].buffer = buffers[bufnr] + current.window.buffer = buffers[bufnr] @_vim def _set_cursor(line, col): - windows[_window - 1].cursor = (line, col) + current.window.cursor = (line, col) if _mode == 'n': _launch_event('CursorMoved') elif _mode == 'i': @@ -675,12 +728,12 @@ def _set_cursor(line, col): @_vim def _get_buffer(): - return buffers[_buffer()] + return current.buffer @_vim def _set_bufoption(option, value, bufnr=None): - buffers[bufnr or _buffer()].options[option] = value + buffers[bufnr or current.buffer.number].options[option] = value if option == 'filetype': _launch_event('FileType') @@ -691,7 +744,7 @@ class _WithNewBuffer(object): def __enter__(self): self.call() - self.bufnr = _buffer() + self.bufnr = current.buffer.number return _get_segment_info() def __exit__(self, *args): @@ -720,7 +773,7 @@ class _WithBufOption(object): self.new = new def __enter__(self): - self.buffer = buffers[_buffer()] + self.buffer = current.buffer self.old = _set_dict(self.buffer.options, self.new, _set_bufoption)[0] def __exit__(self, *args): @@ -768,7 +821,7 @@ class _WithBufName(object): def __enter__(self): import os - buffer = buffers[_buffer()] + buffer = current.buffer self.buffer = buffer self.old = buffer.name buffer.name = self.new @@ -780,6 +833,18 @@ class _WithBufName(object): self.buffer.name = self.old +class _WithNewTabPage(object): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + def __enter__(self): + self.tab = _tabnew(*self.args, **self.kwargs) + + def __exit__(self, *args): + self.tab._close() + + @_vim def _with(key, *args, **kwargs): if key == 'buffer': @@ -795,11 +860,13 @@ def _with(key, *args, **kwargs): elif key == 'globals': return _WithDict(vars, **kwargs) elif key == 'wvars': - return _WithDict(windows[_window - 1].vars, **kwargs) + return _WithDict(current.window.vars, **kwargs) elif key == 'environ': return _WithDict(_environ, **kwargs) elif key == 'split': return _WithSplit() + elif key == 'tabpage': + return _WithNewTabPage(*args, **kwargs) class error(Exception):