From 16c01e8d644d2f5c733f1aa5e0606b4511d95052 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 12 Jul 2014 23:39:45 +0400 Subject: [PATCH 1/2] Add support for display option --- docs/source/configuration.rst | 15 +++++++++++---- powerline/lint/__init__.py | 28 +++++++++++++++++++--------- powerline/segment.py | 3 +++ powerline/theme.py | 17 +++++++++-------- tests/test_configuration.py | 10 ++++++++++ 5 files changed, 52 insertions(+), 21 deletions(-) diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index c3c8c110..23a3022e 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -325,13 +325,14 @@ Themes A dict where keys are segment names or strings ``{module}.{name}``. Used to specify default values for various keys: :ref:`after `, + :ref:`args ` (only for function segments), :ref:`before `, :ref:`contents ` (only for string segments if :ref:`name ` is defined), - :ref:`args ` (only for function segments). When - using :ref:`local themes ` values of these keys are - first searched in the segment description, then in ``segment_data`` key of - a local theme, then in ``segment_data`` key of a :ref:`default theme + :ref:`display ` values of these + keys are first searched in the segment description, then in ``segment_data`` + key of a local theme, then in ``segment_data`` key of a :ref:`default theme `. For the :ref:`default theme ` itself step 2 is obviously avoided. @@ -455,6 +456,12 @@ Themes A list of modes where this segment will be included: The segment is *not* included in any modes, *except* for the modes in this list. + ``display`` + .. _config-themes-seg-display: + + Boolean. If false disables displaying of the segment. + Defaults to ``True``. + Segments ======== diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index 074e00e8..1976e876 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -668,17 +668,25 @@ shell_colorscheme_spec = (Spec( ).context_message('Error while loading shell colorscheme')) -generic_keys = set(('exclude_modes', 'include_modes', 'width', 'align', 'name', 'draw_soft_divider', 'draw_hard_divider', 'priority', 'after', 'before')) +generic_keys = set(( + 'exclude_modes', 'include_modes', + 'width', 'align', + 'name', + 'draw_soft_divider', 'draw_hard_divider', + 'priority', + 'after', 'before', + 'display' +)) 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')), - } + 'function': set(('args', 'module', 'draw_inner_divider')), + 'string': set(('contents', 'type', 'highlight_group', 'divider_highlight_group')), + 'filler': set(('type', 'highlight_group', 'divider_highlight_group')), +} required_keys = { - 'function': set(), - 'string': set(('contents',)), - 'filler': set(), - } + 'function': set(), + 'string': set(('contents',)), + 'filler': set(), +} function_keys = set(('args', 'module')) highlight_keys = set(('highlight_group', 'name')) @@ -1071,6 +1079,7 @@ segments_spec = Spec().optional().list( 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(), @@ -1101,6 +1110,7 @@ theme_spec = (Spec( Spec( after=Spec().type(unicode).optional(), before=Spec().type(unicode).optional(), + display=Spec().type(bool).optional(), args=args_spec().func(lambda *args, **kwargs: check_args(get_all_possible_segments, *args, **kwargs)), contents=Spec().type(unicode).optional(), ), diff --git a/powerline/segment.py b/powerline/segment.py index d0ab2746..836a5739 100644 --- a/powerline/segment.py +++ b/powerline/segment.py @@ -82,6 +82,9 @@ def gen_segment_getter(pl, ext, common_config, theme_configs, default_module=Non pl.exception('Failed to generate segment from {0!r}: {1}', segment, str(e), prefix='segment_generator') return None + if not get_key(segment, module, 'display', True): + return None + if segment_type == 'function': highlight_group = [module + '.' + segment['name'], segment['name']] else: diff --git a/powerline/theme.py b/powerline/theme.py index 6450fab3..9de033a9 100644 --- a/powerline/theme.py +++ b/powerline/theme.py @@ -49,14 +49,15 @@ class Theme(object): for side in ['left', 'right']: for segment in segdict.get(side, []): segment = get_segment(segment, side) - if not run_once: - if segment['startup']: - try: - segment['startup'](pl, shutdown_event) - except Exception as e: - pl.error('Exception during {0} startup: {1}', segment['name'], str(e)) - continue - self.segments[-1][side].append(segment) + if segment: + if not run_once: + if segment['startup']: + try: + segment['startup'](pl, shutdown_event) + except Exception as e: + pl.error('Exception during {0} startup: {1}', segment['name'], str(e)) + continue + self.segments[-1][side].append(segment) def shutdown(self): for line in self.segments: diff --git a/tests/test_configuration.py b/tests/test_configuration.py index b532db81..faf355b5 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -171,6 +171,16 @@ class TestLines(TestRender): ], width=10) +class TestSegments(TestRender): + @add_p_arg + def test_display(self, p): + with replace_item(globals(), 'config', deepcopy(config)): + config['themes/test/default']['segments']['left'][0]['display'] = False + config['themes/test/default']['segments']['left'][1]['display'] = True + config['themes/test/default']['segments']['right'][0]['display'] = False + self.assertRenderEqual(p, '{344} g{4-}>>{--}') + + class TestColorschemesHierarchy(TestRender): @add_p_arg def test_group_redirects(self, p): From 9e8c115eea6080b0a9e7c41444f203a91ea36505 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sat, 2 Aug 2014 18:22:45 +0400 Subject: [PATCH 2/2] Add trailing whitespace segment Note: by default this segment is disabled. Until #923 it may only be enabled by copying the whole file and changing "enabled" to "true". After #923 it may be enabled by having `~/.config/powerline/themes/vim/default.json` with the following contents: { "segment_data": { "trailing_whitespace": { "display": true } } } Fixes #388 --- powerline/bindings/vim/__init__.py | 27 ++++++++ .../config_files/themes/vim/default.json | 6 ++ powerline/segments/vim.py | 61 ++++++++++++++++++- powerline/vim.py | 18 +++++- tests/test_segments.py | 20 ++++++ tests/vim.py | 50 ++++++++++++++- 6 files changed, 177 insertions(+), 5 deletions(-) diff --git a/powerline/bindings/vim/__init__.py b/powerline/bindings/vim/__init__.py index 40e1f616..6830365c 100644 --- a/powerline/bindings/vim/__init__.py +++ b/powerline/bindings/vim/__init__.py @@ -159,4 +159,31 @@ def powerline_vim_strtrans_error(e): codecs.register_error('powerline_vim_strtrans_error', powerline_vim_strtrans_error) +did_autocmd = False +buffer_caches = [] + + +def register_buffer_cache(cachedict): + global did_autocmd + global buffer_caches + from powerline.vim import get_default_pycmd, pycmd + if not did_autocmd: + import __main__ + __main__.powerline_on_bwipe = on_bwipe + vim.command('augroup Powerline') + vim.command(' autocmd! BufWipeout * :{pycmd} powerline_on_bwipe()'.format( + pycmd=(pycmd or get_default_pycmd()))) + vim.command('augroup END') + did_autocmd = True + buffer_caches.append(cachedict) + return cachedict + + +def on_bwipe(): + global buffer_caches + bufnr = int(vim.eval('expand("")')) + for cachedict in buffer_caches: + cachedict.pop(bufnr, None) + + environ = VimEnviron() diff --git a/powerline/config_files/themes/vim/default.json b/powerline/config_files/themes/vim/default.json index 5f10912d..05204943 100644 --- a/powerline/config_files/themes/vim/default.json +++ b/powerline/config_files/themes/vim/default.json @@ -58,6 +58,12 @@ "name": "modified_indicator", "before": " " }, + { + "exclude_modes": ["i", "R", "Rv"], + "name": "trailing_whitespace", + "display": false, + "priority": 60 + }, { "exclude_modes": ["nc"], "module": "powerline.segments.plugin.syntastic", diff --git a/powerline/segments/vim.py b/powerline/segments/vim.py index f1647dee..62f4fe25 100644 --- a/powerline/segments/vim.py +++ b/powerline/segments/vim.py @@ -8,8 +8,14 @@ try: except ImportError: vim = {} # NOQA +try: + from __builtin__ import xrange as range +except ImportError: + pass + from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption, - buffer_name, vim_getwinvar) + buffer_name, vim_getwinvar, + register_buffer_cache) 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 @@ -17,6 +23,7 @@ from powerline.lib.humanize_bytes import humanize_bytes from powerline.lib import wraps_saveargs as wraps from collections import defaultdict + vim_funcs = { 'virtcol': vim_get_func('virtcol', rettype=int), 'getpos': vim_get_func('getpos'), @@ -420,3 +427,55 @@ def file_vcs_status(pl, segment_info, create_watcher): 'highlight_group': ['file_vcs_status_' + status, 'file_vcs_status'], }) return ret + + +trailing_whitespace_cache = None + + +@requires_segment_info +def trailing_whitespace(pl, segment_info): + '''Return the line number for trailing whitespaces + + It is advised not to use this segment in insert mode: in Insert mode it will + iterate over all lines in buffer each time you happen to type a character + which may cause lags. It will also show you whitespace warning each time you + happen to type space. + + Highlight groups used: ``trailing_whitespace`` or ``warning``. + ''' + global trailing_whitespace_cache + if trailing_whitespace_cache is None: + trailing_whitespace_cache = register_buffer_cache(defaultdict(lambda: (0, None))) + bufnr = segment_info['bufnr'] + changedtick = getbufvar(bufnr, 'changedtick') + if trailing_whitespace_cache[bufnr][0] == changedtick: + return trailing_whitespace_cache[bufnr][1] + else: + buf = segment_info['buffer'] + bws = b' \t' + sws = str(bws) + for i in range(len(buf)): + try: + line = buf[i] + except UnicodeDecodeError: # May happen in Python 3 + if hasattr(vim, 'bindeval'): + line = vim.bindeval('getbufline({0}, {1})'.format( + bufnr, i + 1)) + has_trailing_ws = (line[-1] in bws) + else: + line = vim.eval('strtrans(getbufline({0}, {1}))'.format( + bufnr, i + 1)) + has_trailing_ws = (line[-1] in bws) + else: + has_trailing_ws = (line and line[-1] in sws) + if has_trailing_ws: + break + if has_trailing_ws: + ret = [{ + 'contents': str(i + 1), + 'highlight_group': ['trailing_whitespace', 'warning'], + }] + else: + ret = None + trailing_whitespace_cache[bufnr] = (changedtick, ret) + return ret diff --git a/powerline/vim.py b/powerline/vim.py index 829961e9..05d42de4 100644 --- a/powerline/vim.py +++ b/powerline/vim.py @@ -2,6 +2,7 @@ from __future__ import absolute_import +import sys from powerline.bindings.vim import vim_get_func, vim_getvar from powerline import Powerline from powerline.lib import mergedicts @@ -155,14 +156,27 @@ class VimPowerline(Powerline): __main__.__dict__))) +pycmd = None + + +def set_pycmd(new_pycmd): + global pycmd + pycmd = new_pycmd + + +def get_default_pycmd(): + return 'python' if sys.version_info < (3,) else 'python3' + + def setup(pyeval=None, pycmd=None, can_replace_pyeval=True): - import sys import __main__ if not pyeval: pyeval = 'pyeval' if sys.version_info < (3,) else 'py3eval' can_replace_pyeval = True if not pycmd: - pycmd = 'python' if sys.version_info < (3,) else 'python3' + pycmd = get_default_pycmd() + + set_pycmd(pycmd) # pyeval() and vim.bindeval were both introduced in one patch if not hasattr(vim, 'bindeval') and can_replace_pyeval: diff --git a/tests/test_segments.py b/tests/test_segments.py index 441d6951..de467728 100644 --- a/tests/test_segments.py +++ b/tests/test_segments.py @@ -757,6 +757,26 @@ class TestVim(TestCase): with replace_attr(vim, 'guess', get_dummy_guess(status=lambda file: 'M')): self.assertEqual(file_vcs_status(segment_info=segment_info), None) + def test_trailing_whitespace(self): + pl = Pl() + with vim_module._with('buffer', 'tws') as segment_info: + trailing_whitespace = partial(vim.trailing_whitespace, pl=pl, segment_info=segment_info) + self.assertEqual(trailing_whitespace(), None) + self.assertEqual(trailing_whitespace(), None) + vim_module.current.buffer[0] = ' ' + self.assertEqual(trailing_whitespace(), [{ + 'highlight_group': ['trailing_whitespace', 'warning'], + 'contents': '1', + }]) + self.assertEqual(trailing_whitespace(), [{ + 'highlight_group': ['trailing_whitespace', 'warning'], + 'contents': '1', + }]) + vim_module.current.buffer[0] = '' + self.assertEqual(trailing_whitespace(), None) + self.assertEqual(trailing_whitespace(), None) + + old_cwd = None diff --git a/tests/vim.py b/tests/vim.py index 89d12b65..0c0cefaa 100644 --- a/tests/vim.py +++ b/tests/vim.py @@ -167,8 +167,14 @@ def _log_print(): sys.stdout.write(repr(entry) + '\n') +_current_group = None +_on_wipeout = [] + + @_vim def command(cmd): + global _current_group + cmd = cmd.lstrip() if cmd.startswith('let g:'): import re varname, value = re.compile(r'^let g:(\w+)\s*=\s*(.*)').match(cmd).groups() @@ -179,6 +185,26 @@ def command(cmd): elif cmd.startswith('function! Powerline_plugin_ctrlp'): # Ignore CtrlP updating functions pass + elif cmd.startswith('augroup'): + augroup = cmd.partition(' ')[2] + if augroup.upper() == 'END': + _current_group = None + else: + _current_group = augroup + elif cmd.startswith('autocmd'): + rest = cmd.partition(' ')[2] + auevent, rest = rest.partition(' ')[::2] + pattern, aucmd = rest.partition(' ')[::2] + if auevent != 'BufWipeout' or pattern != '*': + raise NotImplementedError + import sys + if sys.version_info < (3,): + if not aucmd.startswith(':python '): + raise NotImplementedError + else: + if not aucmd.startswith(':python3 '): + raise NotImplementedError + _on_wipeout.append(aucmd.partition(' ')[2]) else: raise NotImplementedError @@ -314,8 +340,9 @@ def _emul_fnamemodify(path, modstring): @_vim @_str_func def _emul_expand(expr): + global _abuf if expr == '': - return _buffer() + return _abuf or _buffer() raise NotImplementedError @@ -398,6 +425,7 @@ class _Window(object): _buf_lines = {} _undostate = {} _undo_written = {} +_abuf = None class _Buffer(object): @@ -408,7 +436,7 @@ class _Buffer(object): self.number = bufnr # FIXME Use unicode() for python-3 self.name = name - self.vars = {} + self.vars = {'changedtick': 1} self.options = { 'modified': 0, 'readonly': 0, @@ -447,12 +475,14 @@ class _Buffer(object): def __setitem__(self, line, value): self.options['modified'] = 1 + self.vars['changedtick'] += 1 _buf_lines[self.number][line] = value from copy import copy _undostate[self.number].append(copy(_buf_lines[self.number])) def __setslice__(self, *args): self.options['modified'] = 1 + self.vars['changedtick'] += 1 _buf_lines[self.number].__setslice__(*args) from copy import copy _undostate[self.number].append(copy(_buf_lines[self.number])) @@ -467,7 +497,23 @@ class _Buffer(object): return '' def __del__(self): + global _abuf bufnr = self.number + try: + import __main__ + except ImportError: + pass + except RuntimeError: + # Module may have already been garbage-collected + pass + else: + if _on_wipeout: + _abuf = bufnr + try: + for event in _on_wipeout: + exec(event, __main__.__dict__) + finally: + _abuf = None if _buf_lines: _buf_lines.pop(bufnr) if _undostate: