From d93f58cd13546c62b944103ba4da2ba6a4219a08 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Mon, 9 May 2016 22:29:51 +0200 Subject: [PATCH 01/59] Release 0.0.18 --- CHANGELOG | 6 ++++++ pyvim/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c038368..c7250eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ CHANGELOG ========= +0.0.18: 2016-05-09 +------------------ + +Upgrade to ptpython==0.34. + + 0.0.17: 2016-05-05 ----------------- diff --git a/pyvim/__init__.py b/pyvim/__init__.py index 9f8050e..f10ea9b 100755 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '0.0.17' +__version__ = '0.0.18' diff --git a/setup.py b/setup.py index 87bba8b..2f60acc 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ packages=find_packages('.'), install_requires = [ 'prompt_toolkit>=1.0.0,<1.1.0', - 'ptpython==0.33', # For the Python completion (with Jedi.) + 'ptpython==0.34', # For the Python completion (with Jedi.) 'pyflakes', # For Python error reporting. 'pygments', # For the syntax highlighting. 'docopt', # For command line arguments. From def2c433660a9da348093d875e27897413f17e4e Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Fri, 20 May 2016 22:59:49 +0200 Subject: [PATCH 02/59] Better LANG support. --- pyvim/layout.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/pyvim/layout.py b/pyvim/layout.py index 96237bb..5cae67b 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -29,12 +29,26 @@ import pyvim.window_arrangement as window_arrangement import re +import sys __all__ = ( 'EditorLayout', 'get_terminal_title', ) +def _try_char(character, backup, encoding=sys.stdout.encoding): + """ + Return `character` if it can be encoded using sys.stdout, else return the + backup character. + """ + if character.encode(encoding, 'replace') == b'?': + return backup + else: + return character + + +TABSTOP_DOT = _try_char('\u2508', '.') + class TabsControl(TokenListControl): """ @@ -441,6 +455,11 @@ def __init__(self, editor, manager, window_arrangement): ] ) + def get_vertical_border_char(self, cli): + " Return the character to be used for the vertical border. " + return Char(char=_try_char('\u2502', '|', cli.output.encoding()), + token=Token.FrameBorder) + def update(self): """ Update layout to match the layout as described in the @@ -464,8 +483,10 @@ def create_layout_from_node(node): children = [] for n in node: children.append(create_layout_from_node(n)) - children.append(Window(width=LayoutDimension.exact(1), - content=FillControl('\u2502', token=Token.FrameBorder))) + children.append( + Window(width=LayoutDimension.exact(1), + content=FillControl( + get_char=self.get_vertical_border_char))) children.pop() return VSplit(children) @@ -524,14 +545,15 @@ def preview_search(cli): # selection processor, otherwise, we won't see these spaces # selected.) ConditionalProcessor( - ShowTrailingWhiteSpaceProcessor(), + ShowTrailingWhiteSpaceProcessor(char=_try_char('\xb7', '.')), Condition(lambda cli: self.editor.display_unprintable_characters)), # Replace tabs by spaces. TabsProcessor( tabstop=Integer.from_callable(lambda: self.editor.tabstop), get_char1=(lambda cli: '|' if self.editor.display_unprintable_characters else ' '), - get_char2=(lambda cli: '\u2508' if self.editor.display_unprintable_characters else ' '), + get_char2=(lambda cli: _try_char('\u2508', '.', cli.output.encoding()) + if self.editor.display_unprintable_characters else ' '), ), # Reporting of errors, for Pyflakes. From 6729289edf5b2979e08d7623eac081341a1acde6 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 22 May 2016 12:03:37 +0200 Subject: [PATCH 03/59] In example config: map jj to esc. --- examples/config/pyvimrc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/config/pyvimrc b/examples/config/pyvimrc index 6de61de..7702950 100644 --- a/examples/config/pyvimrc +++ b/examples/config/pyvimrc @@ -2,6 +2,8 @@ """ Pyvim configuration. Save to file to: ~/.pyvimrc """ +from prompt_toolkit.filters import ViInsertMode +from prompt_toolkit.key_binding.input_processor import KeyPress from prompt_toolkit.keys import Keys from subprocess import call import six @@ -44,6 +46,15 @@ def configure(editor): # Add custom key bindings: + @editor.add_key_binding('j', 'j', filter=ViInsertMode()) + def _(event): + """ + Typing 'jj' in Insert mode, should go back to navigation mode. + + (imap jj ) + """ + event.cli.input_processor.feed(KeyPress(Keys.Escape)) + @editor.add_key_binding(Keys.F9) def save_and_execute_python_file(event): """ From 1c3632137f0f22215f3995dbb52171ef6ddada79 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 22 May 2016 15:44:53 +0200 Subject: [PATCH 04/59] Take output encoding into account in ShowTrailingWhiteSpaceProcessor. --- pyvim/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyvim/layout.py b/pyvim/layout.py index 5cae67b..f5505fc 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -545,7 +545,7 @@ def preview_search(cli): # selection processor, otherwise, we won't see these spaces # selected.) ConditionalProcessor( - ShowTrailingWhiteSpaceProcessor(char=_try_char('\xb7', '.')), + ShowTrailingWhiteSpaceProcessor(), Condition(lambda cli: self.editor.display_unprintable_characters)), # Replace tabs by spaces. From 7e1c7bfb505cefba46868b92e29cee4a0c25c252 Mon Sep 17 00:00:00 2001 From: Lukas Nemec Date: Fri, 1 May 2015 11:23:41 +0200 Subject: [PATCH 05/59] Rewritten tests to pytest, added tox and Readme testing section. --- README.rst | 23 ++++++++++++++++++++ tests/conftest.py | 26 ++++++++++++++++++++++ tests/test_window_arrangements.py | 36 +++++++++++-------------------- tox.ini | 9 ++++++++ 4 files changed, 70 insertions(+), 24 deletions(-) create mode 100644 tests/conftest.py create mode 100644 tox.ini diff --git a/README.rst b/README.rst index 92d0c37..c0184ca 100644 --- a/README.rst +++ b/README.rst @@ -145,6 +145,29 @@ Maybe we will also have line folding and probably block editing. Maybe some day we will have a built-in Python debugger or mouse support. We'll see. :) +Testing +------- + +To run all tests, install pytest: + + pip install pytest + +And then run from root pyvim directory: + + py.test + +To test pyvim against all supported python versions, install tox: + + pip install tox + +And then run from root pyvim directory: + + tox + +You need to have installed all the supported versions of python in order to run +tox command successfully (2.6, 2.7, 3.1, 3.2, 3.3, 3.4). + + Why did I create Pyvim? ----------------------- diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..ee82160 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,26 @@ +from __future__ import unicode_literals + +import pytest + +from prompt_toolkit.buffer import Buffer +from pyvim.window_arrangement import TabPage, EditorBuffer, Window + + +@pytest.fixture +def prompt_buffer(): + return Buffer() + + +@pytest.fixture +def editor_buffer(prompt_buffer): + return EditorBuffer(prompt_buffer, 'b1') + + +@pytest.fixture +def window(editor_buffer): + return Window(editor_buffer) + + +@pytest.fixture +def tab_page(window): + return TabPage(window) diff --git a/tests/test_window_arrangements.py b/tests/test_window_arrangements.py index fe136a1..8ede6a8 100644 --- a/tests/test_window_arrangements.py +++ b/tests/test_window_arrangements.py @@ -1,33 +1,21 @@ from __future__ import unicode_literals from prompt_toolkit.buffer import Buffer -from pyvim.window_arrangement import TabPage, EditorBuffer, Window, HSplit, VSplit +from pyvim.window_arrangement import EditorBuffer, VSplit -import unittest +def test_initial(window, tab_page): + assert isinstance(tab_page.root, VSplit) + assert tab_page.root == [window] -class BufferTest(unittest.TestCase): - def setUp(self): - b = Buffer() - eb = EditorBuffer('b1', b) - self.window = Window(eb) - self.tabpage = TabPage(self.window) - def test_initial(self): - self.assertIsInstance(self.tabpage.root, VSplit) - self.assertEqual(self.tabpage.root, [self.window]) +def test_vsplit(tab_page): + # Create new buffer. + b = Buffer() + eb = EditorBuffer(b, 'b1') - def test_vsplit(self): - # Create new buffer. - b = Buffer() - eb = EditorBuffer('b1', b) + # Insert in tab, by splitting. + tab_page.vsplit(eb) - # Insert in tab, by splitting. - self.tabpage.vsplit(eb) - - self.assertIsInstance(self.tabpage.root, VSplit) - self.assertEqual(len(self.tabpage.root), 2) - - -if __name__ == '__main__': - unittest.main() + assert isinstance(tab_page.root, VSplit) + assert len(tab_page.root) == 2 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..6ae9ac6 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +envlist = py26,py27,py31,py32,py33,py34 + +[testenv] +deps=mistune + pytest + prompt-toolkit==0.34 + ptpython==0.8 + pyflakes From 385acfd13da84649b361155c1d8496d2028ab828 Mon Sep 17 00:00:00 2001 From: Lukas Nemec Date: Fri, 1 May 2015 12:47:27 +0200 Subject: [PATCH 06/59] Removed mistune --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 6ae9ac6..e91fca8 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,7 @@ envlist = py26,py27,py31,py32,py33,py34 [testenv] -deps=mistune - pytest +deps=pytest prompt-toolkit==0.34 ptpython==0.8 pyflakes From b3206a44fc8552a8ab5ec087575d04589f033350 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Tue, 24 May 2016 21:50:18 +0200 Subject: [PATCH 07/59] Added .travis.yml file for Travis integration + more Python versions for tests. --- .travis.yml | 15 +++++++++++++++ README.rst | 2 +- tox.ini | 6 +++--- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c6f4220 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +sudo: false +language: python +python: '3.5' +env: + - TOXENV=py26 + - TOXENV=py27 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=py35 + - TOXENV=pypy + - TOXENV=pypy3 +install: + - travis_retry pip install tox pytest +script: + - tox diff --git a/README.rst b/README.rst index c0184ca..a42f42d 100644 --- a/README.rst +++ b/README.rst @@ -165,7 +165,7 @@ And then run from root pyvim directory: tox You need to have installed all the supported versions of python in order to run -tox command successfully (2.6, 2.7, 3.1, 3.2, 3.3, 3.4). +tox command successfully. Why did I create Pyvim? diff --git a/tox.ini b/tox.ini index e91fca8..92f7635 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ [tox] -envlist = py26,py27,py31,py32,py33,py34 +envlist = py26,py27,py31,py32,py33,py34,py35,pypy,pypy3 [testenv] deps=pytest - prompt-toolkit==0.34 - ptpython==0.8 + prompt-toolkit + ptpython pyflakes From 192d6f902809fb2162e9afe11d2d7e024dbf6f55 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Tue, 24 May 2016 21:52:02 +0200 Subject: [PATCH 08/59] Added Travis built-status to the README. --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index a42f42d..8ea9ad1 100644 --- a/README.rst +++ b/README.rst @@ -12,6 +12,8 @@ pyvim Issues, questions, wishes, comments, feedback, remarks? Please create a GitHub issue, I appreciate it. +|Build Status| + Installation ------------ @@ -217,3 +219,7 @@ Thanks - To Jedi, pyflakes and the docopt Python libraries. - To the Python wcwidth port of Jeff Quast for support of double width characters. - To Guido van Rossum, for creating Python. + + +.. |Build Status| image:: https://api.travis-ci.org/jonathanslenders/pyvim.svg?branch=master + :target: https://travis-ci.org/jonathanslenders/pyvim# From 8c9a1decf177405b93f14229542206da3de9aa41 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 31 Jul 2016 18:36:42 +0200 Subject: [PATCH 09/59] input_processor.arg is now a string, not a number. --- pyvim/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyvim/layout.py b/pyvim/layout.py index f5505fc..6c9a51d 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -374,7 +374,7 @@ class SimpleArgToolbar(ConditionalContainer): def __init__(self): def get_tokens(cli): if cli.input_processor.arg is not None: - return [(Token.Arg, ' %i ' % cli.input_processor.arg)] + return [(Token.Arg, ' %s ' % cli.input_processor.arg)] else: return [] From 2edbec0197015cbfea0d8340cd03acdaa2af4b8f Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Thu, 4 Aug 2016 21:38:53 +0200 Subject: [PATCH 10/59] Release 0.0.19 --- CHANGELOG | 8 ++++++++ pyvim/__init__.py | 2 +- setup.py | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c7250eb..b454f99 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,14 @@ CHANGELOG ========= +0.0.19: 2016-08-04 +------------------ + +- Take output encoding ($LANG) into account in several places. + +Upgrade to prompt_toolkit==1.0.4 + + 0.0.18: 2016-05-09 ------------------ diff --git a/pyvim/__init__.py b/pyvim/__init__.py index f10ea9b..d7ac0c3 100755 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '0.0.18' +__version__ = '0.0.19' diff --git a/setup.py b/setup.py index 2f60acc..010299e 100644 --- a/setup.py +++ b/setup.py @@ -21,8 +21,8 @@ long_description=long_description, packages=find_packages('.'), install_requires = [ - 'prompt_toolkit>=1.0.0,<1.1.0', - 'ptpython==0.34', # For the Python completion (with Jedi.) + 'prompt_toolkit>=1.0.4,<1.1.0', + 'ptpython>=0.34', # For the Python completion (with Jedi.) 'pyflakes', # For Python error reporting. 'pygments', # For the syntax highlighting. 'docopt', # For command line arguments. From fd4f414ce6e7dc163308fc6900aa11c0e830be7e Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Wed, 28 Sep 2016 20:38:12 +0200 Subject: [PATCH 11/59] Better Jedi integration for completion of Python files. - Use jedi.Script instead of Jedi.Interpreter - Also, don't depend on ptpython code anymore. --- pyvim/completion.py | 69 +++++++++++++++++++++++++++++++++++++++++++-- setup.py | 1 - 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/pyvim/completion.py b/pyvim/completion.py index 6d21e45..b659f0b 100644 --- a/pyvim/completion.py +++ b/pyvim/completion.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals from prompt_toolkit.completion import Completer, Completion -from ptpython.completer import PythonCompleter import re import weakref @@ -47,9 +46,75 @@ def get_completions(self, document, complete_event): # Select completer. if location.endswith('.py') and editor.enable_jedi: - completer = PythonCompleter(lambda: globals(), lambda: {}) + completer = _PythonCompleter(location) else: completer = DocumentWordsCompleter() # Call completer. return completer.get_completions(document, complete_event) + + +class _PythonCompleter(Completer): + """ + Wrapper around the Jedi completion engine. + """ + def __init__(self, location): + self.location = location + + def get_completions(self, document, complete_event): + script = self._get_jedi_script_from_document(document) + if script: + try: + completions = script.completions() + except TypeError: + # Issue #9: bad syntax causes completions() to fail in jedi. + # https://github.com/jonathanslenders/python-prompt-toolkit/issues/9 + pass + except UnicodeDecodeError: + # Issue #43: UnicodeDecodeError on OpenBSD + # https://github.com/jonathanslenders/python-prompt-toolkit/issues/43 + pass + except AttributeError: + # Jedi issue #513: https://github.com/davidhalter/jedi/issues/513 + pass + except ValueError: + # Jedi issue: "ValueError: invalid \x escape" + pass + except KeyError: + # Jedi issue: "KeyError: u'a_lambda'." + # https://github.com/jonathanslenders/ptpython/issues/89 + pass + except IOError: + # Jedi issue: "IOError: No such file or directory." + # https://github.com/jonathanslenders/ptpython/issues/71 + pass + else: + for c in completions: + yield Completion(c.name_with_symbols, len(c.complete) - len(c.name_with_symbols), + display=c.name_with_symbols) + + def _get_jedi_script_from_document(self, document): + import jedi # We keep this import in-line, to improve start-up time. + # Importing Jedi is 'slow'. + + try: + return jedi.Script( + document.text, + column=document.cursor_position_col, + line=document.cursor_position_row + 1, + path=self.location) + except ValueError: + # Invalid cursor position. + # ValueError('`column` parameter is not in a valid range.') + return None + except AttributeError: + # Workaround for #65: https://github.com/jonathanslenders/python-prompt-toolkit/issues/65 + # See also: https://github.com/davidhalter/jedi/issues/508 + return None + except IndexError: + # Workaround Jedi issue #514: for https://github.com/davidhalter/jedi/issues/514 + return None + except KeyError: + # Workaroud for a crash when the input is "u'", the start of a unicode string. + return None + diff --git a/setup.py b/setup.py index 010299e..2b3e9b7 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,6 @@ packages=find_packages('.'), install_requires = [ 'prompt_toolkit>=1.0.4,<1.1.0', - 'ptpython>=0.34', # For the Python completion (with Jedi.) 'pyflakes', # For Python error reporting. 'pygments', # For the syntax highlighting. 'docopt', # For command line arguments. From 0c36a2766c68a6da75943be77da472e3434fb104 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 9 Oct 2016 21:52:15 +0200 Subject: [PATCH 12/59] Added support for inserting before/after visual block. (Display multiple cursors and 'INSERT' in status bar.) --- pyvim/layout.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyvim/layout.py b/pyvim/layout.py index 6c9a51d..bbf05c2 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -11,7 +11,7 @@ from prompt_toolkit.layout.dimension import LayoutDimension from prompt_toolkit.layout.margins import ConditionalMargin, NumberredMargin from prompt_toolkit.layout.menus import CompletionsMenu -from prompt_toolkit.layout.processors import Processor, ConditionalProcessor, BeforeInput, ShowTrailingWhiteSpaceProcessor, Transformation, HighlightSelectionProcessor, HighlightSearchProcessor, HighlightMatchingBracketProcessor, TabsProcessor +from prompt_toolkit.layout.processors import Processor, ConditionalProcessor, BeforeInput, ShowTrailingWhiteSpaceProcessor, Transformation, HighlightSelectionProcessor, HighlightSearchProcessor, HighlightMatchingBracketProcessor, TabsProcessor, DisplayMultipleCursors from prompt_toolkit.layout.screen import Char from prompt_toolkit.layout.toolbars import TokenListToolbar, SystemToolbar, SearchToolbar, ValidationToolbar, CompletionsToolbar from prompt_toolkit.layout.utils import explode_tokens @@ -291,7 +291,7 @@ class WindowStatusBar(TokenListToolbar): """ def __init__(self, editor, editor_buffer, manager): def get_tokens(cli): - insert_mode = cli.vi_state.input_mode == InputMode.INSERT + insert_mode = cli.vi_state.input_mode in (InputMode.INSERT, InputMode.INSERT_MULTIPLE) replace_mode = cli.vi_state.input_mode == InputMode.REPLACE sel = cli.buffers[editor_buffer.buffer_name].selection_state visual_line = sel is not None and sel.type == SelectionType.LINES @@ -563,6 +563,7 @@ def preview_search(cli): HighlightSearchProcessor(preview_search=preview_search), Condition(lambda cli: self.editor.highlight_search)), HighlightMatchingBracketProcessor(), + DisplayMultipleCursors(buffer_name), ] return BufferControl(lexer=DocumentLexer(editor_buffer), From 6860c4139150896960a8282ba43cb7c2196407e0 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 16 Oct 2016 21:44:29 +0200 Subject: [PATCH 13/59] Release 0.0.20 --- CHANGELOG | 10 ++++++++++ pyvim/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b454f99..5cabad7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,16 @@ CHANGELOG ========= +0.0.20: 2016-10-16 +------------------- + +- Added support for inserting before/after visual block. +- Better Jedi integration for completion of Python files. +- Don't depend on ptpython code anymore. + +Upgrade to prompt_toolkit==1.0.8 + + 0.0.19: 2016-08-04 ------------------ diff --git a/pyvim/__init__.py b/pyvim/__init__.py index d7ac0c3..e17037f 100755 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '0.0.19' +__version__ = '0.0.20' diff --git a/setup.py b/setup.py index 2b3e9b7..c33a85c 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ long_description=long_description, packages=find_packages('.'), install_requires = [ - 'prompt_toolkit>=1.0.4,<1.1.0', + 'prompt_toolkit>=1.0.8,<1.1.0', 'pyflakes', # For Python error reporting. 'pygments', # For the syntax highlighting. 'docopt', # For command line arguments. From 07ae95d97b6c4295c13040ffab0614fe645e6b63 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sat, 17 Dec 2016 22:54:35 +0100 Subject: [PATCH 14/59] Use 'prompt_toolkit.key_binding.defaults.load_key_bindings' instead of KeyBindingManager. --- pyvim/editor.py | 11 +++++------ pyvim/key_bindings.py | 11 ++++------- pyvim/layout.py | 7 +++---- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/pyvim/editor.py b/pyvim/editor.py index f26d2ae..3c3c606 100644 --- a/pyvim/editor.py +++ b/pyvim/editor.py @@ -89,12 +89,11 @@ def __init__(self, config_directory='~/.pyvim'): # Create eventloop. self.eventloop = create_eventloop() - # Create key bindings manager - self.key_bindings_manager = create_key_bindings(self) + # Create key bindings registry. + self.key_bindings_registry = create_key_bindings(self) # Create layout and CommandLineInterface instance. - self.editor_layout = EditorLayout( - self, self.key_bindings_manager, self.window_arrangement) + self.editor_layout = EditorLayout(self, self.window_arrangement) self.application = self._create_application() self.cli = CommandLineInterface( @@ -170,7 +169,7 @@ def handle_action(cli, buffer): application = Application( editing_mode=EditingMode.VI, layout=self.editor_layout.layout, - key_bindings_registry=self.key_bindings_manager.registry, + key_bindings_registry=self.key_bindings_registry, get_title=lambda: get_terminal_title(self), buffers={ COMMAND_BUFFER: command_buffer, @@ -213,7 +212,7 @@ def add_key_binding(self): (Mostly useful for a pyvimrc file, that receives this Editor instance as input.) """ - return self.key_bindings_manager.registry.add_binding + return self.key_bindings_registry.add_binding def show_message(self, message): """ diff --git a/pyvim/key_bindings.py b/pyvim/key_bindings.py index cc45d58..2469108 100644 --- a/pyvim/key_bindings.py +++ b/pyvim/key_bindings.py @@ -1,8 +1,7 @@ from __future__ import unicode_literals from prompt_toolkit.filters import Condition, HasFocus, Filter, ViInsertMode, ViNavigationMode -from prompt_toolkit.key_binding.bindings.utils import create_handle_decorator -from prompt_toolkit.key_binding.manager import KeyBindingManager +from prompt_toolkit.key_binding.defaults import load_key_bindings from prompt_toolkit.keys import Keys from prompt_toolkit.layout.utils import find_window_for_buffer_name @@ -28,8 +27,7 @@ def create_key_bindings(editor): the ones which are specific for the editor. """ # Create new Key binding manager. - manager = KeyBindingManager( - enable_vi_mode=True, + registry = load_key_bindings( enable_search=True, enable_extra_page_navigation=True, enable_system_bindings=True) @@ -40,8 +38,7 @@ def create_key_bindings(editor): in_insert_mode = ViInsertMode() & vi_buffer_focussed in_navigation_mode = ViNavigationMode() & vi_buffer_focussed - # Decorator. - handle = create_handle_decorator(manager.registry) + handle = registry.add_binding @handle(Keys.ControlT) def _(event): @@ -152,7 +149,7 @@ def goto_line_beginning(event): def show_help(event): editor.show_help() - return manager + return registry class WhitespaceBeforeCursorOnLine(Filter): diff --git a/pyvim/layout.py b/pyvim/layout.py index bbf05c2..3b30bb1 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -289,7 +289,7 @@ class WindowStatusBar(TokenListToolbar): """ The status bar, which is shown below each window in a tab page. """ - def __init__(self, editor, editor_buffer, manager): + def __init__(self, editor, editor_buffer): def get_tokens(cli): insert_mode = cli.vi_state.input_mode in (InputMode.INSERT, InputMode.INSERT_MULTIPLE) replace_mode = cli.vi_state.input_mode == InputMode.REPLACE @@ -402,9 +402,8 @@ class EditorLayout(object): """ The main layout class. """ - def __init__(self, editor, manager, window_arrangement): + def __init__(self, editor, window_arrangement): self.editor = editor # Back reference to editor. - self.manager = manager self.window_arrangement = window_arrangement # Mapping from (`window_arrangement.Window`, `EditorBuffer`) to a frame @@ -525,7 +524,7 @@ def wrap_lines(cli): return HSplit([ window, VSplit([ - WindowStatusBar(self.editor, editor_buffer, self.manager), + WindowStatusBar(self.editor, editor_buffer), WindowStatusBarRuler(self.editor, window, editor_buffer.buffer_name), ]), ]) From 78acef7db0db87b83f98d1660613e8fd5c4f9d89 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Tue, 8 Aug 2017 22:35:31 +0200 Subject: [PATCH 15/59] Release Pyvim 0.0.21 --- CHANGELOG | 7 +++++++ pyvim/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5cabad7..5e94760 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ CHANGELOG ========= +0.0.21: 2017-8-8 +---------------- + +- Use load_key_bindings instead of KeyBindingManager (fixes compatibility with + latest prompt_toolkit 1.0) + + 0.0.20: 2016-10-16 ------------------- diff --git a/pyvim/__init__.py b/pyvim/__init__.py index e17037f..0713f60 100755 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '0.0.20' +__version__ = '0.0.21' From 7f3ad3ee3529e0f72cf3c065e92d07adad7ca3f7 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Tue, 8 Aug 2017 22:40:27 +0200 Subject: [PATCH 16/59] Removed pypy3 from .travis.yml. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c6f4220..3e76eb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ env: - TOXENV=py34 - TOXENV=py35 - TOXENV=pypy - - TOXENV=pypy3 install: - travis_retry pip install tox pytest script: From 8c700f5775fd7229ae11fb1ab2ee319f2c8cccf6 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sat, 13 Jan 2018 21:28:34 +0100 Subject: [PATCH 17/59] Upgrade to prompt_toolkit 2.0 --- pyvim/__init__.py | 0 pyvim/commands/commands.py | 37 +++- pyvim/commands/completer.py | 3 +- pyvim/commands/handler.py | 4 +- pyvim/commands/lexer.py | 7 +- pyvim/editor.py | 176 ++++++------------- pyvim/editor_buffer.py | 56 ++++-- pyvim/enums.py | 3 - pyvim/key_bindings.py | 83 +++++---- pyvim/layout.py | 335 ++++++++++++++++++++---------------- pyvim/lexer.py | 8 +- pyvim/reporting.py | 14 +- pyvim/style.py | 120 +++++++++---- pyvim/welcome_message.py | 58 +++---- pyvim/window_arrangement.py | 27 +-- setup.py | 2 +- 16 files changed, 486 insertions(+), 447 deletions(-) mode change 100755 => 100644 pyvim/__init__.py diff --git a/pyvim/__init__.py b/pyvim/__init__.py old mode 100755 new mode 100644 diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index 4e29802..9f03b11 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals, print_function +from prompt_toolkit.application import run_in_terminal import os import six -import sys __all__ = ( 'has_command_handler', @@ -221,7 +221,7 @@ def handler(): print(' %3i %-2s %-20s line %i' % ( info.index, char, eb.location, (eb.buffer.document.cursor_position_row + 1))) six.moves.input('\nPress ENTER to continue...') - editor.cli.run_in_terminal(handler) + run_in_terminal(handler) @_cmd('b') @@ -293,7 +293,7 @@ def quit(editor, all_=False, force=False): editor.show_message('%i more files to edit' % (len(ebs) - 1)) else: - editor.cli.set_return_value('') + editor.application.exit() @cmd('qa', accepts_force=True) @@ -327,7 +327,7 @@ def write_and_quit(editor, location, force=False): Write file and quit. """ write(editor, location, force=force) - editor.cli.set_return_value('') + editor.application.exit() @cmd('cq') @@ -338,7 +338,7 @@ def quit_nonzero(editor): # Note: the try/finally in `prompt_toolkit.Interface.read_input` # will ensure that the render output is reset, leaving the alternate # screen before quiting. - sys.exit(1) + editor.application.exit() @cmd('wqa') @@ -400,6 +400,22 @@ def tab_previous(editor): editor.window_arrangement.go_to_previous_tab() +@cmd('pwd') +def pwd(editor): + " Print working directory. " + directory = os.getcwd() + editor.show_message('{}'.format(directory)) + + +@location_cmd('cd', accepts_force=False) +def pwd(editor, location): + " Change working directory. " + try: + os.chdir(location) + except OSError as e: + editor.show_message('{}'.format(e)) + + @_cmd('colorscheme') @_cmd('colo') def color_scheme(editor, variables): @@ -624,14 +640,14 @@ def disable_mouse(editor): @set_cmd('top') def enable_tildeop(editor): " Enable tilde operator. " - editor.cli.vi_state.tilde_operator = True + editor.application.vi_state.tilde_operator = True @set_cmd('notildeop') @set_cmd('notop') def disable_tildeop(editor): " Disable tilde operator. " - editor.cli.vi_state.tilde_operator = False + editor.application.vi_state.tilde_operator = False @set_cmd('cursorline') @@ -663,9 +679,12 @@ def disable_cursorcolumn(editor): @set_cmd('cc', accepts_value=True) def set_scroll_offset(editor, value): try: - value = [int(val) for val in value.split(',')] + if value: + numbers = [int(val) for val in value.split(',')] + else: + numbers = [] except ValueError: editor.show_message( 'Invalid value. Expecting comma separated list of integers') else: - editor.colorcolumn = value + editor.colorcolumn = numbers diff --git a/pyvim/commands/completer.py b/pyvim/commands/completer.py index 6e0c046..cb2bc34 100644 --- a/pyvim/commands/completer.py +++ b/pyvim/commands/completer.py @@ -1,8 +1,7 @@ from __future__ import unicode_literals from prompt_toolkit.completion import Completer, Completion -from prompt_toolkit.contrib.completers.base import WordCompleter -from prompt_toolkit.contrib.completers.filesystem import PathCompleter +from prompt_toolkit.completion import WordCompleter, PathCompleter from prompt_toolkit.contrib.completers.system import SystemCompleter from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter diff --git a/pyvim/commands/handler.py b/pyvim/commands/handler.py index 454663e..e926169 100644 --- a/pyvim/commands/handler.py +++ b/pyvim/commands/handler.py @@ -30,7 +30,7 @@ def handle_command(editor, input_string): elif shell_command is not None: # Handle shell commands. - editor.cli.run_system_command(shell_command) + editor.application.run_system_command(shell_command) elif has_command_handler(command): # Handle other 'normal' commands. @@ -49,5 +49,5 @@ def _go_to_line(editor, line): """ Move cursor to this line in the current buffer. """ - b = editor.cli.current_buffer + b = editor.application.current_buffer b.cursor_position = b.document.translate_row_col_to_index(max(0, int(line) - 1), 0) diff --git a/pyvim/commands/lexer.py b/pyvim/commands/lexer.py index 3716ace..2dd7807 100644 --- a/pyvim/commands/lexer.py +++ b/pyvim/commands/lexer.py @@ -1,9 +1,8 @@ from __future__ import unicode_literals from prompt_toolkit.contrib.regular_languages.lexer import GrammarLexer -from prompt_toolkit.layout.lexers import PygmentsLexer, SimpleLexer +from prompt_toolkit.lexers import PygmentsLexer, SimpleLexer -from pygments.token import Token from pygments.lexers import BashLexer from .grammar import COMMAND_GRAMMAR @@ -17,7 +16,7 @@ def create_command_lexer(): Lexer for highlighting of the command line. """ return GrammarLexer(COMMAND_GRAMMAR, lexers={ - 'command': SimpleLexer(Token.CommandLine.Command), - 'location': SimpleLexer(Token.CommandLine.Location), + 'command': SimpleLexer('class:commandline.command'), + 'location': SimpleLexer('class:commandline.location'), 'shell_command': PygmentsLexer(BashLexer), }) diff --git a/pyvim/editor.py b/pyvim/editor.py index 3c3c606..74d2759 100644 --- a/pyvim/editor.py +++ b/pyvim/editor.py @@ -10,24 +10,19 @@ from __future__ import unicode_literals from prompt_toolkit.application import Application -from prompt_toolkit.buffer import Buffer, AcceptAction -from prompt_toolkit.enums import SEARCH_BUFFER, EditingMode -from prompt_toolkit.filters import Always, Condition +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.enums import EditingMode +from prompt_toolkit.filters import Condition from prompt_toolkit.history import FileHistory -from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.key_binding.vi_state import InputMode -from prompt_toolkit.shortcuts import create_eventloop from prompt_toolkit.styles import DynamicStyle from .commands.completer import create_command_completer from .commands.handler import handle_command from .commands.preview import CommandPreviewer -from .editor_buffer import EditorBuffer -from .enums import COMMAND_BUFFER from .help import HELP_TEXT from .key_bindings import create_key_bindings from .layout import EditorLayout, get_terminal_title -from .reporting import report from .style import generate_built_in_styles, get_editor_style_by_name from .window_arrangement import WindowArrangement from .io import FileIO, DirectoryIO, HttpIO, GZipFileIO @@ -70,13 +65,12 @@ def __init__(self, config_directory='~/.pyvim'): if not os.path.exists(self.config_directory): os.mkdir(self.config_directory) - self._reporters_running_for_buffer_names = set() self.window_arrangement = WindowArrangement(self) self.message = None # Load styles. (Mapping from name to Style class.) self.styles = generate_built_in_styles() - self.current_style = get_editor_style_by_name('default') + self.current_style = get_editor_style_by_name('vim') # I/O backends. self.io_backends = [ @@ -86,24 +80,43 @@ def __init__(self, config_directory='~/.pyvim'): FileIO(), ] - # Create eventloop. - self.eventloop = create_eventloop() + # Create history and search buffers. + def handle_action(buff): + ' When enter is pressed in the Vi command line. ' + text = buff.text # Remember: leave_command_mode resets the buffer. + + # First leave command mode. We want to make sure that the working + # pane is focussed again before executing the command handlers. + self.leave_command_mode(append_to_history=True) + + # Execute command. + handle_command(self, text) + + commands_history = FileHistory(os.path.join(self.config_directory, 'commands_history')) + self.command_buffer = Buffer( + accept_handler=handle_action, + enable_history_search=True, + completer=create_command_completer(self), + history=commands_history, + multiline=False) + + search_buffer_history = FileHistory(os.path.join(self.config_directory, 'search_history')) + self.search_buffer = Buffer( + history=search_buffer_history, + enable_history_search=True, + multiline=False) # Create key bindings registry. - self.key_bindings_registry = create_key_bindings(self) + self.key_bindings = create_key_bindings(self) # Create layout and CommandLineInterface instance. self.editor_layout = EditorLayout(self, self.window_arrangement) self.application = self._create_application() - self.cli = CommandLineInterface( - eventloop=self.eventloop, - application=self.application) - # Hide message when a key is pressed. def key_pressed(_): self.message = None - self.cli.input_processor.beforeKeyPress += key_pressed + self.application.key_processor.before_key_press += key_pressed # Command line previewer. self.previewer = CommandPreviewer(self) @@ -139,56 +152,27 @@ def _create_application(self): """ Create CommandLineInterface instance. """ - # Create Vi command buffer. - def handle_action(cli, buffer): - ' When enter is pressed in the Vi command line. ' - text = buffer.text # Remember: leave_command_mode resets the buffer. - - # First leave command mode. We want to make sure that the working - # pane is focussed again before executing the command handlers. - self.leave_command_mode(append_to_history=True) - - # Execute command. - handle_command(self, text) - - # Create history and search buffers. - commands_history = FileHistory(os.path.join(self.config_directory, 'commands_history')) - command_buffer = Buffer(accept_action=AcceptAction(handler=handle_action), - enable_history_search=Always(), - completer=create_command_completer(self), - history=commands_history) - - search_buffer_history = FileHistory(os.path.join(self.config_directory, 'search_history')) - search_buffer = Buffer(history=search_buffer_history, - enable_history_search=Always(), - accept_action=AcceptAction.IGNORE) - - # Create app. - - # Create CLI. + # Create Application. application = Application( editing_mode=EditingMode.VI, layout=self.editor_layout.layout, - key_bindings_registry=self.key_bindings_registry, - get_title=lambda: get_terminal_title(self), - buffers={ - COMMAND_BUFFER: command_buffer, - SEARCH_BUFFER: search_buffer, - }, + key_bindings=self.key_bindings, +# get_title=lambda: get_terminal_title(self), style=DynamicStyle(lambda: self.current_style), - paste_mode=Condition(lambda cli: self.paste_mode), - ignore_case=Condition(lambda cli: self.ignore_case), - mouse_support=Condition(lambda cli: self.enable_mouse_support), - use_alternate_screen=True, - on_buffer_changed=self._current_buffer_changed) + paste_mode=Condition(lambda: self.paste_mode), +# ignore_case=Condition(lambda: self.ignore_case), # TODO + include_default_pygments_style=False, + mouse_support=Condition(lambda: self.enable_mouse_support), + full_screen=True, + enable_page_navigation_bindings=True) # Handle command line previews. # (e.g. when typing ':colorscheme blue', it should already show the # preview before pressing enter.) def preview(_): - if self.cli.current_buffer == command_buffer: - self.previewer.preview(command_buffer.text) - command_buffer.on_text_changed += preview + if self.application.layout.has_focus(self.command_buffer): + self.previewer.preview(self.command_buffer.text) + self.command_buffer.on_text_changed += preview return application @@ -198,7 +182,7 @@ def current_editor_buffer(self): Return the `EditorBuffer` that is currently active. """ # For each buffer name on the focus stack. - for current_buffer_name in self.cli.buffers.focus_stack: + for current_buffer_name in self.application.buffers.focus_stack: if current_buffer_name is not None: # Find/return the EditorBuffer with this name. for b in self.window_arrangement.editor_buffers: @@ -212,7 +196,7 @@ def add_key_binding(self): (Mostly useful for a pyvimrc file, that receives this Editor instance as input.) """ - return self.key_bindings_registry.add_binding + return self.key_bindings.add def show_message(self, message): """ @@ -240,59 +224,9 @@ def sync_with_prompt_toolkit(self): # Make sure that the focus stack of prompt-toolkit has the current # page. - self.cli.focus( - self.window_arrangement.active_editor_buffer.buffer_name) - - def _current_buffer_changed(self, cli): - """ - Current buffer changed. - """ - name = self.cli.current_buffer_name - eb = self.window_arrangement.get_editor_buffer_for_buffer_name(name) - - if eb is not None: - # Run reporter. - self.run_reporter_for_editor_buffer(eb) - - def run_reporter_for_editor_buffer(self, editor_buffer): - """ - Run reporter on input. (Asynchronously.) - """ - assert isinstance(editor_buffer, EditorBuffer) - eb = editor_buffer - name = eb.buffer_name - - if name not in self._reporters_running_for_buffer_names: - text = eb.buffer.text - self._reporters_running_for_buffer_names.add(name) - eb.report_errors = [] - - # Don't run reporter when we don't have a location. (We need to - # know the filetype, actually.) - if eb.location is None: - return - - # Better not to access the document in an executor. - document = eb.buffer.document - - def in_executor(): - # Call reporter - report_errors = report(eb.location, document) - - def ready(): - self._reporters_running_for_buffer_names.remove(name) - - # If the text has not been changed yet in the meantime, set - # reporter errors. (We were running in another thread.) - if text == eb.buffer.text: - eb.report_errors = report_errors - self.cli.invalidate() - else: - # Restart reporter when the text was changed. - self._current_buffer_changed(self.cli) - - self.cli.eventloop.call_from_executor(ready) - self.cli.eventloop.run_in_executor(in_executor) + window = self.window_arrangement.active_pt_window + if window: + self.application.layout.focus(window) def show_help(self): """ @@ -311,17 +245,17 @@ def run(self): def pre_run(): # Start in navigation mode. - self.cli.vi_state.input_mode = InputMode.NAVIGATION + self.application.vi_state.input_mode = InputMode.NAVIGATION # Run eventloop of prompt_toolkit. - self.cli.run(reset_current_buffer=False, pre_run=pre_run) + self.application.run(pre_run=pre_run) def enter_command_mode(self): """ Go into command mode. """ - self.cli.push_focus(COMMAND_BUFFER) - self.cli.vi_state.input_mode = InputMode.INSERT + self.application.layout.focus(self.command_buffer) + self.application.vi_state.input_mode = InputMode.INSERT self.previewer.save() @@ -331,7 +265,7 @@ def leave_command_mode(self, append_to_history=False): """ self.previewer.restore() - self.cli.pop_focus() - self.cli.vi_state.input_mode = InputMode.NAVIGATION + self.application.layout.focus_last() + self.application.vi_state.input_mode = InputMode.NAVIGATION - self.cli.buffers[COMMAND_BUFFER].reset(append_to_history=append_to_history) + self.command_buffer.reset(append_to_history=append_to_history) diff --git a/pyvim/editor_buffer.py b/pyvim/editor_buffer.py index 47e1952..80fb314 100644 --- a/pyvim/editor_buffer.py +++ b/pyvim/editor_buffer.py @@ -1,9 +1,11 @@ from __future__ import unicode_literals +from prompt_toolkit.application.current import get_app +from prompt_toolkit.buffer import Buffer from prompt_toolkit.document import Document +from prompt_toolkit.eventloop import call_from_executor, run_in_executor -from prompt_toolkit.buffer import Buffer, AcceptAction -from prompt_toolkit.filters import Always from pyvim.completion import DocumentCompleter +from pyvim.reporting import report from six import string_types @@ -22,14 +24,12 @@ class EditorBuffer(object): A 'prompt-toolkit' `Buffer` doesn't know anything about files, changes, etc... This wrapper contains the necessary data for the editor. """ - def __init__(self, editor, buffer_name, location=None, text=None): - assert isinstance(buffer_name, string_types) + def __init__(self, editor, location=None, text=None): assert location is None or isinstance(location, string_types) assert text is None or isinstance(text, string_types) assert not (location and text) self._editor_ref = weakref.ref(editor) - self.buffer_name = buffer_name self.location = location self.encoding = 'utf-8' @@ -46,13 +46,14 @@ def __init__(self, editor, buffer_name, location=None, text=None): # Create Buffer. self.buffer = Buffer( - is_multiline=Always(), + multiline=True, completer=DocumentCompleter(editor, self), - initial_document=Document(text, 0), - accept_action=AcceptAction.IGNORE) + document=Document(text, 0), + on_text_changed=lambda _: self.run_reporter()) # List of reporting errors. self.report_errors = [] + self._reporter_is_running = False @property def editor(self): @@ -149,6 +150,39 @@ def get_display_name(self, short=False): return self.location def __repr__(self): - return '%s(buffer_name=%r, buffer=%r)' % ( - self.__class__.__name__, - self.buffer_name, self.buffer) + return '%s(buffer=%r)' % (self.__class__.__name__, self.buffer) + + def run_reporter(self): + " Buffer text changed. " + if not self._reporter_is_running: + self._reporter_is_running = True + + text = self.buffer.text + self.report_errors = [] + + # Don't run reporter when we don't have a location. (We need to + # know the filetype, actually.) + if self.location is None: + return + + # Better not to access the document in an executor. + document = self.buffer.document + + def in_executor(): + # Call reporter + report_errors = report(self.location, document) + + def ready(): + self._reporter_is_running = False + + # If the text has not been changed yet in the meantime, set + # reporter errors. (We were running in another thread.) + if text == self.buffer.text: + self.report_errors = report_errors + get_app().invalidate() + else: + # Restart reporter when the text was changed. + self.run_reporter() + + call_from_executor(ready) + run_in_executor(in_executor) diff --git a/pyvim/enums.py b/pyvim/enums.py index 8f65f35..baffc48 100644 --- a/pyvim/enums.py +++ b/pyvim/enums.py @@ -1,4 +1 @@ from __future__ import unicode_literals - -# Vim command line buffer. -COMMAND_BUFFER = 'command-buffer' diff --git a/pyvim/key_bindings.py b/pyvim/key_bindings.py index 2469108..e6a3e55 100644 --- a/pyvim/key_bindings.py +++ b/pyvim/key_bindings.py @@ -1,11 +1,8 @@ from __future__ import unicode_literals -from prompt_toolkit.filters import Condition, HasFocus, Filter, ViInsertMode, ViNavigationMode -from prompt_toolkit.key_binding.defaults import load_key_bindings -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout.utils import find_window_for_buffer_name - -from .enums import COMMAND_BUFFER +from prompt_toolkit.application import get_app +from prompt_toolkit.filters import Condition, has_focus, vi_insert_mode, vi_navigation_mode +from prompt_toolkit.key_binding import KeyBindings __all__ = ( 'create_key_bindings', @@ -16,7 +13,7 @@ def _current_window_for_event(event): """ Return the `Window` for the currently focussed Buffer. """ - return find_window_for_buffer_name(event.cli.layout, event.cli.current_buffer_name) + return event.app.layout.current_window def create_key_bindings(editor): @@ -26,21 +23,20 @@ def create_key_bindings(editor): This starts with the key bindings, defined by `prompt-toolkit`, but adds the ones which are specific for the editor. """ - # Create new Key binding manager. - registry = load_key_bindings( - enable_search=True, - enable_extra_page_navigation=True, - enable_system_bindings=True) + kb = KeyBindings() # Filters. - vi_buffer_focussed = Condition(lambda cli: cli.current_buffer_name.startswith('buffer-')) - - in_insert_mode = ViInsertMode() & vi_buffer_focussed - in_navigation_mode = ViNavigationMode() & vi_buffer_focussed + @Condition + def vi_buffer_focussed(): + app = get_app() + if app.layout.has_focus(editor.search_buffer) or app.layout.has_focus(editor.command_buffer): + return False + return True - handle = registry.add_binding + in_insert_mode = vi_insert_mode & vi_buffer_focussed + in_navigation_mode = vi_navigation_mode & vi_buffer_focussed - @handle(Keys.ControlT) + @kb.add('c-t') def _(event): """ Override default behaviour of prompt-toolkit. @@ -49,12 +45,12 @@ def _(event): """ pass - @handle(Keys.ControlT, filter=in_insert_mode) + @kb.add('c-t', filter=in_insert_mode) def indent_line(event): """ Indent current line. """ - b = event.cli.current_buffer + b = event.application.current_buffer # Move to start of line. pos = b.document.get_start_of_line_position(after_whitespace=True) @@ -69,50 +65,49 @@ def indent_line(event): # Restore cursor. b.cursor_position -= pos - @handle(Keys.ControlR, filter=in_navigation_mode, save_before=(lambda e: False)) + @kb.add('c-r', filter=in_navigation_mode, save_before=(lambda e: False)) def redo(event): """ Redo. """ - event.cli.current_buffer.redo() + event.app.current_buffer.redo() - @handle(':', filter=in_navigation_mode) + @kb.add(':', filter=in_navigation_mode) def enter_command_mode(event): """ Entering command mode. """ editor.enter_command_mode() - @handle(Keys.Tab, filter=ViInsertMode() & - ~HasFocus(COMMAND_BUFFER) & WhitespaceBeforeCursorOnLine()) + @kb.add('tab', filter=vi_insert_mode & + ~has_focus(editor.command_buffer) & whitespace_before_cursor_on_line) def autocomplete_or_indent(event): """ When the 'tab' key is pressed with only whitespace character before the cursor, do autocompletion. Otherwise, insert indentation. """ - b = event.cli.current_buffer + b = event.app.current_buffer if editor.expand_tab: b.insert_text(' ') else: b.insert_text('\t') - @handle(Keys.Escape, filter=HasFocus(COMMAND_BUFFER)) - @handle(Keys.ControlC, filter=HasFocus(COMMAND_BUFFER)) - @handle( - Keys.Backspace, - filter=HasFocus(COMMAND_BUFFER) & Condition(lambda cli: cli.buffers[COMMAND_BUFFER].text == '')) + @kb.add('escape', filter=has_focus(editor.command_buffer)) + @kb.add('c-c', filter=has_focus(editor.command_buffer)) + @kb.add('backspace', + filter=has_focus(editor.command_buffer) & Condition(lambda: editor.command_buffer.text == '')) def leave_command_mode(event): """ Leaving command mode. """ editor.leave_command_mode() - @handle(Keys.ControlW, Keys.ControlW, filter=in_navigation_mode) + @kb.add('c-w', 'c-w', filter=in_navigation_mode) def focus_next_window(event): editor.window_arrangement.cycle_focus() editor.sync_with_prompt_toolkit() - @handle(Keys.ControlW, 'n', filter=in_navigation_mode) + @kb.add('c-w', 'n', filter=in_navigation_mode) def horizontal_split(event): """ Split horizontally. @@ -120,7 +115,7 @@ def horizontal_split(event): editor.window_arrangement.hsplit(None) editor.sync_with_prompt_toolkit() - @handle(Keys.ControlW, 'v', filter=in_navigation_mode) + @kb.add('c-w', 'v', filter=in_navigation_mode) def vertical_split(event): """ Split vertically. @@ -128,37 +123,37 @@ def vertical_split(event): editor.window_arrangement.vsplit(None) editor.sync_with_prompt_toolkit() - @handle('g', 't', filter=in_navigation_mode) + @kb.add('g', 't', filter=in_navigation_mode) def focus_next_tab(event): editor.window_arrangement.go_to_next_tab() editor.sync_with_prompt_toolkit() - @handle('g', 'T', filter=in_navigation_mode) + @kb.add('g', 'T', filter=in_navigation_mode) def focus_previous_tab(event): editor.window_arrangement.go_to_previous_tab() editor.sync_with_prompt_toolkit() - @handle(Keys.ControlJ, filter=in_navigation_mode) + @kb.add('enter', filter=in_navigation_mode) def goto_line_beginning(event): """ Enter in navigation mode should move to the start of the next line. """ b = event.current_buffer b.cursor_down(count=event.arg) b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) - @handle(Keys.F1) + @kb.add('f1') def show_help(event): editor.show_help() - return registry + return kb -class WhitespaceBeforeCursorOnLine(Filter): +@Condition +def whitespace_before_cursor_on_line(): """ Filter which evaluates to True when the characters before the cursor are whitespace, or we are at the start of te line. """ - def __call__(self, cli): - b = cli.current_buffer - before_cursor = b.document.current_line_before_cursor + b = get_app().current_buffer + before_cursor = b.document.current_line_before_cursor - return bool(not before_cursor or before_cursor[-1].isspace()) + return bool(not before_cursor or before_cursor[-1].isspace()) diff --git a/pyvim/layout.py b/pyvim/layout.py index 3b30bb1..2720b65 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -2,27 +2,23 @@ The actual layout for the renderer. """ from __future__ import unicode_literals -from prompt_toolkit.filters import HasFocus, HasSearch, Condition, HasArg, Always +from prompt_toolkit.application.current import get_app +from prompt_toolkit.filters import has_focus, is_searching, Condition, has_arg from prompt_toolkit.key_binding.vi_state import InputMode -from prompt_toolkit.layout import HSplit, VSplit, FloatContainer, Float -from prompt_toolkit.layout.containers import Window, ConditionalContainer, ScrollOffsets, ColorColumn -from prompt_toolkit.layout.controls import BufferControl, FillControl -from prompt_toolkit.layout.controls import TokenListControl -from prompt_toolkit.layout.dimension import LayoutDimension -from prompt_toolkit.layout.margins import ConditionalMargin, NumberredMargin +from prompt_toolkit.layout import HSplit, VSplit, FloatContainer, Float, Layout +from prompt_toolkit.layout.containers import Window, ConditionalContainer, ColorColumn, WindowAlign, ScrollOffsets +from prompt_toolkit.layout.controls import BufferControl +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.dimension import Dimension +from prompt_toolkit.layout.margins import ConditionalMargin, NumberedMargin from prompt_toolkit.layout.menus import CompletionsMenu -from prompt_toolkit.layout.processors import Processor, ConditionalProcessor, BeforeInput, ShowTrailingWhiteSpaceProcessor, Transformation, HighlightSelectionProcessor, HighlightSearchProcessor, HighlightMatchingBracketProcessor, TabsProcessor, DisplayMultipleCursors -from prompt_toolkit.layout.screen import Char -from prompt_toolkit.layout.toolbars import TokenListToolbar, SystemToolbar, SearchToolbar, ValidationToolbar, CompletionsToolbar -from prompt_toolkit.layout.utils import explode_tokens -from prompt_toolkit.mouse_events import MouseEventTypes -from prompt_toolkit.reactive import Integer +from prompt_toolkit.layout.processors import Processor, ConditionalProcessor, BeforeInput, ShowTrailingWhiteSpaceProcessor, Transformation, HighlightSelectionProcessor, HighlightSearchProcessor, HighlightIncrementalSearchProcessor, HighlightMatchingBracketProcessor, TabsProcessor, DisplayMultipleCursors +from prompt_toolkit.layout.utils import explode_text_fragments +from prompt_toolkit.mouse_events import MouseEventType from prompt_toolkit.selection import SelectionType - -from pygments.token import Token +from prompt_toolkit.widgets.toolbars import FormattedTextToolbar, SystemToolbar, SearchToolbar, ValidationToolbar, CompletionsToolbar from .commands.lexer import create_command_lexer -from .enums import COMMAND_BUFFER from .lexer import DocumentLexer from .welcome_message import WELCOME_MESSAGE_TOKENS, WELCOME_MESSAGE_HEIGHT, WELCOME_MESSAGE_WIDTH @@ -50,7 +46,7 @@ def _try_char(character, backup, encoding=sys.stdout.encoding): TABSTOP_DOT = _try_char('\u2508', '.') -class TabsControl(TokenListControl): +class TabsControl(FormattedTextControl): """ Displays the tabs at the top of the screen, when there is more than one open tab. @@ -61,15 +57,15 @@ def location_for_tab(tab): def create_tab_handler(index): " Return a mouse handler for this tab. Select the tab on click. " - def handler(cli, mouse_event): - if mouse_event.event_type == MouseEventTypes.MOUSE_DOWN: + def handler(app, mouse_event): + if mouse_event.event_type == MouseEventType.MOUSE_DOWN: editor.window_arrangement.active_tab_index = index editor.sync_with_prompt_toolkit() else: return NotImplemented return handler - def get_tokens(cli): + def get_tokens(): selected_tab_index = editor.window_arrangement.active_tab_index result = [] @@ -83,36 +79,36 @@ def get_tokens(cli): handler = create_tab_handler(i) if i == selected_tab_index: - append((Token.TabBar.Tab.Active, ' %s ' % caption, handler)) + append(('class:tabbar.tab.active', ' %s ' % caption, handler)) else: - append((Token.TabBar.Tab, ' %s ' % caption, handler)) - append((Token.TabBar, ' ')) + append(('class:tabbar.tab', ' %s ' % caption, handler)) + append(('class:tabbar', ' ')) return result - super(TabsControl, self).__init__(get_tokens, Char(token=Token.TabBar)) + super(TabsControl, self).__init__(get_tokens, style='class:tabbar') class TabsToolbar(ConditionalContainer): def __init__(self, editor): super(TabsToolbar, self).__init__( - Window(TabsControl(editor), height=LayoutDimension.exact(1)), - filter=Condition(lambda cli: len(editor.window_arrangement.tab_pages) > 1)) + Window(TabsControl(editor), height=1), + filter=Condition(lambda: len(editor.window_arrangement.tab_pages) > 1)) class CommandLine(ConditionalContainer): """ The editor command line. (For at the bottom of the screen.) """ - def __init__(self): + def __init__(self, editor): super(CommandLine, self).__init__( Window( BufferControl( - buffer_name=COMMAND_BUFFER, - input_processors=[BeforeInput.static(':')], + buffer=editor.command_buffer, + input_processors=[BeforeInput(':')], lexer=create_command_lexer()), - height=LayoutDimension.exact(1)), - filter=HasFocus(COMMAND_BUFFER)) + height=1), + filter=has_focus(editor.command_buffer)) class WelcomeMessageWindow(ConditionalContainer): @@ -123,7 +119,7 @@ class WelcomeMessageWindow(ConditionalContainer): def __init__(self, editor): once_hidden = [False] # Nonlocal - def condition(cli): + def condition(): # Get editor buffers buffers = editor.window_arrangement.editor_buffers @@ -136,20 +132,26 @@ def condition(cli): return result super(WelcomeMessageWindow, self).__init__( - Window(TokenListControl(lambda cli: WELCOME_MESSAGE_TOKENS)), + Window( + FormattedTextControl(lambda: WELCOME_MESSAGE_TOKENS), + align=WindowAlign.CENTER, + style="class:welcome"), filter=Condition(condition)) -def _bufferlist_overlay_visible_condition(cli): +def _bufferlist_overlay_visible(editor): """ True when the buffer list overlay should be displayed. (This is when someone starts typing ':b' or ':buffer' in the command line.) """ - text = cli.buffers[COMMAND_BUFFER].text.lstrip() - return cli.current_buffer_name == COMMAND_BUFFER and ( - any(text.startswith(p) for p in ['b ', 'b! ', 'buffer', 'buffer!'])) + @Condition + def overlay_is_visible(): + app = get_app() -bufferlist_overlay_visible_filter = Condition(_bufferlist_overlay_visible_condition) + text = editor.command_buffer.text.lstrip() + return app.layout.has_focus(editor.command_buffer) and ( + any(text.startswith(p) for p in ['b ', 'b! ', 'buffer', 'buffer!'])) + return overlay_is_visible class BufferListOverlay(ConditionalContainer): @@ -158,8 +160,6 @@ class BufferListOverlay(ConditionalContainer): inside the vim command line. """ def __init__(self, editor): - token = Token.BufferList - def highlight_location(location, search_string, default_token): """ Return a tokenlist with the `search_string` highlighted. @@ -169,15 +169,19 @@ def highlight_location(location, search_string, default_token): # Replace token of matching positions. for m in re.finditer(re.escape(search_string), location): for i in range(m.start(), m.end()): - result[i] = (token.SearchMatch, result[i][1]) + result[i] = ('class:searchmatch', result[i][1]) + + if location == search_string: + result[0] = (result[0][0] + ' [SetCursorPosition]', result[0][1]) + return result - def get_tokens(cli): + def get_tokens(): wa = editor.window_arrangement buffer_infos = wa.list_open_buffers() # Filter infos according to typed text. - input_params = cli.buffers[COMMAND_BUFFER].text.lstrip().split(None, 1) + input_params = editor.command_buffer.text.lstrip().split(None, 1) search_string = input_params[1] if len(input_params) > 1 else '' if search_string: @@ -194,10 +198,10 @@ def matches(info): return True # When this entry is part of the current completions list. - b = cli.buffers[COMMAND_BUFFER] + b = editor.command_buffer if b.complete_state and any(info.editor_buffer.location in c.display - for c in b.complete_state.current_completions + for c in b.complete_state.completions if info.editor_buffer.location is not None): return True @@ -207,13 +211,13 @@ def matches(info): # Render output. if len(buffer_infos) == 0: - return [(token, ' No match found. ')] + return [('', ' No match found. ')] else: result = [] # Create title. - result.append((token, ' ')) - result.append((token.Title, 'Open buffers\n')) + result.append(('', ' ')) + result.append(('class:title', 'Open buffers\n')) # Get length of longest location max_location_len = max(len(info.editor_buffer.get_display_name()) for info in buffer_infos) @@ -224,10 +228,10 @@ def matches(info): char = '%' if info.is_active else ' ' char2 = 'a' if info.is_visible else ' ' char3 = ' + ' if info.editor_buffer.has_unsaved_changes else ' ' - t = token.Active if info.is_active else token + t = 'class:active' if info.is_active else '' result.extend([ - (token, ' '), + ('', ' '), (t, '%3i ' % info.index), (t, '%s' % char), (t, '%s ' % char2), @@ -236,39 +240,41 @@ def matches(info): result.extend(highlight_location(eb.get_display_name(), search_string, t)) result.extend([ (t, ' ' * (max_location_len - len(eb.get_display_name()))), - (t.Lineno, ' line %i' % (eb.buffer.document.cursor_position_row + 1)), + (t + ' class:lineno', ' line %i' % (eb.buffer.document.cursor_position_row + 1)), (t, ' \n') ]) return result super(BufferListOverlay, self).__init__( - Window(TokenListControl(get_tokens, default_char=Char(token=token))), - filter=bufferlist_overlay_visible_filter) + Window(FormattedTextControl(get_tokens), + style='class:bufferlist', + scroll_offsets=ScrollOffsets(top=1, bottom=1)), + filter=_bufferlist_overlay_visible(editor)) -class MessageToolbarBar(TokenListToolbar): +class MessageToolbarBar(ConditionalContainer): """ Pop-up (at the bottom) for showing error/status messages. """ def __init__(self, editor): - def get_tokens(cli): + def get_tokens(): if editor.message: - return [(Token.Message, editor.message)] + return [('class:message', editor.message)] else: return [] super(MessageToolbarBar, self).__init__( - get_tokens, - filter=Condition(lambda cli: editor.message is not None)) + FormattedTextToolbar(get_tokens), + filter=Condition(lambda: editor.message is not None)) -class ReportMessageToolbar(TokenListToolbar): +class ReportMessageToolbar(ConditionalContainer): """ Toolbar that shows the messages, given by the reporter. (It shows the error message, related to the current line.) """ def __init__(self, editor): - def get_tokens(cli): + def get_formatted_text(): eb = editor.window_arrangement.active_editor_buffer lineno = eb.buffer.document.cursor_position_row @@ -276,30 +282,32 @@ def get_tokens(cli): for e in errors: if e.lineno == lineno: - return e.message_token_list + return e.formatted_text return [] super(ReportMessageToolbar, self).__init__( - get_tokens, - filter=~HasFocus(COMMAND_BUFFER) & ~HasSearch() & ~HasFocus('system')) + FormattedTextToolbar(get_formatted_text), + filter=~has_focus(editor.command_buffer) & ~is_searching & ~has_focus('system')) -class WindowStatusBar(TokenListToolbar): +class WindowStatusBar(FormattedTextToolbar): """ The status bar, which is shown below each window in a tab page. """ def __init__(self, editor, editor_buffer): - def get_tokens(cli): - insert_mode = cli.vi_state.input_mode in (InputMode.INSERT, InputMode.INSERT_MULTIPLE) - replace_mode = cli.vi_state.input_mode == InputMode.REPLACE - sel = cli.buffers[editor_buffer.buffer_name].selection_state + def get_text(): + app = get_app() + + insert_mode = app.vi_state.input_mode in (InputMode.INSERT, InputMode.INSERT_MULTIPLE) + replace_mode = app.vi_state.input_mode == InputMode.REPLACE + sel = editor_buffer.buffer.selection_state visual_line = sel is not None and sel.type == SelectionType.LINES visual_block = sel is not None and sel.type == SelectionType.BLOCK visual_char = sel is not None and sel.type == SelectionType.CHARACTERS def mode(): - if cli.current_buffer_name == editor_buffer.buffer_name: + if get_app().layout.has_focus(editor_buffer.buffer): if insert_mode: if editor.paste_mode: return ' -- INSERT (paste)--' @@ -315,15 +323,24 @@ def mode(): return ' -- VISUAL --' return ' ' - return [ - (Token.Toolbar.Status, ' '), - (Token.Toolbar.Status, editor_buffer.location or ''), - (Token.Toolbar.Status, ' [New File]' if editor_buffer.is_new else ''), - (Token.Toolbar.Status, '*' if editor_buffer.has_unsaved_changes else ''), - (Token.Toolbar.Status, ' '), - (Token.Toolbar.Status, mode()), - ] - super(WindowStatusBar, self).__init__(get_tokens, default_char=Char(' ', Token.Toolbar.Status)) + def recording(): + if app.vi_state.recording_register: + return 'recording ' + else: + return '' + + return ''.join([ + ' ', + recording(), + (editor_buffer.location or ''), + (' [New File]' if editor_buffer.is_new else ''), + ('*' if editor_buffer.has_unsaved_changes else ''), + (' '), + mode(), + ]) + super(WindowStatusBar, self).__init__( + get_text, + style='class:toolbar.status') class WindowStatusBarRuler(ConditionalContainer): @@ -331,7 +348,7 @@ class WindowStatusBarRuler(ConditionalContainer): The right side of the Vim toolbar, showing the location of the cursor in the file, and the vectical scroll percentage. """ - def __init__(self, editor, buffer_window, buffer_name): + def __init__(self, editor, buffer_window, buffer): def get_scroll_text(): info = buffer_window.render_info @@ -348,23 +365,26 @@ def get_scroll_text(): return '' - def get_tokens(cli): - main_document = cli.buffers[buffer_name].document + def get_tokens(): + main_document = buffer.document return [ - (Token.Toolbar.Status.CursorPosition, '(%i,%i)' % (main_document.cursor_position_row + 1, - main_document.cursor_position_col + 1)), - (Token.Toolbar.Status, ' - '), - (Token.Toolbar.Status.Percentage, get_scroll_text()), - (Token.Toolbar.Status, ' '), + ('class:cursorposition', '(%i,%i)' % (main_document.cursor_position_row + 1, + main_document.cursor_position_col + 1)), + ('', ' - '), + ('class:percentage', get_scroll_text()), + ('', ' '), ] super(WindowStatusBarRuler, self).__init__( Window( - TokenListControl(get_tokens, default_char=Char(' ', Token.Toolbar.Status), align_right=True), - height=LayoutDimension.exact(1), - ), - filter=Condition(lambda cli: editor.show_ruler)) + FormattedTextControl(get_tokens), + char=' ', + align=WindowAlign.RIGHT, + style='class:toolbar.status', + height=1, + ), + filter=Condition(lambda: editor.show_ruler)) class SimpleArgToolbar(ConditionalContainer): @@ -372,15 +392,16 @@ class SimpleArgToolbar(ConditionalContainer): Simple control showing the Vi repeat arg. """ def __init__(self): - def get_tokens(cli): - if cli.input_processor.arg is not None: - return [(Token.Arg, ' %s ' % cli.input_processor.arg)] + def get_tokens(): + arg = get_app().key_processor.arg + if arg is not None: + return [('class:arg', ' %s ' % arg)] else: return [] super(SimpleArgToolbar, self).__init__( - Window(TokenListControl(get_tokens, align_right=True)), - filter=HasArg()), + Window(FormattedTextControl(get_tokens), align=WindowAlign.RIGHT), + filter=has_arg), class PyvimScrollOffsets(ScrollOffsets): @@ -422,13 +443,14 @@ def __init__(self, editor, window_arrangement): Float(xcursor=True, ycursor=True, content=CompletionsMenu(max_height=12, scroll_offset=2, - extra_filter=~HasFocus(COMMAND_BUFFER))), + extra_filter=~has_focus(editor.command_buffer))), Float(content=BufferListOverlay(editor), bottom=1, left=0), Float(bottom=1, left=0, right=0, height=1, - content=CompletionsToolbar( - extra_filter=HasFocus(COMMAND_BUFFER) & - ~bufferlist_overlay_visible_filter & - Condition(lambda cli: editor.show_wildmenu))), + content=ConditionalContainer( + CompletionsToolbar(), + filter=has_focus(editor.command_buffer) & + ~_bufferlist_overlay_visible(editor) & + Condition(lambda: editor.show_wildmenu))), Float(bottom=1, left=0, right=0, height=1, content=ValidationToolbar()), Float(bottom=1, left=0, right=0, height=1, @@ -439,25 +461,27 @@ def __init__(self, editor, window_arrangement): ] ) - self.layout = FloatContainer( + search_toolbar = SearchToolbar(vi_mode=True, search_buffer=editor.search_buffer) + self.search_control = search_toolbar.control + + self.layout = Layout(FloatContainer( content=HSplit([ TabsToolbar(editor), self._fc, - CommandLine(), + CommandLine(editor), ReportMessageToolbar(editor), SystemToolbar(), - SearchToolbar(vi_mode=True), + search_toolbar, ]), floats=[ Float(right=0, height=1, bottom=0, width=5, content=SimpleArgToolbar()), ] - ) + )) - def get_vertical_border_char(self, cli): + def get_vertical_border_char(self): " Return the character to be used for the vertical border. " - return Char(char=_try_char('\u2502', '|', cli.output.encoding()), - token=Token.FrameBorder) + return _try_char('\u2502', '|', get_app().output.encoding()) def update(self): """ @@ -474,20 +498,20 @@ def create_layout_from_node(node): key = (node, node.editor_buffer) frame = existing_frames.get(key) if frame is None: - frame = self._create_window_frame(node.editor_buffer) + frame, pt_window = self._create_window_frame(node.editor_buffer) + + # Link layout Window to arrangement. + node.pt_window = pt_window + self._frames[key] = frame return frame elif isinstance(node, window_arrangement.VSplit): - children = [] - for n in node: - children.append(create_layout_from_node(n)) - children.append( - Window(width=LayoutDimension.exact(1), - content=FillControl( - get_char=self.get_vertical_border_char))) - children.pop() - return VSplit(children) + return VSplit( + [create_layout_from_node(n) for n in node], + padding=1, + padding_char=self.get_vertical_border_char(), + padding_style='class:frameborder') if isinstance(node, window_arrangement.HSplit): return HSplit([create_layout_from_node(n) for n in node]) @@ -500,43 +524,43 @@ def _create_window_frame(self, editor_buffer): Create a Window for the buffer, with underneat a status bar. """ @Condition - def wrap_lines(cli): + def wrap_lines(): return self.editor.wrap_lines window = Window( self._create_buffer_control(editor_buffer), - allow_scroll_beyond_bottom=Always(), + allow_scroll_beyond_bottom=True, scroll_offsets=ScrollOffsets( left=0, right=0, - top=Integer.from_callable(lambda: self.editor.scroll_offset), - bottom=Integer.from_callable(lambda: self.editor.scroll_offset)), + top=(lambda: self.editor.scroll_offset), + bottom=(lambda: self.editor.scroll_offset)), wrap_lines=wrap_lines, left_margins=[ConditionalMargin( - margin=NumberredMargin( + margin=NumberedMargin( display_tildes=True, - relative=Condition(lambda cli: self.editor.relative_number)), - filter=Condition(lambda cli: self.editor.show_line_numbers))], - cursorline=Condition(lambda cli: self.editor.cursorline), - cursorcolumn=Condition(lambda cli: self.editor.cursorcolumn), - get_colorcolumns=( - lambda cli: [ColorColumn(pos) for pos in self.editor.colorcolumn])) + relative=Condition(lambda: self.editor.relative_number)), + filter=Condition(lambda: self.editor.show_line_numbers))], + cursorline=Condition(lambda: self.editor.cursorline), + cursorcolumn=Condition(lambda: self.editor.cursorcolumn), + colorcolumns=( + lambda: [ColorColumn(pos) for pos in self.editor.colorcolumn]), + ignore_content_width=True, + ignore_content_height=True) return HSplit([ window, VSplit([ WindowStatusBar(self.editor, editor_buffer), - WindowStatusBarRuler(self.editor, window, editor_buffer.buffer_name), - ]), - ]) + WindowStatusBarRuler(self.editor, window, editor_buffer.buffer), + ], width=Dimension()), # Ignore actual status bar width. + ]), window def _create_buffer_control(self, editor_buffer): """ Create a new BufferControl for a given location. """ - buffer_name = editor_buffer.buffer_name - @Condition - def preview_search(cli): + def preview_search(): return self.editor.incsearch input_processors = [ @@ -545,13 +569,13 @@ def preview_search(cli): # selected.) ConditionalProcessor( ShowTrailingWhiteSpaceProcessor(), - Condition(lambda cli: self.editor.display_unprintable_characters)), + Condition(lambda: self.editor.display_unprintable_characters)), # Replace tabs by spaces. TabsProcessor( - tabstop=Integer.from_callable(lambda: self.editor.tabstop), - get_char1=(lambda cli: '|' if self.editor.display_unprintable_characters else ' '), - get_char2=(lambda cli: _try_char('\u2508', '.', cli.output.encoding()) + tabstop=(lambda: self.editor.tabstop), + char1=(lambda: '|' if self.editor.display_unprintable_characters else ' '), + char2=(lambda: _try_char('\u2508', '.', get_app().output.encoding()) if self.editor.display_unprintable_characters else ' '), ), @@ -559,17 +583,22 @@ def preview_search(cli): ReportingProcessor(editor_buffer), HighlightSelectionProcessor(), ConditionalProcessor( - HighlightSearchProcessor(preview_search=preview_search), - Condition(lambda cli: self.editor.highlight_search)), + HighlightSearchProcessor(), + Condition(lambda: self.editor.highlight_search)), + ConditionalProcessor( + HighlightIncrementalSearchProcessor(), + Condition(lambda: self.editor.highlight_search) & preview_search), HighlightMatchingBracketProcessor(), - DisplayMultipleCursors(buffer_name), + DisplayMultipleCursors(), ] - return BufferControl(lexer=DocumentLexer(editor_buffer), - input_processors=input_processors, - buffer_name=buffer_name, - preview_search=preview_search, - focus_on_click=True) + return BufferControl( + lexer=DocumentLexer(editor_buffer), + input_processors=input_processors, + buffer=editor_buffer.buffer, + preview_search=preview_search, + search_buffer_control=self.search_control, + focus_on_click=True) class ReportingProcessor(Processor): @@ -579,16 +608,18 @@ class ReportingProcessor(Processor): def __init__(self, editor_buffer): self.editor_buffer = editor_buffer - def apply_transformation(self, cli, document, lineno, source_to_display, tokens): + def apply_transformation(self, transformation_input): + fragments = transformation_input.fragments + if self.editor_buffer.report_errors: for error in self.editor_buffer.report_errors: - if error.lineno == lineno: - tokens = explode_tokens(tokens) + if error.lineno == transformation_input.lineno: + fragments = explode_text_fragments(fragments) for i in range(error.start_column, error.end_column): - if i < len(tokens): - tokens[i] = (Token.FlakesError, tokens[i][1]) + if i < len(fragments): + fragments[i] = ('class:flakeserror', fragments[i][1]) - return Transformation(tokens) + return Transformation(fragments) diff --git a/pyvim/lexer.py b/pyvim/lexer.py index 3171d62..9d5d432 100644 --- a/pyvim/lexer.py +++ b/pyvim/lexer.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from prompt_toolkit.layout.lexers import Lexer, SimpleLexer, PygmentsLexer +from prompt_toolkit.lexers import Lexer, SimpleLexer, PygmentsLexer __all__ = ( 'DocumentLexer', @@ -14,13 +14,13 @@ class DocumentLexer(Lexer): def __init__(self, editor_buffer): self.editor_buffer = editor_buffer - def lex_document(self, cli, document): + def lex_document(self, document): """ Call the lexer and return a get_tokens_for_line function. """ location = self.editor_buffer.location if location: - return PygmentsLexer.from_filename(location, sync_from_start=False).lex_document(cli, document) + return PygmentsLexer.from_filename(location, sync_from_start=False).lex_document(document) - return SimpleLexer().lex_document(cli, document) + return SimpleLexer().lex_document(document) diff --git a/pyvim/reporting.py b/pyvim/reporting.py index 910945c..cb8dc5b 100644 --- a/pyvim/reporting.py +++ b/pyvim/reporting.py @@ -14,8 +14,6 @@ import string import six -from pygments.token import Token - __all__ = ( 'report', ) @@ -25,11 +23,11 @@ class ReporterError(object): """ Error found by a reporter. """ - def __init__(self, lineno, start_column, end_column, message_token_list): + def __init__(self, lineno, start_column, end_column, formatted_text): self.lineno = lineno # Zero based line number. self.start_column = start_column self.end_column = end_column - self.message_token_list = message_token_list + self.formatted_text = formatted_text def report(location, document): @@ -60,9 +58,9 @@ def report_pyflakes(document): def format_flake_message(message): return [ - (Token.FlakeMessage.Prefix, 'pyflakes:'), - (Token, ' '), - (Token.FlakeMessage, message.message % message.message_args) + ('class:flakemessage.prefix', 'pyflakes:'), + ('', ' '), + ('class:flakemessage', message.message % message.message_args) ] def message_to_reporter_error(message): @@ -75,7 +73,7 @@ def message_to_reporter_error(message): return ReporterError(lineno=message.lineno - 1, start_column=message.col, end_column=message.col + end_index - start_index, - message_token_list=format_flake_message(message)) + formatted_text=format_flake_message(message)) # Construct list of ReporterError instances. return [message_to_reporter_error(m) for m in reporter.messages] diff --git a/pyvim/style.py b/pyvim/style.py index 31bf781..348501a 100644 --- a/pyvim/style.py +++ b/pyvim/style.py @@ -2,10 +2,10 @@ The styles, for the colorschemes. """ from __future__ import unicode_literals -from prompt_toolkit.styles import DEFAULT_STYLE_EXTENSIONS, style_from_dict +from prompt_toolkit.styles import Style, merge_styles +from prompt_toolkit.styles.pygments import style_from_pygments_cls from pygments.styles import get_all_styles, get_style_by_name -from pygments.token import Token __all__ = ( 'generate_built_in_styles', @@ -19,14 +19,15 @@ def get_editor_style_by_name(name): This raises `pygments.util.ClassNotFound` when there is no style with this name. """ - style_cls = get_style_by_name(name) + if name == 'vim': + vim_style = Style.from_dict(default_vim_style) + else: + vim_style = style_from_pygments_cls(get_style_by_name(name)) - styles = {} - styles.update(style_cls.styles) - styles.update(DEFAULT_STYLE_EXTENSIONS) - styles.update(style_extensions) - - return style_from_dict(styles) + return merge_styles([ + vim_style, + Style.from_dict(style_extensions), + ]) def generate_built_in_styles(): @@ -38,52 +39,97 @@ def generate_built_in_styles(): style_extensions = { # Toolbar colors. - Token.Toolbar.Status: '#ffffff bg:#444444', - Token.Toolbar.Status.CursorPosition: '#bbffbb bg:#444444', - Token.Toolbar.Status.Percentage: '#ffbbbb bg:#444444', + 'toolbar.status': '#ffffff bg:#444444', + 'toolbar.status.cursorposition': '#bbffbb bg:#444444', + 'toolbar.status.percentage': '#ffbbbb bg:#444444', # Flakes color. - Token.FlakesError: 'bg:#ff4444 #ffffff', + 'flakeserror': 'bg:#ff4444 #ffffff', # Flake messages - Token.FlakeMessage.Prefix: 'bg:#ff8800 #ffffff', - Token.FlakeMessage: '#886600', + 'flakemessage.prefix': 'bg:#ff8800 #ffffff', + 'flakemessage': '#886600', # Highlighting for the text in the command bar. - Token.CommandLine.Command: 'bold', - Token.CommandLine.Location: 'bg:#bbbbff #000000', + 'commandline.command': 'bold', + 'commandline.location': 'bg:#bbbbff #000000', # Frame borders (for between vertical splits.) - Token.FrameBorder: 'bold', #bg:#88aa88 #ffffff', + 'frameborder': 'bold', #bg:#88aa88 #ffffff', # Messages - Token.Message: 'bg:#bbee88 #222222', + 'message': 'bg:#bbee88 #222222', # Welcome message - Token.Welcome.Title: 'underline', - Token.Welcome.Body: '', - Token.Welcome.Body.Key: '#0000ff', - Token.Welcome.PythonVersion: 'bg:#888888 #ffffff', + 'welcome title': 'underline', + 'welcome version': '#8800ff', + 'welcome key': '#0000ff', + 'welcome pythonversion': 'bg:#888888 #ffffff', # Tabs - Token.TabBar: 'noinherit reverse', - Token.TabBar.Tab: 'underline', - Token.TabBar.Tab.Active: 'bold noinherit', + 'tabbar': 'noinherit reverse', + 'tabbar.tab': 'underline', + 'tabbar.tab.active': 'bold noinherit', # Arg count. - Token.Arg: 'bg:#cccc44 #000000', + 'arg': 'bg:#cccc44 #000000', # Buffer list - Token.BufferList: 'bg:#aaddaa #000000', - Token.BufferList.Title: 'underline', - Token.BufferList.Lineno: '#666666', - Token.BufferList.Active: 'bg:#ccffcc', - Token.BufferList.Active.Lineno: '#666666', - Token.BufferList.SearchMatch: 'bg:#eeeeaa', + 'bufferlist': 'bg:#aaddaa #000000', + 'bufferlist title': 'underline', + 'bufferlist lineno': '#666666', + 'bufferlist active': 'bg:#ccffcc', + 'bufferlist active.lineno': '#666666', + 'bufferlist searchmatch': 'bg:#eeeeaa', # Completions toolbar. - Token.Toolbar.Completions: 'bg:#aaddaa #000000', - Token.Toolbar.Completions.Arrow: 'bg:#aaddaa #000000 bold', - Token.Toolbar.Completions.Completion: 'bg:#aaddaa #000000', - Token.Toolbar.Completions.Completion.Current: 'bg:#444444 #ffffff', + 'completions-toolbar': 'bg:#aaddaa #000000', + 'completions-toolbar.arrow': 'bg:#aaddaa #000000 bold', + 'completions-toolbar completion': 'bg:#aaddaa #000000', + 'completions-toolbar current-completion': 'bg:#444444 #ffffff', +} + + +# Default 'vim' color scheme. Taken from the Pygments Vim colorscheme, but +# modified to use mainly ANSI colors. +default_vim_style = { + 'pygments': '', + 'pygments.whitespace': '', + 'pygments.comment': 'ansiblue', + 'pygments.comment.preproc': 'ansiyellow', + 'pygments.comment.special': 'bold', + + 'pygments.keyword': '#999900', + 'pygments.keyword.declaration': 'ansigreen', + 'pygments.keyword.namespace': 'ansimagenta', + 'pygments.keyword.pseudo': '', + 'pygments.keyword.type': 'ansigreen', + + 'pygments.operator': '', + 'pygments.operator.word': '', + + 'pygments.name': '', + 'pygments.name.class': 'ansicyan', + 'pygments.name.builtin': 'ansicyan', + 'pygments.name.exception': '', + 'pygments.name.variable': 'ansicyan', + 'pygments.name.function': 'ansicyan', + + 'pygments.literal': 'ansired', + 'pygments.string': 'ansired', + 'pygments.string.doc': '', + 'pygments.number': 'ansimagenta', + + 'pygments.generic.heading': 'bold ansiblue', + 'pygments.generic.subheading': 'bold ansimagenta', + 'pygments.generic.deleted': 'ansired', + 'pygments.generic.inserted': 'ansigreen', + 'pygments.generic.error': 'ansibrightred', + 'pygments.generic.emph': 'italic', + 'pygments.generic.strong': 'bold', + 'pygments.generic.prompt': 'bold ansiblue', + 'pygments.generic.output': 'ansigray', + 'pygments.generic.traceback': '#04d', + + 'pygments.error': 'border:ansired' } diff --git a/pyvim/welcome_message.py b/pyvim/welcome_message.py index ea90ff9..20c3916 100644 --- a/pyvim/welcome_message.py +++ b/pyvim/welcome_message.py @@ -2,9 +2,9 @@ The welcome message. This is displayed when the editor opens without any files. """ from __future__ import unicode_literals -from pygments.token import Token -from prompt_toolkit.layout.utils import token_list_len +from prompt_toolkit.formatted_text.utils import fragment_list_len +import prompt_toolkit import pyvim import platform import sys @@ -20,39 +20,25 @@ WELCOME_MESSAGE_WIDTH = 34 -def _t(token_list): - """ - Center tokens on this line. - """ - length = token_list_len(token_list) - - return [(Token.Welcome, ' ' * int((WELCOME_MESSAGE_WIDTH - length) / 2))] \ - + token_list + [(Token.Welcome, '\n')] - - -WELCOME_MESSAGE_TOKENS = ( - _t([(Token.Welcome.Title, 'PyVim - Pure Python Vi clone')]) + - _t([(Token.Welcome.Body, 'Still experimental')]) + - _t([(Token.Welcome.Body, '')]) + - _t([(Token.Welcome.Body, 'version %s' % pyvim_version)]) + - _t([(Token.Welcome.Body, 'by Jonathan Slenders')]) + - _t([(Token.Welcome.Body, '')]) + - _t([(Token.Welcome.Body, 'type :q'), - (Token.Welcome.Body.Key, ''), - (Token.Welcome.Body, ' to exit')]) + - _t([(Token.Welcome.Body, 'type :help'), - (Token.Welcome.Body.Key, ''), - (Token.Welcome.Body, ' or '), - (Token.Welcome.Body.Key, ''), - (Token.Welcome.Body, ' for help')]) + - _t([(Token.Welcome.Body, '')]) + - _t([(Token.Welcome.Body, 'All feedback is appreciated.')]) + - _t([(Token.Welcome.Body, '')]) + - _t([(Token.Welcome.Body, '')]) + - - _t([(Token.Welcome.PythonVersion, ' %s %i.%i.%i ' % ( +WELCOME_MESSAGE_TOKENS = [ + ('class:title', 'PyVim - Pure Python Vi clone\n'), + ('', 'Still experimental\n\n'), + ('', 'version '), ('class:version', pyvim_version), + ('', ', prompt_toolkit '), ('class:version', prompt_toolkit.__version__), + ('', '\n'), + ('', 'by Jonathan Slenders\n\n'), + ('', 'type :q'), + ('class:key', ''), + ('', ' to exit\n'), + ('', 'type :help'), + ('class:key', ''), + ('', ' or '), + ('class:key', ''), + ('', ' for help\n\n'), + ('', 'All feedback is appreciated.\n\n'), + ('class:pythonversion', ' %s %i.%i.%i ' % ( platform.python_implementation(), - version[0], version[1], version[2]))]) -) + version[0], version[1], version[2])), +] -WELCOME_MESSAGE_HEIGHT = ''.join(t[1] for t in WELCOME_MESSAGE_TOKENS).count('\n') +WELCOME_MESSAGE_HEIGHT = ''.join(t[1] for t in WELCOME_MESSAGE_TOKENS).count('\n') + 1 diff --git a/pyvim/window_arrangement.py b/pyvim/window_arrangement.py index 2315752..3984168 100644 --- a/pyvim/window_arrangement.py +++ b/pyvim/window_arrangement.py @@ -33,6 +33,9 @@ def __init__(self, editor_buffer): assert isinstance(editor_buffer, EditorBuffer) self.editor_buffer = editor_buffer + # The prompt_toolkit layout Window. + self.pt_window = None + def __repr__(self): return '%s(editor_buffer=%r)' % (self.__class__.__name__, self.editor_buffer) @@ -214,8 +217,6 @@ def __init__(self, editor): self.active_tab_index = None self.editor_buffers = [] # List of EditorBuffer - self._buffer_index = 0 # Index for generating buffer names. - @property def editor(self): """ The Editor instance. """ @@ -233,6 +234,14 @@ def active_editor_buffer(self): if self.active_tab and self.active_tab.active_window: return self.active_tab.active_window.editor_buffer + @property + def active_pt_window(self): + " The active prompt_toolkit layout Window. " + if self.active_tab: + w = self.active_tab.active_window + if w: + return w.pt_window + def get_editor_buffer_for_location(self, location): """ Return the `EditorBuffer` for this location. @@ -390,11 +399,8 @@ def _add_editor_buffer(self, editor_buffer, show_in_current_window=False): if show_in_current_window and self.active_tab: self.active_tab.show_editor_buffer(editor_buffer) - # Add buffer to CLI. - self.editor.cli.add_buffer(editor_buffer.buffer_name, editor_buffer.buffer) - # Start reporter. - self.editor.run_reporter_for_editor_buffer(editor_buffer) + editor_buffer.run_reporter() def _get_or_create_editor_buffer(self, location=None, text=None): """ @@ -406,14 +412,9 @@ def _get_or_create_editor_buffer(self, location=None, text=None): assert location is None or text is None # Don't pass two of them. assert location is None or isinstance(location, string_types) - def new_name(): - """ Generate name for new buffer. """ - self._buffer_index += 1 - return 'buffer-%i' % self._buffer_index - if location is None: # Create and add an empty EditorBuffer - eb = EditorBuffer(self.editor, new_name(), text=text) + eb = EditorBuffer(self.editor, text=text) self._add_editor_buffer(eb) return eb @@ -425,7 +426,7 @@ def new_name(): # Not found? Create one. if eb is None: # Create and add EditorBuffer - eb = EditorBuffer(self.editor, new_name(), location) + eb = EditorBuffer(self.editor, location) self._add_editor_buffer(eb) return eb diff --git a/setup.py b/setup.py index c33a85c..9a58616 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ long_description=long_description, packages=find_packages('.'), install_requires = [ - 'prompt_toolkit>=1.0.8,<1.1.0', + 'prompt_toolkit>=2.0.0,<2.1.0', 'pyflakes', # For Python error reporting. 'pygments', # For the syntax highlighting. 'docopt', # For command line arguments. From 6e6f81cf587d080638ddda259be315d5d0fa7de4 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sat, 2 Jun 2018 21:01:11 +0200 Subject: [PATCH 18/59] Release 2.0.21 - Prompt_toolkit 2.0. --- CHANGELOG | 10 ++++++++-- pyvim/__init__.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5e94760..4d69f52 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,14 @@ CHANGELOG ========= -0.0.21: 2017-8-8 ----------------- +2.0.1: 2018-06-02 +----------------- + +Upgrade to prompt_toolkit 2.0 + + +0.0.21: 2017-08-08 +------------------ - Use load_key_bindings instead of KeyBindingManager (fixes compatibility with latest prompt_toolkit 1.0) diff --git a/pyvim/__init__.py b/pyvim/__init__.py index 0713f60..7e1aa7d 100644 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '0.0.21' +__version__ = '2.0.21' From 4dfe9840f97f7197f36185cb7f87a80659c15a8f Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sat, 2 Jun 2018 21:31:44 +0200 Subject: [PATCH 19/59] Fixed unit tests. --- pyvim/editor.py | 11 ++++++++++- tests/conftest.py | 11 +++++++---- tests/test_window_arrangements.py | 5 ++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/pyvim/editor.py b/pyvim/editor.py index 74d2759..3dcdd8c 100644 --- a/pyvim/editor.py +++ b/pyvim/editor.py @@ -38,8 +38,15 @@ class Editor(object): """ The main class. Containing the whole editor. + + :param config_directory: Place where configuration is stored. + :param input: (Optionally) `prompt_toolkit.input.Input` object. + :param output: (Optionally) `prompt_toolkit.output.Output` object. """ - def __init__(self, config_directory='~/.pyvim'): + def __init__(self, config_directory='~/.pyvim', input=None, output=None): + self.input = input + self.output = output + # Vi options. self.show_line_numbers = True self.highlight_search = True @@ -154,6 +161,8 @@ def _create_application(self): """ # Create Application. application = Application( + input=self.input, + output=self.output, editing_mode=EditingMode.VI, layout=self.editor_layout.layout, key_bindings=self.key_bindings, diff --git a/tests/conftest.py b/tests/conftest.py index ee82160..4fda75b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,17 +3,20 @@ import pytest from prompt_toolkit.buffer import Buffer +from prompt_toolkit.output import DummyOutput +from prompt_toolkit.input import DummyInput +from pyvim.editor import Editor from pyvim.window_arrangement import TabPage, EditorBuffer, Window @pytest.fixture -def prompt_buffer(): - return Buffer() +def editor(): + return Editor(output=DummyOutput(), input=DummyInput()) @pytest.fixture -def editor_buffer(prompt_buffer): - return EditorBuffer(prompt_buffer, 'b1') +def editor_buffer(editor): + return EditorBuffer(editor) @pytest.fixture diff --git a/tests/test_window_arrangements.py b/tests/test_window_arrangements.py index 8ede6a8..b3d02bd 100644 --- a/tests/test_window_arrangements.py +++ b/tests/test_window_arrangements.py @@ -9,10 +9,9 @@ def test_initial(window, tab_page): assert tab_page.root == [window] -def test_vsplit(tab_page): +def test_vsplit(editor, tab_page): # Create new buffer. - b = Buffer() - eb = EditorBuffer(b, 'b1') + eb = EditorBuffer(editor) # Insert in tab, by splitting. tab_page.vsplit(eb) From 3f4dcd4111519c1c5b25724b50c0fa2a84479a09 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sat, 2 Jun 2018 21:43:27 +0200 Subject: [PATCH 20/59] Removed tox. --- .travis.yml | 26 ++++++++++++++++---------- tox.ini | 8 -------- 2 files changed, 16 insertions(+), 18 deletions(-) delete mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml index 3e76eb7..3e8f487 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,20 @@ sudo: false language: python -python: '3.5' -env: - - TOXENV=py26 - - TOXENV=py27 - - TOXENV=py33 - - TOXENV=py34 - - TOXENV=py35 - - TOXENV=pypy + +matrix: + include: + - python: 3.6 + - python: 3.5 + - python: 2.7 + - python: pypy + - python: pypy3 + install: - - travis_retry pip install tox pytest + - pip install . + - pip install pytest + - pip list + script: - - tox + - echo "$TRAVIS_PYTHON_VERSION" + - cd tests + - py.test diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 92f7635..0000000 --- a/tox.ini +++ /dev/null @@ -1,8 +0,0 @@ -[tox] -envlist = py26,py27,py31,py32,py33,py34,py35,pypy,pypy3 - -[testenv] -deps=pytest - prompt-toolkit - ptpython - pyflakes From 811843f719775a73e9832a6b6b18fa5512e1d274 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 3 Jun 2018 16:07:21 +0200 Subject: [PATCH 21/59] Don't include default prompt_toolkit input processors in BufferControl. --- pyvim/layout.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyvim/layout.py b/pyvim/layout.py index 2720b65..27b948b 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -594,6 +594,7 @@ def preview_search(): return BufferControl( lexer=DocumentLexer(editor_buffer), + include_default_input_processors=False, input_processors=input_processors, buffer=editor_buffer.buffer, preview_search=preview_search, From 313d9b52628c27ade8f71c523dcd46bc6cb719fb Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Fri, 8 Jun 2018 20:07:24 +0200 Subject: [PATCH 22/59] Release 2.0.22 --- CHANGELOG | 8 ++++++++ pyvim/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4d69f52..daa0f01 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,11 +1,19 @@ CHANGELOG ========= +2.0.22: 2018-06-03 +----------------- + +- Small fix: don't include default input processors from prompt_toolkit. + + 2.0.1: 2018-06-02 ----------------- Upgrade to prompt_toolkit 2.0 +Edit: By accident, this was uploaded as 2.0.21. + 0.0.21: 2017-08-08 ------------------ diff --git a/pyvim/__init__.py b/pyvim/__init__.py index 7e1aa7d..8399830 100644 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '2.0.21' +__version__ = '2.0.22' From 369cdfe417f8a083b9de0a17bb6d03d3da785c7d Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Wed, 20 Jun 2018 21:36:48 +0200 Subject: [PATCH 23/59] Removed enter key binding, which is now in prompt_toolkit itself. --- pyvim/key_bindings.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pyvim/key_bindings.py b/pyvim/key_bindings.py index e6a3e55..30112ac 100644 --- a/pyvim/key_bindings.py +++ b/pyvim/key_bindings.py @@ -133,13 +133,6 @@ def focus_previous_tab(event): editor.window_arrangement.go_to_previous_tab() editor.sync_with_prompt_toolkit() - @kb.add('enter', filter=in_navigation_mode) - def goto_line_beginning(event): - """ Enter in navigation mode should move to the start of the next line. """ - b = event.current_buffer - b.cursor_down(count=event.arg) - b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) - @kb.add('f1') def show_help(event): editor.show_help() From a41f04cbf958f74f0ff462bd0ad7ae4d9215c1e2 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Thu, 30 Aug 2018 21:51:46 +0200 Subject: [PATCH 24/59] Show correct input mode when in 'temporary navigation mode'. --- pyvim/layout.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyvim/layout.py b/pyvim/layout.py index 27b948b..558a44d 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -302,6 +302,7 @@ def get_text(): insert_mode = app.vi_state.input_mode in (InputMode.INSERT, InputMode.INSERT_MULTIPLE) replace_mode = app.vi_state.input_mode == InputMode.REPLACE sel = editor_buffer.buffer.selection_state + temp_navigation = app.vi_state.temporary_navigation_mode visual_line = sel is not None and sel.type == SelectionType.LINES visual_block = sel is not None and sel.type == SelectionType.BLOCK visual_char = sel is not None and sel.type == SelectionType.CHARACTERS @@ -309,12 +310,17 @@ def get_text(): def mode(): if get_app().layout.has_focus(editor_buffer.buffer): if insert_mode: - if editor.paste_mode: + if temp_navigation: + return ' -- (insert) --' + elif editor.paste_mode: return ' -- INSERT (paste)--' else: return ' -- INSERT --' elif replace_mode: - return ' -- REPLACE --' + if temp_navigation: + return ' -- (replace) --' + else: + return ' -- REPLACE --' elif visual_block: return ' -- VISUAL BLOCK --' elif visual_line: From 8f3df9c328bbcb40d0349eba9e98a3f94133aab5 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 30 Sep 2018 20:43:06 +0200 Subject: [PATCH 25/59] Implemented breakindent option and added soft wrap mark. --- pyvim/commands/commands.py | 15 +++++++++++++++ pyvim/editor.py | 1 + pyvim/layout.py | 18 +++++++++++++++++- pyvim/style.py | 3 +++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index 9f03b11..8b0bef5 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -624,6 +624,21 @@ def disable_wrap(editor): " disable line wrapping. " editor.wrap_lines = False + +@set_cmd('breakindent') +@set_cmd('bri') +def enable_breakindent(editor): + " Enable the breakindent option. " + editor.break_indent = True + + +@set_cmd('nobreakindent') +@set_cmd('nobri') +def disable_breakindent(editor): + " Enable the breakindent option. " + editor.break_indent = False + + @set_cmd('mouse') def enable_mouse(editor): " Enable mouse . " diff --git a/pyvim/editor.py b/pyvim/editor.py index 3dcdd8c..0fd748f 100644 --- a/pyvim/editor.py +++ b/pyvim/editor.py @@ -63,6 +63,7 @@ def __init__(self, config_directory='~/.pyvim', input=None, output=None): self.scroll_offset = 0 # ':set scrolloff' self.relative_number = False # ':set relativenumber' self.wrap_lines = True # ':set wrap' + self.break_indent = False # ':set breakindent' self.cursorline = False # ':set cursorline' self.cursorcolumn = False # ':set cursorcolumn' self.colorcolumn = [] # ':set colorcolumn'. List of integers. diff --git a/pyvim/layout.py b/pyvim/layout.py index 558a44d..6c3a951 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -23,6 +23,7 @@ from .welcome_message import WELCOME_MESSAGE_TOKENS, WELCOME_MESSAGE_HEIGHT, WELCOME_MESSAGE_WIDTH import pyvim.window_arrangement as window_arrangement +from functools import partial import re import sys @@ -551,7 +552,8 @@ def wrap_lines(): colorcolumns=( lambda: [ColorColumn(pos) for pos in self.editor.colorcolumn]), ignore_content_width=True, - ignore_content_height=True) + ignore_content_height=True, + get_line_prefix=partial(self._get_line_prefix, editor_buffer.buffer)) return HSplit([ window, @@ -607,6 +609,20 @@ def preview_search(): search_buffer_control=self.search_control, focus_on_click=True) + def _get_line_prefix(self, buffer, line_number, wrap_count): + if wrap_count > 0: + result = [] + + # Add 'breakindent' prefix. + if self.editor.break_indent: + line = buffer.document.lines[line_number] + prefix = line[:len(line) - len(line.lstrip())] + result.append(('', prefix)) + + # Add softwrap mark. + result.append(('class:soft-wrap', '...')) + return result + return '' class ReportingProcessor(Processor): """ diff --git a/pyvim/style.py b/pyvim/style.py index 348501a..5d88cfa 100644 --- a/pyvim/style.py +++ b/pyvim/style.py @@ -87,6 +87,9 @@ def generate_built_in_styles(): 'completions-toolbar.arrow': 'bg:#aaddaa #000000 bold', 'completions-toolbar completion': 'bg:#aaddaa #000000', 'completions-toolbar current-completion': 'bg:#444444 #ffffff', + + # Soft wrap. + 'soft-wrap': '#888888', } From 289e1ec81483474955c5e3bf3282b55d2a858ad1 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 30 Sep 2018 20:53:26 +0200 Subject: [PATCH 26/59] Release 2.0.23 --- CHANGELOG | 6 ++++++ pyvim/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index daa0f01..b46b3b3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ CHANGELOG ========= +2.0.23: 2018-09-30 +------------------ + +- Implemented "breakindent" option. +- Implemented "temporary navigation mode". + 2.0.22: 2018-06-03 ----------------- diff --git a/pyvim/__init__.py b/pyvim/__init__.py index 8399830..fcc02ef 100644 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '2.0.22' +__version__ = '2.0.23' From e6bda376bf7a7d78a3001f8d96683667d29e6862 Mon Sep 17 00:00:00 2001 From: cosven Date: Sat, 29 Sep 2018 22:59:36 +0800 Subject: [PATCH 27/59] fix pyvimrc example no more `input_processor` module --- examples/config/pyvimrc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/config/pyvimrc b/examples/config/pyvimrc index 7702950..978d260 100644 --- a/examples/config/pyvimrc +++ b/examples/config/pyvimrc @@ -3,7 +3,7 @@ Pyvim configuration. Save to file to: ~/.pyvimrc """ from prompt_toolkit.filters import ViInsertMode -from prompt_toolkit.key_binding.input_processor import KeyPress +from prompt_toolkit.key_binding.key_processor import KeyPress from prompt_toolkit.keys import Keys from subprocess import call import six @@ -53,7 +53,7 @@ def configure(editor): (imap jj ) """ - event.cli.input_processor.feed(KeyPress(Keys.Escape)) + event.cli.key_processor.feed(KeyPress(Keys.Escape)) @editor.add_key_binding(Keys.F9) def save_and_execute_python_file(event): @@ -69,7 +69,7 @@ def configure(editor): return else: editor_buffer.write() - + # Now run the Python interpreter. But use # `CommandLineInterface.run_in_terminal` to go to the background and # not destroy the window layout. From a511949ff13bffcef64e5421e7a939e997efb1b9 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Sat, 12 Aug 2017 23:16:41 +0200 Subject: [PATCH 28/59] Implement file explorer. Thanks to David Brochart! Commit modified by Jonathan Slenders. --- pyvim/commands/commands.py | 1 + pyvim/editor.py | 13 ++++++------- pyvim/editor_buffer.py | 12 ++++++++++++ pyvim/io/backends.py | 5 +++++ pyvim/io/base.py | 6 ++++++ pyvim/key_bindings.py | 20 ++++++++++++++++++++ pyvim/window_arrangement.py | 1 + 7 files changed, 51 insertions(+), 7 deletions(-) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index 8b0bef5..def2a59 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -273,6 +273,7 @@ def buffer_edit(editor, location, force=False): else: eb.reload() else: + editor.file_explorer = '' editor.window_arrangement.open_buffer(location, show_in_current_window=True) diff --git a/pyvim/editor.py b/pyvim/editor.py index 0fd748f..075674a 100644 --- a/pyvim/editor.py +++ b/pyvim/editor.py @@ -191,13 +191,12 @@ def current_editor_buffer(self): """ Return the `EditorBuffer` that is currently active. """ - # For each buffer name on the focus stack. - for current_buffer_name in self.application.buffers.focus_stack: - if current_buffer_name is not None: - # Find/return the EditorBuffer with this name. - for b in self.window_arrangement.editor_buffers: - if b.buffer_name == current_buffer_name: - return b + current_buffer = self.application.current_buffer + + # Find/return the EditorBuffer with this name. + for b in self.window_arrangement.editor_buffers: + if b.buffer == current_buffer: + return b @property def add_key_binding(self): diff --git a/pyvim/editor_buffer.py b/pyvim/editor_buffer.py index 80fb314..77f96b3 100644 --- a/pyvim/editor_buffer.py +++ b/pyvim/editor_buffer.py @@ -36,6 +36,9 @@ def __init__(self, editor, location=None, text=None): #: is_new: True when this file does not yet exist in the storage. self.is_new = True + # Empty if not in file explorer mode, directory path otherwise. + self.isdir = False + # Read text. if location: text = self._read(location) @@ -67,6 +70,13 @@ def has_unsaved_changes(self): """ return self._file_content != self.buffer.text + @property + def in_file_explorer_mode(self): + """ + True when we are in file explorer mode (when this is a directory). + """ + return self.isdir + def _read(self, location): """ Read file I/O backend. @@ -75,6 +85,8 @@ def _read(self, location): if io.can_open_location(location): # Found an I/O backend. exists = io.exists(location) + self.isdir = io.isdir(location) + if exists in (True, NotImplemented): # File could exist. Read it. self.is_new = False diff --git a/pyvim/io/backends.py b/pyvim/io/backends.py index c121877..997ba1d 100644 --- a/pyvim/io/backends.py +++ b/pyvim/io/backends.py @@ -116,6 +116,8 @@ def read(self, directory): result.append('" Directory Listing\n') result.append('" %s\n' % os.path.abspath(directory)) result.append('" ==================================\n') + result.append('../\n') + result.append('./\n') for d in directories: result.append('%s/\n' % d) @@ -128,6 +130,9 @@ def read(self, directory): def write(self, location, text, encoding): raise NotImplementedError('Cannot write to directory.') + def isdir(self, location): + return True + class HttpIO(EditorIO): """ diff --git a/pyvim/io/base.py b/pyvim/io/base.py index 830a099..f1ca302 100644 --- a/pyvim/io/base.py +++ b/pyvim/io/base.py @@ -44,3 +44,9 @@ def write(self, location, data, encoding='utf-8'): Write file to storage. Can raise IOError. """ + + def isdir(self, location): + """ + Return whether this location is a directory. + """ + return False diff --git a/pyvim/key_bindings.py b/pyvim/key_bindings.py index 30112ac..54f40cb 100644 --- a/pyvim/key_bindings.py +++ b/pyvim/key_bindings.py @@ -4,6 +4,8 @@ from prompt_toolkit.filters import Condition, has_focus, vi_insert_mode, vi_navigation_mode from prompt_toolkit.key_binding import KeyBindings +import os + __all__ = ( 'create_key_bindings', ) @@ -137,6 +139,24 @@ def focus_previous_tab(event): def show_help(event): editor.show_help() + @Condition + def in_file_explorer_mode(): + return bool(editor.current_editor_buffer and + editor.current_editor_buffer.in_file_explorer_mode) + + @kb.add('enter', filter=in_file_explorer_mode) + def open_path(event): + """ + Open file/directory in file explorer mode. + """ + name_under_cursor = event.current_buffer.document.current_line + new_path = os.path.normpath(os.path.join( + editor.current_editor_buffer.location, name_under_cursor)) + + editor.window_arrangement.open_buffer( + new_path, show_in_current_window=True) + editor.sync_with_prompt_toolkit() + return kb diff --git a/pyvim/window_arrangement.py b/pyvim/window_arrangement.py index 3984168..c9755d7 100644 --- a/pyvim/window_arrangement.py +++ b/pyvim/window_arrangement.py @@ -432,6 +432,7 @@ def _get_or_create_editor_buffer(self, location=None, text=None): return eb else: # Found! Return it. + self.editor.file_explorer = eb.file_explorer return eb def open_buffer(self, location=None, show_in_current_window=False): From 691a108214c95d0cdc323369036609437433fd92 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Wed, 10 Oct 2018 23:06:53 +0200 Subject: [PATCH 29/59] Bug fix for previous commit. --- pyvim/window_arrangement.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyvim/window_arrangement.py b/pyvim/window_arrangement.py index c9755d7..3984168 100644 --- a/pyvim/window_arrangement.py +++ b/pyvim/window_arrangement.py @@ -432,7 +432,6 @@ def _get_or_create_editor_buffer(self, location=None, text=None): return eb else: # Found! Return it. - self.editor.file_explorer = eb.file_explorer return eb def open_buffer(self, location=None, show_in_current_window=False): From de56edd378b8881367984f5ccabf80110a5252ca Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Wed, 10 Oct 2018 23:07:08 +0200 Subject: [PATCH 30/59] Implement '-' key binding in file explorer mode. --- pyvim/io/backends.py | 1 + pyvim/key_bindings.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/pyvim/io/backends.py b/pyvim/io/backends.py index 997ba1d..b1a8f0a 100644 --- a/pyvim/io/backends.py +++ b/pyvim/io/backends.py @@ -115,6 +115,7 @@ def read(self, directory): result.append('" ==================================\n') result.append('" Directory Listing\n') result.append('" %s\n' % os.path.abspath(directory)) + result.append('" Quick help: -: go up dir\n') result.append('" ==================================\n') result.append('../\n') result.append('./\n') diff --git a/pyvim/key_bindings.py b/pyvim/key_bindings.py index 54f40cb..e3209ee 100644 --- a/pyvim/key_bindings.py +++ b/pyvim/key_bindings.py @@ -157,6 +157,15 @@ def open_path(event): new_path, show_in_current_window=True) editor.sync_with_prompt_toolkit() + @kb.add('-', filter=in_file_explorer_mode) + def to_parent_directory(event): + new_path = os.path.normpath(os.path.join( + editor.current_editor_buffer.location, '..')) + + editor.window_arrangement.open_buffer( + new_path, show_in_current_window=True) + editor.sync_with_prompt_toolkit() + return kb From ec0d10678dcbdb250e75c96d5e5d4b023764d7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20N=C4=9Bmec?= Date: Wed, 10 Oct 2018 23:10:05 +0200 Subject: [PATCH 31/59] Added another alternative to readme (#46) --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 8ea9ad1..dbf5852 100644 --- a/README.rst +++ b/README.rst @@ -201,6 +201,7 @@ Certainly have a look at the alternatives: - Kaa: https://github.com/kaaedit/kaa by @atsuoishimoto - Vai: https://github.com/stefanoborini/vai by @stefanoborini +- Vis: https://github.com/martanne/vis by @martanne Q & A: From dd2e2618a4a592d9bb6b144849a3b44bdf536d69 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Mon, 15 Oct 2018 21:08:25 +0200 Subject: [PATCH 32/59] Added lexer for directory listings. --- pyvim/lexer.py | 29 +++++++++++++++++++++++++++++ pyvim/style.py | 10 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/pyvim/lexer.py b/pyvim/lexer.py index 9d5d432..a750f06 100644 --- a/pyvim/lexer.py +++ b/pyvim/lexer.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals from prompt_toolkit.lexers import Lexer, SimpleLexer, PygmentsLexer +from pygments.lexer import RegexLexer +from pygments.token import Token __all__ = ( 'DocumentLexer', @@ -21,6 +23,33 @@ def lex_document(self, document): location = self.editor_buffer.location if location: + if self.editor_buffer.in_file_explorer_mode: + return PygmentsLexer(DirectoryListingLexer, sync_from_start=False).lex_document(document) + return PygmentsLexer.from_filename(location, sync_from_start=False).lex_document(document) return SimpleLexer().lex_document(document) + + +_DirectoryListing = Token.DirectoryListing + +class DirectoryListingLexer(RegexLexer): + """ + Highlighting of directory listings. + """ + name = 'directory-listing' + tokens = { + 'root': [ + (r'^".*', _DirectoryListing.Header), + + (r'^\.\./$', _DirectoryListing.ParentDirectory), + (r'^\./$', _DirectoryListing.CurrentDirectory), + + (r'^[^"].*/$', _DirectoryListing.Directory), + (r'^[^"].*\.(txt|rst|md)$', _DirectoryListing.Textfile), + (r'^[^"].*\.(py)$', _DirectoryListing.PythonFile), + + (r'^[^"].*\.(pyc|pyd)$', _DirectoryListing.Tempfile), + (r'^\..*$', _DirectoryListing.Dotfile), + ] + } diff --git a/pyvim/style.py b/pyvim/style.py index 5d88cfa..8013d75 100644 --- a/pyvim/style.py +++ b/pyvim/style.py @@ -90,6 +90,16 @@ def generate_built_in_styles(): # Soft wrap. 'soft-wrap': '#888888', + + # Directory listing style. + 'pygments.directorylisting.header': '#4444ff', + 'pygments.directorylisting.directory': '#ff4444 bold', + 'pygments.directorylisting.currentdirectory': '#888888', + 'pygments.directorylisting.parentdirectory': '#888888', + 'pygments.directorylisting.tempfile': '#888888', + 'pygments.directorylisting.dotfile': '#888888', + 'pygments.directorylisting.pythonfile': '#8800ff', + 'pygments.directorylisting.textfile': '#aaaa00', } From a49a9e6b4c786e816481a8d51031276ac9a26040 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Tue, 30 Oct 2018 22:22:36 +0100 Subject: [PATCH 33/59] Fixed width of welcome message (prompt_toolkit version was partly invisible). --- pyvim/welcome_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyvim/welcome_message.py b/pyvim/welcome_message.py index 20c3916..bd97315 100644 --- a/pyvim/welcome_message.py +++ b/pyvim/welcome_message.py @@ -17,7 +17,7 @@ 'WELCOME_MESSAGE_HEIGHT', ) -WELCOME_MESSAGE_WIDTH = 34 +WELCOME_MESSAGE_WIDTH = 36 WELCOME_MESSAGE_TOKENS = [ From 22b5a85e4d0ea36cfd24328c60b275dd2a0ec057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Mon, 7 Jan 2019 10:48:01 +0100 Subject: [PATCH 34/59] Fix ResourceWarning: unclosed file in setup.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mickaƫl Schoentgen --- setup.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 9a58616..3ea0f84 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,8 @@ from setuptools import setup, find_packages import pyvim -long_description = open( - os.path.join( - os.path.dirname(__file__), - 'README.rst' - ) -).read() +with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f: + long_description = f.read() setup( From fd030b3439649813696da8d8b0d4723b1c85edb3 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 27 Jan 2019 22:33:47 +0100 Subject: [PATCH 35/59] Fix for directory listing on Python 2. Pygments expects a string. --- pyvim/lexer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyvim/lexer.py b/pyvim/lexer.py index a750f06..8d6a1c1 100644 --- a/pyvim/lexer.py +++ b/pyvim/lexer.py @@ -39,7 +39,7 @@ class DirectoryListingLexer(RegexLexer): """ name = 'directory-listing' tokens = { - 'root': [ + str('root'): [ # Conversion to `str` because of Pygments on Python 2. (r'^".*', _DirectoryListing.Header), (r'^\.\./$', _DirectoryListing.ParentDirectory), From 5928b53b9d700863c1a06d2181a034a955f94594 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Sun, 27 Jan 2019 22:28:21 +0100 Subject: [PATCH 36/59] Release 2.0.24 --- CHANGELOG | 5 +++++ pyvim/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b46b3b3..de0fb28 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ CHANGELOG ========= +2.0.24: 2019-01-27 +------------------ + +- Improved the file explorer. + 2.0.23: 2018-09-30 ------------------ diff --git a/pyvim/__init__.py b/pyvim/__init__.py index fcc02ef..b498509 100644 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '2.0.23' +__version__ = '2.0.24' From b6e82d98dc2698b1f356ad99c89aec65ccf22bd0 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Fri, 17 May 2019 23:38:29 +0200 Subject: [PATCH 37/59] Support prompt_toolkit 3.0. --- pyvim/editor_buffer.py | 23 ++++++++++++++++++++--- setup.py | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/pyvim/editor_buffer.py b/pyvim/editor_buffer.py index 77f96b3..76f7d1e 100644 --- a/pyvim/editor_buffer.py +++ b/pyvim/editor_buffer.py @@ -2,7 +2,7 @@ from prompt_toolkit.application.current import get_app from prompt_toolkit.buffer import Buffer from prompt_toolkit.document import Document -from prompt_toolkit.eventloop import call_from_executor, run_in_executor +from prompt_toolkit import __version__ as ptk_version from pyvim.completion import DocumentCompleter from pyvim.reporting import report @@ -12,6 +12,13 @@ import os import weakref +PTK3 = ptk_version.startswith('3.') + +if PTK3: + from asyncio import get_event_loop +else: + from prompt_toolkit.eventloop import call_from_executor, run_in_executor + __all__ = ( 'EditorBuffer', ) @@ -180,6 +187,9 @@ def run_reporter(self): # Better not to access the document in an executor. document = self.buffer.document + if PTK3: + loop = get_event_loop() + def in_executor(): # Call reporter report_errors = report(self.location, document) @@ -196,5 +206,12 @@ def ready(): # Restart reporter when the text was changed. self.run_reporter() - call_from_executor(ready) - run_in_executor(in_executor) + if PTK3: + loop.call_soon_threadsafe(ready) + else: + call_from_executor(ready) + + if PTK3: + loop.run_in_executor(None, in_executor) + else: + run_in_executor(in_executor) diff --git a/setup.py b/setup.py index 3ea0f84..9a06126 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ long_description=long_description, packages=find_packages('.'), install_requires = [ - 'prompt_toolkit>=2.0.0,<2.1.0', + 'prompt_toolkit>=2.0.0,<3.1.0', 'pyflakes', # For Python error reporting. 'pygments', # For the syntax highlighting. 'docopt', # For command line arguments. From 5416fb1652a976da5b0c29eb34ba65d0fe41d1f9 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Thu, 28 Nov 2019 22:05:52 +0100 Subject: [PATCH 38/59] Release 3.0.1 --- CHANGELOG | 6 ++++++ pyvim/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index de0fb28..c737052 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ CHANGELOG ========= +3.0.1: 2019-11-28 +------------------ + +- Upgrade to prompt_toolkit 3.0 + + 2.0.24: 2019-01-27 ------------------ diff --git a/pyvim/__init__.py b/pyvim/__init__.py index b498509..239e0ce 100644 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '2.0.24' +__version__ = '3.0.1' From 798c87fd4045b22147ab268cfb687f2332b94fd9 Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Thu, 28 Nov 2019 22:09:41 +0100 Subject: [PATCH 39/59] Added missing dependency: six. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 9a06126..81de3a3 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ packages=find_packages('.'), install_requires = [ 'prompt_toolkit>=2.0.0,<3.1.0', + 'six', 'pyflakes', # For Python error reporting. 'pygments', # For the syntax highlighting. 'docopt', # For command line arguments. From aeb9264eae669aaaa51c2e66df9be14ea4ac449e Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Thu, 28 Nov 2019 22:10:31 +0100 Subject: [PATCH 40/59] Release 3.0.2. --- CHANGELOG | 6 ++++++ pyvim/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c737052..08059dc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ CHANGELOG ========= +3.0.2: 2019-11-28 +------------------ + +- Added missing dependency: 'six'. + + 3.0.1: 2019-11-28 ------------------ diff --git a/pyvim/__init__.py b/pyvim/__init__.py index 239e0ce..21f3b1d 100644 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '3.0.1' +__version__ = '3.0.2' From 3980234cfd507f6ab42c5f6da43fd38e98aab353 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Tue, 4 Jun 2019 01:52:21 +1000 Subject: [PATCH 41/59] Implement basic :s[ubstitute] command --- pyvim/commands/commands.py | 26 ++++++++++++++++++++++++++ pyvim/commands/grammar.py | 3 +++ pyvim/commands/handler.py | 12 +++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index def2a59..3ff4d8b 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -1,6 +1,9 @@ from __future__ import unicode_literals, print_function from prompt_toolkit.application import run_in_terminal +from prompt_toolkit.document import Document + import os +import re import six __all__ = ( @@ -704,3 +707,26 @@ def set_scroll_offset(editor, value): 'Invalid value. Expecting comma separated list of integers') else: editor.colorcolumn = numbers + + +def substitute(editor, range_start, range_end, search, replace, flags): + if flags == 'g': + transform_callback = lambda s: re.sub(search, replace, s) + else: + transform_callback = lambda s: re.sub(search, replace, s, count=1) + + buffer = editor.current_editor_buffer.buffer + current_row = buffer.document.cursor_position_row + + if not range_end: + range_end = range_start + if range_start and range_end: + line_index_iterator = range(int(range_start) - 1, int(range_end)) + else: + line_index_iterator = range(current_row, current_row + 1) + + new_text = buffer.transform_lines(line_index_iterator, transform_callback) + buffer.document = Document( + new_text, + Document(new_text).translate_row_col_to_index(current_row, 0)) + buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) diff --git a/pyvim/commands/grammar.py b/pyvim/commands/grammar.py index bc3630e..5b47c28 100644 --- a/pyvim/commands/grammar.py +++ b/pyvim/commands/grammar.py @@ -11,6 +11,9 @@ :* \s* ( + # Substitute command + ((?P\d+)(,(?P\d+))?)? (?Ps|substitute) \s* / (?P[^/]*) / (?P[^/]*) (?P /g )? | + # Commands accepting a location. (?P%(commands_taking_locations)s)(?P!?) \s+ (?P[^\s]+) | diff --git a/pyvim/commands/handler.py b/pyvim/commands/handler.py index e926169..606f971 100644 --- a/pyvim/commands/handler.py +++ b/pyvim/commands/handler.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from .grammar import COMMAND_GRAMMAR -from .commands import call_command_handler, has_command_handler +from .commands import call_command_handler, has_command_handler, substitute __all__ = ( 'handle_command', @@ -21,6 +21,11 @@ def handle_command(editor, input_string): command = variables.get('command') go_to_line = variables.get('go_to_line') shell_command = variables.get('shell_command') + range_start = variables.get('range_start') + range_end = variables.get('range_end') + search = variables.get('search') + replace = variables.get('replace') + flags = variables.get('flags', '') # Call command handler. @@ -35,6 +40,11 @@ def handle_command(editor, input_string): elif has_command_handler(command): # Handle other 'normal' commands. call_command_handler(command, editor, variables) + + elif command in ('s', 'substitute'): + flags = flags.lstrip('/') + substitute(editor, range_start, range_end, search, replace, flags) + else: # For unknown commands, show error message. editor.show_message('Not an editor command: %s' % input_string) From 103057c63d5b5f4796cc3fa3ad8ad98e862fe4d4 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sat, 29 Feb 2020 21:19:07 +1100 Subject: [PATCH 42/59] Add test for :substitute --- tests/test_substitute.py | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/test_substitute.py diff --git a/tests/test_substitute.py b/tests/test_substitute.py new file mode 100644 index 0000000..4c69235 --- /dev/null +++ b/tests/test_substitute.py @@ -0,0 +1,60 @@ +from pyvim.commands.handler import handle_command + +sample_text = """ +Roses are red, + Violets are blue, +Sugar is sweet, + And so are you. +""".lstrip() + +def given_sample_text(editor_buffer): + editor = editor_buffer.editor + editor.window_arrangement._add_editor_buffer(editor_buffer) + editor_buffer.buffer.text = sample_text + editor.sync_with_prompt_toolkit() + + +def given_cursor_position(editor_buffer, line_number, column=0): + editor_buffer.buffer.cursor_position = \ + editor_buffer.buffer.document.translate_row_col_to_index(line_number - 1, column) + + +def test_substitute_current_line(editor, editor_buffer): + given_sample_text(editor_buffer) + given_cursor_position(editor_buffer, 2) + + handle_command(editor, ':s/s are/ is') + + assert 'Roses are red,' in editor_buffer.buffer.text + assert 'Violet is blue,' in editor_buffer.buffer.text + assert 'And so are you.' in editor_buffer.buffer.text + assert editor_buffer.buffer.cursor_position \ + == editor_buffer.buffer.text.index('Violet') + + +def test_substitute_single_line(editor, editor_buffer): + given_sample_text(editor_buffer) + given_cursor_position(editor_buffer, 1) + + handle_command(editor, ':2s/s are/ is') + + assert 'Roses are red,' in editor_buffer.buffer.text + assert 'Violet is blue,' in editor_buffer.buffer.text + assert 'And so are you.' in editor_buffer.buffer.text + # FIXME: vim would have set the cursor position on the substituted line + # assert editor_buffer.buffer.cursor_position \ + # == editor_buffer.buffer.text.index('Violet') + + +def test_substitute_range(editor, editor_buffer): + given_sample_text(editor_buffer) + given_cursor_position(editor_buffer, 1) + + handle_command(editor, ':1,3s/s are/ is') + + assert 'Rose is red,' in editor_buffer.buffer.text + assert 'Violet is blue,' in editor_buffer.buffer.text + assert 'And so are you.' in editor_buffer.buffer.text + # FIXME: vim would have set the cursor position on last substituted line + # assert editor_buffer.buffer.cursor_position \ + # == editor_buffer.buffer.text.index('Violet') From d9c6234427f500cdfeffbb8bb38a1a2fc08c9d68 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sat, 29 Feb 2020 22:11:46 +1100 Subject: [PATCH 43/59] Fix cursor position after :substitute with line number --- pyvim/commands/commands.py | 1 + tests/test_substitute.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index 3ff4d8b..ffeee4a 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -719,6 +719,7 @@ def substitute(editor, range_start, range_end, search, replace, flags): current_row = buffer.document.cursor_position_row if not range_end: + current_row = (int(range_start) - 1) if range_start else current_row range_end = range_start if range_start and range_end: line_index_iterator = range(int(range_start) - 1, int(range_end)) diff --git a/tests/test_substitute.py b/tests/test_substitute.py index 4c69235..baef413 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -41,9 +41,8 @@ def test_substitute_single_line(editor, editor_buffer): assert 'Roses are red,' in editor_buffer.buffer.text assert 'Violet is blue,' in editor_buffer.buffer.text assert 'And so are you.' in editor_buffer.buffer.text - # FIXME: vim would have set the cursor position on the substituted line - # assert editor_buffer.buffer.cursor_position \ - # == editor_buffer.buffer.text.index('Violet') + assert editor_buffer.buffer.cursor_position \ + == editor_buffer.buffer.text.index('Violet') def test_substitute_range(editor, editor_buffer): From 8825d816dcbd8d59217dc8a590639f6a0434c28a Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 11:27:53 +1100 Subject: [PATCH 44/59] Implement :substitute with last /-search pattern --- pyvim/commands/commands.py | 2 ++ tests/test_substitute.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index ffeee4a..4bd4957 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -710,6 +710,8 @@ def set_scroll_offset(editor, value): def substitute(editor, range_start, range_end, search, replace, flags): + if not search: + search = editor.application.current_search_state.text if flags == 'g': transform_callback = lambda s: re.sub(search, replace, s) else: diff --git a/tests/test_substitute.py b/tests/test_substitute.py index baef413..b66c0d8 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -57,3 +57,11 @@ def test_substitute_range(editor, editor_buffer): # FIXME: vim would have set the cursor position on last substituted line # assert editor_buffer.buffer.cursor_position \ # == editor_buffer.buffer.text.index('Violet') + + +def test_substitute_from_search_history(editor, editor_buffer): + given_sample_text(editor_buffer) + editor.application.current_search_state.text = 'blue' + + handle_command(editor, ':1,3s//pretty') + assert 'Violets are pretty,' in editor_buffer.buffer.text From a090d5f3dc3b34bd4e65cb9070fa56f35c6b0537 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 12:09:07 +1100 Subject: [PATCH 45/59] Add test for :s's 'g' flag and fix empty flags case --- pyvim/commands/grammar.py | 2 +- tests/test_substitute.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pyvim/commands/grammar.py b/pyvim/commands/grammar.py index 5b47c28..8531e45 100644 --- a/pyvim/commands/grammar.py +++ b/pyvim/commands/grammar.py @@ -12,7 +12,7 @@ \s* ( # Substitute command - ((?P\d+)(,(?P\d+))?)? (?Ps|substitute) \s* / (?P[^/]*) / (?P[^/]*) (?P /g )? | + ((?P\d+)(,(?P\d+))?)? (?Ps|substitute) \s* / (?P[^/]*) / (?P[^/]*) (?P /(g)? )? | # Commands accepting a location. (?P%(commands_taking_locations)s)(?P!?) \s+ (?P[^\s]+) | diff --git a/tests/test_substitute.py b/tests/test_substitute.py index b66c0d8..f5350ea 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -7,10 +7,10 @@ And so are you. """.lstrip() -def given_sample_text(editor_buffer): +def given_sample_text(editor_buffer, text=None): editor = editor_buffer.editor editor.window_arrangement._add_editor_buffer(editor_buffer) - editor_buffer.buffer.text = sample_text + editor_buffer.buffer.text = text or sample_text editor.sync_with_prompt_toolkit() @@ -65,3 +65,14 @@ def test_substitute_from_search_history(editor, editor_buffer): handle_command(editor, ':1,3s//pretty') assert 'Violets are pretty,' in editor_buffer.buffer.text + + +def test_substitute_flags_empty_flags(editor, editor_buffer): + given_sample_text(editor_buffer, 'Violet is Violet\n') + handle_command(editor, ':s/Violet/Rose/') + assert 'Rose is Violet' in editor_buffer.buffer.text + +def test_substitute_flags_g(editor, editor_buffer): + given_sample_text(editor_buffer, 'Rose is Violet\n') + handle_command(editor, ':s/Violet/Rose/g') + assert 'Rose is Rose' in editor_buffer.buffer.text From 9d15fbcc8d79959d3e30d17b21af4e330dda6eb0 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 12:53:37 +1100 Subject: [PATCH 46/59] Implement repeat last substitution --- pyvim/commands/commands.py | 8 ++++++++ pyvim/editor.py | 2 ++ tests/test_substitute.py | 11 +++++++++++ 3 files changed, 21 insertions(+) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index 4bd4957..e1b9c37 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -710,8 +710,16 @@ def set_scroll_offset(editor, value): def substitute(editor, range_start, range_end, search, replace, flags): + if editor.last_substitute_text and replace is None: + replace = editor.last_substitute_text + else: + editor.last_substitute_text = replace + if not search: search = editor.application.current_search_state.text + else: + editor.application.current_search_state.text = search + if flags == 'g': transform_callback = lambda s: re.sub(search, replace, s) else: diff --git a/pyvim/editor.py b/pyvim/editor.py index 075674a..842df37 100644 --- a/pyvim/editor.py +++ b/pyvim/editor.py @@ -129,6 +129,8 @@ def key_pressed(_): # Command line previewer. self.previewer = CommandPreviewer(self) + self.last_substitute_text = '' + def load_initial_files(self, locations, in_tab_pages=False, hsplit=False, vsplit=False): """ Load a list of files. diff --git a/tests/test_substitute.py b/tests/test_substitute.py index f5350ea..3f26698 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -67,6 +67,17 @@ def test_substitute_from_search_history(editor, editor_buffer): assert 'Violets are pretty,' in editor_buffer.buffer.text +def test_substitute_with_repeat_last_substitution(editor, editor_buffer): + given_sample_text(editor_buffer, 'Violet is Violet\n') + editor.application.current_search_state.text = 'Lily' + + handle_command(editor, ':s/Violet/Rose') + assert 'Rose is Violet' in editor_buffer.buffer.text + + handle_command(editor, ':s') + assert 'Rose is Rose' in editor_buffer.buffer.text + + def test_substitute_flags_empty_flags(editor, editor_buffer): given_sample_text(editor_buffer, 'Violet is Violet\n') handle_command(editor, ':s/Violet/Rose/') From f84253284d290edc6ed476472fa43b7fbcd3dfca Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 16:52:41 +1100 Subject: [PATCH 47/59] Add test for repeating substitution pattern from substitute history --- tests/test_substitute.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_substitute.py b/tests/test_substitute.py index 3f26698..a39fe83 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -67,6 +67,16 @@ def test_substitute_from_search_history(editor, editor_buffer): assert 'Violets are pretty,' in editor_buffer.buffer.text +def test_substitute_from_substitute_search_history(editor, editor_buffer): + given_sample_text(editor_buffer, 'Violet is Violet') + + handle_command(editor, ':s/Violet/Rose') + assert 'Rose is Violet' in editor_buffer.buffer.text + + handle_command(editor, ':s//Lily') + assert 'Rose is Lily' in editor_buffer.buffer.text + + def test_substitute_with_repeat_last_substitution(editor, editor_buffer): given_sample_text(editor_buffer, 'Violet is Violet\n') editor.application.current_search_state.text = 'Lily' From a272f954bd0ec2aa095abd157c026956a9b6d81a Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 17:38:44 +1100 Subject: [PATCH 48/59] Fix g-flag test --- tests/test_substitute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_substitute.py b/tests/test_substitute.py index a39fe83..60589ae 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -94,6 +94,6 @@ def test_substitute_flags_empty_flags(editor, editor_buffer): assert 'Rose is Violet' in editor_buffer.buffer.text def test_substitute_flags_g(editor, editor_buffer): - given_sample_text(editor_buffer, 'Rose is Violet\n') + given_sample_text(editor_buffer, 'Violet is Violet\n') handle_command(editor, ':s/Violet/Rose/g') assert 'Rose is Rose' in editor_buffer.buffer.text From 9bdd3c6d215e330efd7c91eb1baab1fa3c57819c Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 21:25:33 +1100 Subject: [PATCH 49/59] Refactor commands/commands.py:substitute() --- pyvim/commands/commands.py | 56 +++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index e1b9c37..170f34b 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -710,34 +710,46 @@ def set_scroll_offset(editor, value): def substitute(editor, range_start, range_end, search, replace, flags): - if editor.last_substitute_text and replace is None: - replace = editor.last_substitute_text - else: - editor.last_substitute_text = replace + def get_line_index_iterator(cursor_position_row, range_start, range_end): + if not range_start: + range_start = range_end = cursor_position_row + else: + range_start = int(range_start) - 1 + if not range_end: + range_end = range_start + range_end = int(range_end) + return range(range_start, range_end + 1) + + def get_transform_callback(search, replace, flags): + SUBSTITUTE_ALL, SUBSTITUTE_ONE = 0, 1 + sub_count = SUBSTITUTE_ALL if 'g' in flags else SUBSTITUTE_ONE + return lambda s: re.sub(search, replace, s, count=sub_count) + + search_state = editor.application.current_search_state + buffer = editor.current_editor_buffer.buffer + cursor_position_row = buffer.document.cursor_position_row + # read editor state if not search: - search = editor.application.current_search_state.text - else: - editor.application.current_search_state.text = search + search = search_state.text - if flags == 'g': - transform_callback = lambda s: re.sub(search, replace, s) - else: - transform_callback = lambda s: re.sub(search, replace, s, count=1) + if editor.last_substitute_text and replace is None: + replace = editor.last_substitute_text - buffer = editor.current_editor_buffer.buffer - current_row = buffer.document.cursor_position_row + line_index_iterator = get_line_index_iterator(cursor_position_row, range_start, range_end) + transform_callback = get_transform_callback(search, replace, flags) + new_text = buffer.transform_lines(line_index_iterator, transform_callback) - if not range_end: - current_row = (int(range_start) - 1) if range_start else current_row - range_end = range_start - if range_start and range_end: - line_index_iterator = range(int(range_start) - 1, int(range_end)) - else: - line_index_iterator = range(current_row, current_row + 1) + new_cursor_position_row = int(range_start) - 1 if range_start and not range_end else cursor_position_row - new_text = buffer.transform_lines(line_index_iterator, transform_callback) + # update text buffer buffer.document = Document( new_text, - Document(new_text).translate_row_col_to_index(current_row, 0)) + Document(new_text).translate_row_col_to_index(new_cursor_position_row, 0), + ) buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) + buffer._search(search_state, include_current_position=True) + + # update editor state + editor.last_substitute_text = replace + search_state.text = search From a49de3cb8bbb59151d61621eff8f824b80f14785 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 21:30:22 +1100 Subject: [PATCH 50/59] Add documentation --- pyvim/commands/commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index 170f34b..2930f12 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -710,6 +710,7 @@ def set_scroll_offset(editor, value): def substitute(editor, range_start, range_end, search, replace, flags): + """ Substitute /search/ with /replace/ over a range of text """ def get_line_index_iterator(cursor_position_row, range_start, range_end): if not range_start: range_start = range_end = cursor_position_row From 496a5120fcdd230a9202c468b737bc0e20858dd1 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 21:37:31 +1100 Subject: [PATCH 51/59] Fix exception on repeat substitution without substitution --- pyvim/commands/commands.py | 2 +- tests/test_substitute.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index 2930f12..c54174a 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -734,7 +734,7 @@ def get_transform_callback(search, replace, flags): if not search: search = search_state.text - if editor.last_substitute_text and replace is None: + if replace is None: replace = editor.last_substitute_text line_index_iterator = get_line_index_iterator(cursor_position_row, range_start, range_end) diff --git a/tests/test_substitute.py b/tests/test_substitute.py index 60589ae..0356e01 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -88,6 +88,19 @@ def test_substitute_with_repeat_last_substitution(editor, editor_buffer): assert 'Rose is Rose' in editor_buffer.buffer.text +def test_substitute_with_repeat_last_substitution_without_previous_substitution(editor, editor_buffer): + original_text = 'Violet is blue\n' + given_sample_text(editor_buffer, original_text) + + handle_command(editor, ':s') + assert original_text in editor_buffer.buffer.text + + editor.application.current_search_state.text = 'blue' + + handle_command(editor, ':s') + assert 'Violet is \n' in editor_buffer.buffer.text + + def test_substitute_flags_empty_flags(editor, editor_buffer): given_sample_text(editor_buffer, 'Violet is Violet\n') handle_command(editor, ':s/Violet/Rose/') From 862be133495179ea226421bcab1b34a335503eaa Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 21:57:01 +1100 Subject: [PATCH 52/59] Add test for range specification's boundary condition --- pyvim/commands/commands.py | 4 +--- tests/test_substitute.py | 8 ++++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index c54174a..7cadb55 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -716,9 +716,7 @@ def get_line_index_iterator(cursor_position_row, range_start, range_end): range_start = range_end = cursor_position_row else: range_start = int(range_start) - 1 - if not range_end: - range_end = range_start - range_end = int(range_end) + range_end = int(range_end) - 1 if range_end else range_start return range(range_start, range_end + 1) def get_transform_callback(search, replace, flags): diff --git a/tests/test_substitute.py b/tests/test_substitute.py index 0356e01..e0b72c9 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -59,6 +59,14 @@ def test_substitute_range(editor, editor_buffer): # == editor_buffer.buffer.text.index('Violet') +def test_substitute_range_boundaries(editor, editor_buffer): + given_sample_text(editor_buffer, 'Violet\n' * 4) + + handle_command(editor, ':2,3s/Violet/Rose') + + assert 'Violet\nRose\nRose\nViolet\n' in editor_buffer.buffer.text + + def test_substitute_from_search_history(editor, editor_buffer): given_sample_text(editor_buffer) editor.application.current_search_state.text = 'blue' From 5e45d33606dfcfa32cb3a6bebbf51bc7414ddb8e Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 22:07:22 +1100 Subject: [PATCH 53/59] Set cursor position after replacement to last selected range --- pyvim/commands/commands.py | 4 +++- tests/test_substitute.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index 7cadb55..826d458 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -713,6 +713,7 @@ def substitute(editor, range_start, range_end, search, replace, flags): """ Substitute /search/ with /replace/ over a range of text """ def get_line_index_iterator(cursor_position_row, range_start, range_end): if not range_start: + assert not range_end range_start = range_end = cursor_position_row else: range_start = int(range_start) - 1 @@ -739,7 +740,8 @@ def get_transform_callback(search, replace, flags): transform_callback = get_transform_callback(search, replace, flags) new_text = buffer.transform_lines(line_index_iterator, transform_callback) - new_cursor_position_row = int(range_start) - 1 if range_start and not range_end else cursor_position_row + assert len(line_index_iterator) >= 1 + new_cursor_position_row = line_index_iterator[-1] # update text buffer buffer.document = Document( diff --git a/tests/test_substitute.py b/tests/test_substitute.py index e0b72c9..5c49e0a 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -55,8 +55,12 @@ def test_substitute_range(editor, editor_buffer): assert 'Violet is blue,' in editor_buffer.buffer.text assert 'And so are you.' in editor_buffer.buffer.text # FIXME: vim would have set the cursor position on last substituted line + # but we set the cursor position on the end_range even when there + # is not substitution there # assert editor_buffer.buffer.cursor_position \ # == editor_buffer.buffer.text.index('Violet') + assert editor_buffer.buffer.cursor_position \ + == editor_buffer.buffer.text.index('Sugar') def test_substitute_range_boundaries(editor, editor_buffer): From 44937c508397486722e2e6d8c044792a818acfe3 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 1 Mar 2020 22:28:17 +1100 Subject: [PATCH 54/59] Loosen grammar to allow for missing parentheses --- pyvim/commands/grammar.py | 2 +- tests/test_substitute.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pyvim/commands/grammar.py b/pyvim/commands/grammar.py index 8531e45..1975395 100644 --- a/pyvim/commands/grammar.py +++ b/pyvim/commands/grammar.py @@ -12,7 +12,7 @@ \s* ( # Substitute command - ((?P\d+)(,(?P\d+))?)? (?Ps|substitute) \s* / (?P[^/]*) / (?P[^/]*) (?P /(g)? )? | + ((?P\d+)(,(?P\d+))?)? (?Ps|substitute) \s* / (?P[^/]*) ( / (?P[^/]*) (?P /(g)? )? )? | # Commands accepting a location. (?P%(commands_taking_locations)s)(?P!?) \s+ (?P[^\s]+) | diff --git a/tests/test_substitute.py b/tests/test_substitute.py index 5c49e0a..5d1711b 100644 --- a/tests/test_substitute.py +++ b/tests/test_substitute.py @@ -80,7 +80,7 @@ def test_substitute_from_search_history(editor, editor_buffer): def test_substitute_from_substitute_search_history(editor, editor_buffer): - given_sample_text(editor_buffer, 'Violet is Violet') + given_sample_text(editor_buffer, 'Violet is Violet\n') handle_command(editor, ':s/Violet/Rose') assert 'Rose is Violet' in editor_buffer.buffer.text @@ -100,6 +100,20 @@ def test_substitute_with_repeat_last_substitution(editor, editor_buffer): assert 'Rose is Rose' in editor_buffer.buffer.text +def test_substitute_without_replacement_text(editor, editor_buffer): + given_sample_text(editor_buffer, 'Violet Violet Violet \n') + editor.application.current_search_state.text = 'Lily' + + handle_command(editor, ':s/Violet/') + assert ' Violet Violet \n' in editor_buffer.buffer.text + + handle_command(editor, ':s/Violet') + assert ' Violet \n' in editor_buffer.buffer.text + + handle_command(editor, ':s/') + assert ' \n' in editor_buffer.buffer.text + + def test_substitute_with_repeat_last_substitution_without_previous_substitution(editor, editor_buffer): original_text = 'Violet is blue\n' given_sample_text(editor_buffer, original_text) @@ -118,6 +132,7 @@ def test_substitute_flags_empty_flags(editor, editor_buffer): handle_command(editor, ':s/Violet/Rose/') assert 'Rose is Violet' in editor_buffer.buffer.text + def test_substitute_flags_g(editor, editor_buffer): given_sample_text(editor_buffer, 'Violet is Violet\n') handle_command(editor, ':s/Violet/Rose/g') From 210816b4b1d2dc00606ec924dda29e49ac4cd87c Mon Sep 17 00:00:00 2001 From: Tom Huibregtse Date: Fri, 15 Oct 2021 11:55:20 -0500 Subject: [PATCH 55/59] Fix the F9 key binding in the example pyvimrc This fixes the "editor_buffer.filename is None" issue as well as an issue with running the command in a subprocess. --- examples/config/pyvimrc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/config/pyvimrc b/examples/config/pyvimrc index 978d260..94a1703 100644 --- a/examples/config/pyvimrc +++ b/examples/config/pyvimrc @@ -2,6 +2,7 @@ """ Pyvim configuration. Save to file to: ~/.pyvimrc """ +from prompt_toolkit.application import run_in_terminal from prompt_toolkit.filters import ViInsertMode from prompt_toolkit.key_binding.key_processor import KeyPress from prompt_toolkit.keys import Keys @@ -64,7 +65,7 @@ def configure(editor): editor_buffer = editor.current_editor_buffer if editor_buffer is not None: - if editor_buffer.filename is None: + if editor_buffer.location is None: editor.show_message("File doesn't have a filename. Please save first.") return else: @@ -74,7 +75,7 @@ def configure(editor): # `CommandLineInterface.run_in_terminal` to go to the background and # not destroy the window layout. def execute(): - call(['python', editor_buffer.filename]) + call(['python3', editor_buffer.location]) six.moves.input('Press enter to continue...') - editor.cli.run_in_terminal(execute) + run_in_terminal(execute) From 365be09cad90a469320174019ecc4135acf82e24 Mon Sep 17 00:00:00 2001 From: James Templet Date: Fri, 13 May 2022 16:24:56 -0500 Subject: [PATCH 56/59] Update license string in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 81de3a3..f6257e7 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ name='pyvim', author='Jonathan Slenders', version=pyvim.__version__, - license='LICENSE', + license='BSD License', url='https://github.com/jonathanslenders/pyvim', description='Pure Python Vi Implementation', long_description=long_description, From 14118ad2e4d0da2e955fd9069b8772408307618b Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Mon, 16 May 2022 21:00:03 +0200 Subject: [PATCH 57/59] Release 3.0.3 --- CHANGELOG | 7 +++++++ pyvim/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 08059dc..7d46893 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ CHANGELOG ========= +3.0.3: 2022-05-16 +----------------- + +- Implemented basic :s[ubstitute] command and various related fixes. +- Fixed license text in setup.py. + + 3.0.2: 2019-11-28 ------------------ diff --git a/pyvim/__init__.py b/pyvim/__init__.py index 21f3b1d..673f6eb 100644 --- a/pyvim/__init__.py +++ b/pyvim/__init__.py @@ -1,3 +1,3 @@ from __future__ import unicode_literals -__version__ = '3.0.2' +__version__ = '3.0.3' From e8a2d69c1123f3f8a45bcc0e58c755af56d107f7 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Fri, 25 Dec 2020 06:44:41 +1100 Subject: [PATCH 58/59] docs: fix simple typo, underneat -> underneath There is a small typo in pyvim/layout.py. Should read `underneath` rather than `underneat`. --- pyvim/layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyvim/layout.py b/pyvim/layout.py index 6c3a951..f7bc2be 100644 --- a/pyvim/layout.py +++ b/pyvim/layout.py @@ -528,7 +528,7 @@ def create_layout_from_node(node): def _create_window_frame(self, editor_buffer): """ - Create a Window for the buffer, with underneat a status bar. + Create a Window for the buffer, with underneath a status bar. """ @Condition def wrap_lines(): From ae9257ad4118da0d882bd0e75f687e7158aad02a Mon Sep 17 00:00:00 2001 From: Jonathan Slenders Date: Wed, 12 Apr 2023 15:09:44 +0000 Subject: [PATCH 59/59] Fix various typos. --- README.rst | 4 ++-- pyvim/commands/commands.py | 2 +- pyvim/completion.py | 2 +- pyvim/window_arrangement.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index dbf5852..2bf09db 100644 --- a/README.rst +++ b/README.rst @@ -50,7 +50,7 @@ We have already many nice things, for instance: - All of the functionality of `prompt_toolkit `_. This includes a lot of Vi key bindings, it's platform independent and runs on every Python - version from python 2.6 up to 3.4. It also runs on Pypy with a noticable + version from python 2.6 up to 3.4. It also runs on Pypy with a noticeable performance boost. - Several ``:set ...`` commands have been implemented, like ``incsearch``, @@ -121,7 +121,7 @@ Compared to Vi Improved, Pyvim is still less powerful in many aspects. well for development and quickly prototyping of new features, but it comes with a performance penalty. Depending on the system, when a file has above a thousand lines and syntax highlighting is enabled, editing will become - noticable slower. (The bottleneck is probably the ``BufferControl`` code, + noticeable slower. (The bottleneck is probably the ``BufferControl`` code, which on every key press tries to reflow the text and calls pygments for highlighting. And this is Python code looping through single characters.) - A lot of nice Vim features, like line folding, macros, etcetera are not yet diff --git a/pyvim/commands/commands.py b/pyvim/commands/commands.py index 826d458..9cf174f 100644 --- a/pyvim/commands/commands.py +++ b/pyvim/commands/commands.py @@ -341,7 +341,7 @@ def quit_nonzero(editor): """ # Note: the try/finally in `prompt_toolkit.Interface.read_input` # will ensure that the render output is reset, leaving the alternate - # screen before quiting. + # screen before quitting. editor.application.exit() diff --git a/pyvim/completion.py b/pyvim/completion.py index b659f0b..50a18ad 100644 --- a/pyvim/completion.py +++ b/pyvim/completion.py @@ -115,6 +115,6 @@ def _get_jedi_script_from_document(self, document): # Workaround Jedi issue #514: for https://github.com/davidhalter/jedi/issues/514 return None except KeyError: - # Workaroud for a crash when the input is "u'", the start of a unicode string. + # Workaround for a crash when the input is "u'", the start of a unicode string. return None diff --git a/pyvim/window_arrangement.py b/pyvim/window_arrangement.py index 3984168..5f5044b 100644 --- a/pyvim/window_arrangement.py +++ b/pyvim/window_arrangement.py @@ -183,7 +183,7 @@ def close_active_window(self): self.active_window = None # No windows left. # When there is exactly on item left, move this back into the parent - # split. (We don't want to keep a split with one item around -- exept + # split. (We don't want to keep a split with one item around -- except # for the root.) if len(active_split) == 1 and active_split != self.root: parent = self._get_split_parent(active_split)