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: