[Python-modules-commits] [prompt-toolkit] 01/04: Import prompt-toolkit_1.0.8.orig.tar.gz
Scott Kitterman
kitterman at moszumanska.debian.org
Sun Oct 30 13:14:44 UTC 2016
This is an automated email from the git hooks/post-receive script.
kitterman pushed a commit to branch master
in repository prompt-toolkit.
commit 97babc702d91a47d6cba9ba00db0e0e7f8d5492d
Author: Scott Kitterman <scott at kitterman.com>
Date: Sun Oct 30 09:07:11 2016 -0400
Import prompt-toolkit_1.0.8.orig.tar.gz
---
CHANGELOG | 25 +++
PKG-INFO | 4 +-
README.rst | 2 +-
examples/patch-stdout.py | 2 +-
prompt_toolkit.egg-info/PKG-INFO | 4 +-
prompt_toolkit/__init__.py | 2 +-
prompt_toolkit/buffer.py | 5 +
prompt_toolkit/eventloop/base.py | 6 +-
prompt_toolkit/eventloop/posix.py | 43 +++--
prompt_toolkit/eventloop/select.py | 237 ++++++++++++++++++++-------
prompt_toolkit/filters/cli.py | 17 ++
prompt_toolkit/interface.py | 17 +-
prompt_toolkit/key_binding/bindings/basic.py | 4 +
prompt_toolkit/key_binding/bindings/vi.py | 207 +++++++++++++++++++----
prompt_toolkit/key_binding/vi_state.py | 1 +
prompt_toolkit/keys.py | 6 +
prompt_toolkit/layout/lexers.py | 1 +
prompt_toolkit/layout/processors.py | 41 ++++-
prompt_toolkit/renderer.py | 43 ++---
prompt_toolkit/shortcuts.py | 13 +-
prompt_toolkit/styles/defaults.py | 2 +
prompt_toolkit/terminal/vt100_input.py | 65 ++++++--
prompt_toolkit/terminal/win32_input.py | 7 +-
prompt_toolkit/terminal/win32_output.py | 2 +-
setup.cfg | 2 +-
tests/test_cli.py | 40 ++++-
26 files changed, 629 insertions(+), 169 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
index d7c0de5..181efc6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,31 @@
CHANGELOG
=========
+1.0.8: 2016-10-16
+-----------------
+
+Fixes:
+- In 'shortcuts': complete_while_typing was a SimpleFilter, not a CLIFilter.
+- Always reset color attributes after rendering.
+- Handle bug in Windows when '$TERM' is not defined.
+- Ignore errors when calling tcgetattr/tcsetattr.
+ (This handles the "Inappropriate ioctl for device" crash in some scenarios.)
+- Fix for Windows. Correctly recognize all Chinese and Lithuanian characters.
+
+New features:
+- Added shift+left/up/down/right keys.
+- Small performance optimization in the renderer.
+- Small optimization in the posix event loop. Don't call time.time() if we
+ don't have an inputhook. (Less syscalls.)
+- Turned the _max_postpone_until argument of call_from_executor into a float.
+ (As returned by `time.time`.) This will do less system calls. It's
+ backwards-incompatible, but this is still a private API, used only by pymux.)
+- Added Shift-I/A commands in Vi block selection mode for inserting text at the
+ beginning of each line of the block.
+- Refactoring of the 'selectors' module for the posix event loop. (Reuse the
+ same selector object in one loop, don't recreate it for each select.)
+
+
1.0.7: 2016-08-21
-----------------
diff --git a/PKG-INFO b/PKG-INFO
index 0035cfa..7907dd9 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: prompt_toolkit
-Version: 1.0.7
+Version: 1.0.8
Summary: Library for building powerful interactive command lines in Python
Home-page: https://github.com/jonathanslenders/python-prompt-toolkit
Author: Jonathan Slenders
@@ -171,7 +171,7 @@ Description: Python Prompt Toolkit
.. |Build Status| image:: https://api.travis-ci.org/jonathanslenders/python-prompt-toolkit.svg?branch=master
:target: https://travis-ci.org/jonathanslenders/python-prompt-toolkit#
- .. |PyPI| image:: https://pypip.in/version/prompt-toolkit/badge.svg
+ .. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg
:target: https://pypi.python.org/pypi/prompt-toolkit/
:alt: Latest Version
diff --git a/README.rst b/README.rst
index 8eea0c5..80a6e07 100644
--- a/README.rst
+++ b/README.rst
@@ -163,6 +163,6 @@ Special thanks to
.. |Build Status| image:: https://api.travis-ci.org/jonathanslenders/python-prompt-toolkit.svg?branch=master
:target: https://travis-ci.org/jonathanslenders/python-prompt-toolkit#
-.. |PyPI| image:: https://pypip.in/version/prompt-toolkit/badge.svg
+.. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg
:target: https://pypi.python.org/pypi/prompt-toolkit/
:alt: Latest Version
diff --git a/examples/patch-stdout.py b/examples/patch-stdout.py
index 2f952df..23eb3ff 100755
--- a/examples/patch-stdout.py
+++ b/examples/patch-stdout.py
@@ -29,7 +29,7 @@ def main():
result = prompt('Say something: ', patch_stdout=True)
print('You said: %s' % result)
- # Stop thrad.
+ # Stop thread.
running = False
diff --git a/prompt_toolkit.egg-info/PKG-INFO b/prompt_toolkit.egg-info/PKG-INFO
index 8a6d2bb..d8f089f 100644
--- a/prompt_toolkit.egg-info/PKG-INFO
+++ b/prompt_toolkit.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: prompt-toolkit
-Version: 1.0.7
+Version: 1.0.8
Summary: Library for building powerful interactive command lines in Python
Home-page: https://github.com/jonathanslenders/python-prompt-toolkit
Author: Jonathan Slenders
@@ -171,7 +171,7 @@ Description: Python Prompt Toolkit
.. |Build Status| image:: https://api.travis-ci.org/jonathanslenders/python-prompt-toolkit.svg?branch=master
:target: https://travis-ci.org/jonathanslenders/python-prompt-toolkit#
- .. |PyPI| image:: https://pypip.in/version/prompt-toolkit/badge.svg
+ .. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg
:target: https://pypi.python.org/pypi/prompt-toolkit/
:alt: Latest Version
diff --git a/prompt_toolkit/__init__.py b/prompt_toolkit/__init__.py
index b5a76aa..57d6c1e 100644
--- a/prompt_toolkit/__init__.py
+++ b/prompt_toolkit/__init__.py
@@ -19,4 +19,4 @@ from .shortcuts import prompt
# Don't forget to update in `docs/conf.py`!
-__version__ = '1.0.7'
+__version__ = '1.0.8'
diff --git a/prompt_toolkit/buffer.py b/prompt_toolkit/buffer.py
index 2612f8d..5b821fe 100644
--- a/prompt_toolkit/buffer.py
+++ b/prompt_toolkit/buffer.py
@@ -256,6 +256,11 @@ class Buffer(object):
# State of the selection.
self.selection_state = None
+ # Multiple cursor mode. (When we press 'I' or 'A' in visual-block mode,
+ # we can insert text on multiple lines at once. This is implemented by
+ # using multiple cursors.)
+ self.multiple_cursor_positions = []
+
# When doing consecutive up/down movements, prefer to stay at this column.
self.preferred_column = None
diff --git a/prompt_toolkit/eventloop/base.py b/prompt_toolkit/eventloop/base.py
index bac1e02..db86fac 100644
--- a/prompt_toolkit/eventloop/base.py
+++ b/prompt_toolkit/eventloop/base.py
@@ -74,8 +74,12 @@ class EventLoop(with_metaclass(ABCMeta, object)):
Call this function in the main event loop. Similar to Twisted's
``callFromThread``.
- :param _max_postpone_until: `None` or `datetime` instance. For interal
+ :param _max_postpone_until: `None` or `time.time` value. For interal
use. If the eventloop is saturated, consider this task to be low
priority and postpone maximum until this timestamp. (For instance,
repaint is done using low priority.)
+
+ Note: In the past, this used to be a datetime.datetime instance,
+ but apparently, executing `time.time` is more efficient: it
+ does fewer system calls. (It doesn't read /etc/localtime.)
"""
diff --git a/prompt_toolkit/eventloop/posix.py b/prompt_toolkit/eventloop/posix.py
index b6a8f2e..0262f4d 100644
--- a/prompt_toolkit/eventloop/posix.py
+++ b/prompt_toolkit/eventloop/posix.py
@@ -1,10 +1,10 @@
from __future__ import unicode_literals
-import datetime
import fcntl
import os
import random
import signal
import threading
+import time
from prompt_toolkit.terminal.vt100_input import InputStream
from prompt_toolkit.utils import DummyContext, in_main_thread
@@ -14,21 +14,22 @@ from .callbacks import EventLoopCallbacks
from .inputhook import InputHookContext
from .posix_utils import PosixStdinReader
from .utils import TimeIt
-from .select import select_fds
+from .select import AutoSelector, Selector, fd_to_int
__all__ = (
'PosixEventLoop',
)
-_now = datetime.datetime.now
+_now = time.time
class PosixEventLoop(EventLoop):
"""
Event loop for posix systems (Linux, Mac os X).
"""
- def __init__(self, inputhook=None):
+ def __init__(self, inputhook=None, selector=AutoSelector):
assert inputhook is None or callable(inputhook)
+ assert issubclass(selector, Selector)
self.running = False
self.closed = False
@@ -37,6 +38,7 @@ class PosixEventLoop(EventLoop):
self._calls_from_executor = []
self._read_fds = {} # Maps fd to handler.
+ self.selector = selector()
# Create a pipe for inter thread communication.
self._schedule_pipe = os.pipe()
@@ -93,18 +95,21 @@ class PosixEventLoop(EventLoop):
with ctx:
while self._running:
# Call inputhook.
- with TimeIt() as inputhook_timer:
- if self._inputhook_context:
+ if self._inputhook_context:
+ with TimeIt() as inputhook_timer:
def ready(wait):
" True when there is input ready. The inputhook should return control. "
return self._ready_for_reading(current_timeout[0] if wait else 0) != []
self._inputhook_context.call_inputhook(ready)
+ inputhook_duration = inputhook_timer.duration
+ else:
+ inputhook_duration = 0
# Calculate remaining timeout. (The inputhook consumed some of the time.)
if current_timeout[0] is None:
remaining_timeout = None
else:
- remaining_timeout = max(0, current_timeout[0] - inputhook_timer.duration)
+ remaining_timeout = max(0, current_timeout[0] - inputhook_duration)
# Wait until input is ready.
fds = self._ready_for_reading(remaining_timeout)
@@ -122,17 +127,23 @@ class PosixEventLoop(EventLoop):
# case.
tasks = []
low_priority_tasks = []
- now = _now()
+ now = None # Lazy load time. (Fewer system calls.)
for fd in fds:
# For the 'call_from_executor' fd, put each pending
# item on either the high or low priority queue.
if fd == self._schedule_pipe[0]:
for c, max_postpone_until in self._calls_from_executor:
- if max_postpone_until is None or max_postpone_until < now:
+ if max_postpone_until is None:
+ # Execute now.
tasks.append(c)
else:
- low_priority_tasks.append((c, max_postpone_until))
+ # Execute soon, if `max_postpone_until` is in the future.
+ now = now or _now()
+ if max_postpone_until < now:
+ tasks.append(c)
+ else:
+ low_priority_tasks.append((c, max_postpone_until))
self._calls_from_executor = []
# Flush all the pipe content.
@@ -179,8 +190,7 @@ class PosixEventLoop(EventLoop):
"""
Return the file descriptors that are ready for reading.
"""
- read_fds = list(self._read_fds.keys())
- fds = select_fds(read_fds, timeout)
+ fds = self.selector.select(timeout)
return fds
def received_winch(self):
@@ -225,11 +235,12 @@ class PosixEventLoop(EventLoop):
Call this function in the main event loop.
Similar to Twisted's ``callFromThread``.
- :param _max_postpone_until: `None` or `datetime` instance. For interal
+ :param _max_postpone_until: `None` or `time.time` value. For interal
use. If the eventloop is saturated, consider this task to be low
priority and postpone maximum until this timestamp. (For instance,
repaint is done using low priority.)
"""
+ assert _max_postpone_until is None or isinstance(_max_postpone_until, float)
self._calls_from_executor.append((callback, _max_postpone_until))
if self._schedule_pipe:
@@ -257,13 +268,19 @@ class PosixEventLoop(EventLoop):
def add_reader(self, fd, callback):
" Add read file descriptor to the event loop. "
+ fd = fd_to_int(fd)
self._read_fds[fd] = callback
+ self.selector.register(fd)
def remove_reader(self, fd):
" Remove read file descriptor from the event loop. "
+ fd = fd_to_int(fd)
+
if fd in self._read_fds:
del self._read_fds[fd]
+ self.selector.unregister(fd)
+
class call_on_sigwinch(object):
"""
diff --git a/prompt_toolkit/eventloop/select.py b/prompt_toolkit/eventloop/select.py
index ae4fde4..f678f84 100644
--- a/prompt_toolkit/eventloop/select.py
+++ b/prompt_toolkit/eventloop/select.py
@@ -1,105 +1,216 @@
+"""
+Selectors for the Posix event loop.
+"""
from __future__ import unicode_literals, absolute_import
import sys
-import select
+import abc
import errno
+import select
+import six
__all__ = (
- 'select_fds',
+ 'AutoSelector',
+ 'PollSelector',
+ 'SelectSelector',
+ 'Selector',
+ 'fd_to_int',
)
-def _fd_to_int(fd):
+def fd_to_int(fd):
+ assert isinstance(fd, int) or hasattr(fd, 'fileno')
+
if isinstance(fd, int):
return fd
else:
return fd.fileno()
-def select_fds(read_fds, timeout):
- """
- Wait for a list of file descriptors (`read_fds`) to become ready for
- reading. This chooses the most appropriate select-tool for use in
- prompt-toolkit.
+class Selector(six.with_metaclass(abc.ABCMeta, object)):
+ @abc.abstractmethod
+ def register(self, fd):
+ assert isinstance(fd, int)
- Note: This is an internal API that shouldn't be used for external projects.
- """
- # Map to ensure that we return the objects that were passed in originally.
- # Whether they are a fd integer or an object that has a fileno().
- # (The 'poll' implementation for instance, returns always integers.)
- fd_map = dict((_fd_to_int(fd), fd) for fd in read_fds)
+ @abc.abstractmethod
+ def unregister(self, fd):
+ assert isinstance(fd, int)
- # Use of the 'select' module, that was introduced in Python3.4. We don't
- # use it before 3.5 however, because this is the point where this module
- # retries interrupted system calls.
- if sys.version_info >= (3, 5):
- try:
- result = _python3_selectors(read_fds, timeout)
- except PermissionError:
- # We had a situation (in pypager) where epoll raised a
- # PermissionError when a local file descriptor was registered,
- # however poll and select worked fine. So, in that case, just try
- # using select below.
- result = None
- else:
- result = None
+ @abc.abstractmethod
+ def select(self, timeout):
+ pass
+
+ @abc.abstractmethod
+ def close(self):
+ pass
+
+
+class AutoSelector(Selector):
+ def __init__(self):
+ self._fds = []
+
+ self._select_selector = SelectSelector()
+ self._selectors = [self._select_selector]
+
+ # When 'select.poll' exists, create a PollSelector.
+ if hasattr(select, 'poll'):
+ self._poll_selector = PollSelector()
+ self._selectors.append(self._poll_selector)
+ else:
+ self._poll_selector = None
+
+ # Use of the 'select' module, that was introduced in Python3.4. We don't
+ # use it before 3.5 however, because this is the point where this module
+ # retries interrupted system calls.
+ if sys.version_info >= (3, 5):
+ self._py3_selector = Python3Selector()
+ self._selectors.append(self._py3_selector)
+ else:
+ self._py3_selector = None
+
+ def register(self, fd):
+ assert isinstance(fd, int)
+
+ self._fds.append(fd)
+
+ for sel in self._selectors:
+ sel.register(fd)
+
+ def unregister(self, fd):
+ assert isinstance(fd, int)
+
+ self._fds.remove(fd)
+
+ for sel in self._selectors:
+ sel.unregister(fd)
+
+ def select(self, timeout):
+ # Try Python 3 selector first.
+ if self._py3_selector:
+ try:
+ return self._py3_selector.select(timeout)
+ except PermissionError:
+ # We had a situation (in pypager) where epoll raised a
+ # PermissionError when a local file descriptor was registered,
+ # however poll and select worked fine. So, in that case, just
+ # try using select below.
+ pass
- if result is None:
try:
- # First, try the 'select' module. This is the most universal, and
- # powerful enough in our case.
- result = _select(read_fds, timeout)
+ # Prefer 'select.select', if we don't have much file descriptors.
+ # This is more universal.
+ return self._select_selector.select(timeout)
except ValueError:
# When we have more than 1024 open file descriptors, we'll always
# get a "ValueError: filedescriptor out of range in select()" for
- # 'select'. In this case, retry, using 'poll' instead.
- result = _poll(read_fds, timeout)
+ # 'select'. In this case, try, using 'poll' instead.
+ if self._poll_selector is not None:
+ return self._poll_selector.select(timeout)
+ else:
+ raise
- return [fd_map[_fd_to_int(fd)] for fd in result]
+ def close(self):
+ for sel in self._selectors:
+ sel.close()
-def _python3_selectors(read_fds, timeout):
+class Python3Selector(Selector):
"""
Use of the Python3 'selectors' module.
NOTE: Only use on Python 3.5 or newer!
"""
- import selectors # Inline import: Python3 only!
- sel = selectors.DefaultSelector()
+ def __init__(self):
+ assert sys.version_info >= (3, 5)
- for fd in read_fds:
- sel.register(fd, selectors.EVENT_READ, None)
+ import selectors # Inline import: Python3 only!
+ self._sel = selectors.DefaultSelector()
- events = sel.select(timeout=timeout)
- try:
+ def register(self, fd):
+ assert isinstance(fd, int)
+ import selectors # Inline import: Python3 only!
+ self._sel.register(fd, selectors.EVENT_READ, None)
+
+ def unregister(self, fd):
+ assert isinstance(fd, int)
+ self._sel.unregister(fd)
+
+ def select(self, timeout):
+ events = self._sel.select(timeout=timeout)
return [key.fileobj for key, mask in events]
- finally:
- sel.close()
+ def close(self):
+ self._sel.close()
-def _poll(read_fds, timeout):
- """
- Use 'poll', to wait for any of the given `read_fds` to become ready.
- """
- p = select.poll()
- for fd in read_fds:
- p.register(fd, select.POLLIN)
- tuples = p.poll(timeout) # Returns (fd, event) tuples.
- return [t[0] for t in tuples]
+class PollSelector(Selector):
+ def __init__(self):
+ self._poll = select.poll()
+
+ def register(self, fd):
+ assert isinstance(fd, int)
+ self._poll.register(fd, select.POLLIN)
+
+ def unregister(self, fd):
+ assert isinstance(fd, int)
+
+ def select(self, timeout):
+ tuples = self._poll.poll(timeout) # Returns (fd, event) tuples.
+ return [t[0] for t in tuples]
+
+ def close(self):
+ pass # XXX
-def _select(read_fds, timeout):
+class SelectSelector(Selector):
"""
Wrapper around select.select.
When the SIGWINCH signal is handled, other system calls, like select
are aborted in Python. This wrapper will retry the system call.
"""
- while True:
- try:
- return select.select(read_fds, [], [], timeout)[0]
- except select.error as e:
- # Retry select call when EINTR
- if e.args and e.args[0] == errno.EINTR:
- continue
- else:
- raise
+ def __init__(self):
+ self._fds = []
+
+ def register(self, fd):
+ self._fds.append(fd)
+
+ def unregister(self, fd):
+ self._fds.remove(fd)
+
+ def select(self, timeout):
+ while True:
+ try:
+ return select.select(self._fds, [], [], timeout)[0]
+ except select.error as e:
+ # Retry select call when EINTR
+ if e.args and e.args[0] == errno.EINTR:
+ continue
+ else:
+ raise
+
+ def close(self):
+ pass
+
+
+def select_fds(read_fds, timeout, selector=AutoSelector):
+ """
+ Wait for a list of file descriptors (`read_fds`) to become ready for
+ reading. This chooses the most appropriate select-tool for use in
+ prompt-toolkit.
+ """
+ # Map to ensure that we return the objects that were passed in originally.
+ # Whether they are a fd integer or an object that has a fileno().
+ # (The 'poll' implementation for instance, returns always integers.)
+ fd_map = dict((fd_to_int(fd), fd) for fd in read_fds)
+
+ # Wait, using selector.
+ sel = selector()
+ try:
+ for fd in read_fds:
+ sel.register(fd)
+
+ result = sel.select(timeout)
+
+ if result is not None:
+ return [fd_map[fd_to_int(fd)] for fd in result]
+ finally:
+ sel.close()
diff --git a/prompt_toolkit/filters/cli.py b/prompt_toolkit/filters/cli.py
index f4486be..c0b0731 100644
--- a/prompt_toolkit/filters/cli.py
+++ b/prompt_toolkit/filters/cli.py
@@ -27,6 +27,7 @@ __all__ = (
'ViMode',
'ViNavigationMode',
'ViInsertMode',
+ 'ViInsertMultipleMode',
'ViReplaceMode',
'ViSelectionMode',
'ViWaitingForTextObjectMode',
@@ -294,6 +295,22 @@ class ViInsertMode(Filter):
@memoized()
+class ViInsertMultipleMode(Filter):
+ def __call__(self, cli):
+ if (cli.editing_mode != EditingMode.VI
+ or cli.vi_state.operator_func
+ or cli.vi_state.waiting_for_digraph
+ or cli.current_buffer.selection_state
+ or cli.current_buffer.read_only()):
+ return False
+
+ return cli.vi_state.input_mode == ViInputMode.INSERT_MULTIPLE
+
+ def __repr__(self):
+ return 'ViInsertMultipleMode()'
+
+
+ at memoized()
class ViReplaceMode(Filter):
def __call__(self, cli):
if (cli.editing_mode != EditingMode.VI
diff --git a/prompt_toolkit/interface.py b/prompt_toolkit/interface.py
index c91f383..f1a7b62 100644
--- a/prompt_toolkit/interface.py
+++ b/prompt_toolkit/interface.py
@@ -3,7 +3,6 @@ The main `CommandLineInterface` class and logic.
"""
from __future__ import unicode_literals
-import datetime
import functools
import os
import signal
@@ -11,6 +10,7 @@ import six
import sys
import textwrap
import threading
+import time
import types
import weakref
@@ -326,11 +326,16 @@ class CommandLineInterface(object):
self._redraw()
# Call redraw in the eventloop (thread safe).
- # Give it low priority. If there is other I/O or CPU intensive
- # stuff to handle, give that priority, but max postpone x seconds.
- _max_postpone_until = datetime.datetime.now() + datetime.timedelta(
- seconds=self.max_render_postpone_time)
- self.eventloop.call_from_executor(redraw, _max_postpone_until=_max_postpone_until)
+ # Usually with the high priority, in order to make the application
+ # feel responsive, but this can be tuned by changing the value of
+ # `max_render_postpone_time`.
+ if self.max_render_postpone_time:
+ _max_postpone_until = time.time() + self.max_render_postpone_time
+ else:
+ _max_postpone_until = None
+
+ self.eventloop.call_from_executor(
+ redraw, _max_postpone_until=_max_postpone_until)
# Depracated alias for 'invalidate'.
request_redraw = invalidate
diff --git a/prompt_toolkit/key_binding/bindings/basic.py b/prompt_toolkit/key_binding/bindings/basic.py
index cd8cb94..2d9de77 100644
--- a/prompt_toolkit/key_binding/bindings/basic.py
+++ b/prompt_toolkit/key_binding/bindings/basic.py
@@ -89,6 +89,10 @@ def load_basic_bindings(registry, filter=Always()):
@handle(Keys.Down)
@handle(Keys.Right)
@handle(Keys.Left)
+ @handle(Keys.ShiftUp)
+ @handle(Keys.ShiftDown)
+ @handle(Keys.ShiftRight)
+ @handle(Keys.ShiftLeft)
@handle(Keys.Home)
@handle(Keys.End)
@handle(Keys.Delete)
diff --git a/prompt_toolkit/key_binding/bindings/vi.py b/prompt_toolkit/key_binding/bindings/vi.py
index c77efaa..6185573 100644
--- a/prompt_toolkit/key_binding/bindings/vi.py
+++ b/prompt_toolkit/key_binding/bindings/vi.py
@@ -4,7 +4,7 @@ from prompt_toolkit.buffer import ClipboardData, indent, unindent, reshape_text
from prompt_toolkit.document import Document
from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYSTEM_BUFFER
from prompt_toolkit.filters import Filter, Condition, HasArg, Always, to_cli_filter, IsReadOnly
-from prompt_toolkit.filters.cli import ViNavigationMode, ViInsertMode, ViReplaceMode, ViSelectionMode, ViWaitingForTextObjectMode, ViDigraphMode, ViMode
+from prompt_toolkit.filters.cli import ViNavigationMode, ViInsertMode, ViInsertMultipleMode, ViReplaceMode, ViSelectionMode, ViWaitingForTextObjectMode, ViDigraphMode, ViMode
from prompt_toolkit.key_binding.digraphs import DIGRAPHS
from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode
from prompt_toolkit.keys import Keys
@@ -20,6 +20,16 @@ import codecs
import six
import string
+try:
+ from itertools import accumulate
+except ImportError: # < Python 3.2
+ def accumulate(iterable):
+ " Super simpel 'accumulate' implementation. "
+ total = 0
+ for item in iterable:
+ total += item
+ yield total
+
__all__ = (
'load_vi_bindings',
'load_vi_search_bindings',
@@ -161,6 +171,7 @@ def load_vi_bindings(registry, enable_visual_key=Always(),
# ViState says different.)
navigation_mode = ViNavigationMode()
insert_mode = ViInsertMode()
+ insert_multiple_mode = ViInsertMultipleMode()
replace_mode = ViReplaceMode()
selection_mode = ViSelectionMode()
operator_given = ViWaitingForTextObjectMode()
@@ -408,6 +419,41 @@ def load_vi_bindings(registry, enable_visual_key=Always(),
event.current_buffer.cursor_position += \
event.current_buffer.document.get_start_of_line_position(after_whitespace=True)
+ @Condition
+ def in_block_selection(cli):
+ buff = cli.current_buffer
+ return buff.selection_state and buff.selection_state.type == SelectionType.BLOCK
+
+ @handle('I', filter=in_block_selection & ~IsReadOnly())
+ def go_to_block_selection(event, after=False):
+ " Insert in block selection mode. "
+ buff = event.current_buffer
+
+ # Store all cursor positions.
+ positions = []
+
+ if after:
+ def get_pos(from_to):
+ return from_to[1] + 1
+ else:
+ def get_pos(from_to):
+ return from_to[0]
+
+ for i, from_to in enumerate(buff.document.selection_ranges()):
+ positions.append(get_pos(from_to))
+ if i == 0:
+ buff.cursor_position = get_pos(from_to)
+
+ buff.multiple_cursor_positions = positions
+
+ # Go to 'INSERT_MULTIPLE' mode.
+ event.cli.vi_state.input_mode = InputMode.INSERT_MULTIPLE
+ buff.exit_selection()
+
+ @handle('A', filter=in_block_selection & ~IsReadOnly())
+ def _(event):
+ go_to_block_selection(event, after=True)
+
@handle('J', filter=navigation_mode & ~IsReadOnly())
def _(event):
" Join lines. "
@@ -797,6 +843,7 @@ def load_vi_bindings(registry, enable_visual_key=Always(),
"""
filter = kw.pop('filter', Always())
no_move_handler = kw.pop('no_move_handler', False)
+ no_selection_handler = kw.pop('no_selection_handler', False)
assert not kw
def decorator(text_object_func):
@@ -828,30 +875,32 @@ def load_vi_bindings(registry, enable_visual_key=Always(),
text_object = text_object_func(event)
event.current_buffer.cursor_position += text_object.start
- @handle(*keys, filter=~operator_given & filter & selection_mode)
- def _(event):
- " Move handler for selection mode. "
- text_object = text_object_func(event)
- buff = event.current_buffer
-
- # When the text object has both a start and end position, like 'i(' or 'iw',
- # Turn this into a selection, otherwise the cursor.
- if text_object.end:
- # Take selection positions from text object.
- start, end = text_object.operator_range(buff.document)
- start += buff.cursor_position
- end += buff.cursor_position
-
- buff.selection_state.original_cursor_position = start
- buff.cursor_position = end
-
- # Take selection type from text object.
- if text_object.type == TextObjectType.LINEWISE:
- buff.selection_state.type = SelectionType.LINES
+ # Register a move selection operation.
+ if not no_selection_handler:
+ @handle(*keys, filter=~operator_given & filter & selection_mode)
+ def _(event):
+ " Move handler for selection mode. "
+ text_object = text_object_func(event)
+ buff = event.current_buffer
+
+ # When the text object has both a start and end position, like 'i(' or 'iw',
+ # Turn this into a selection, otherwise the cursor.
+ if text_object.end:
+ # Take selection positions from text object.
+ start, end = text_object.operator_range(buff.document)
+ start += buff.cursor_position
+ end += buff.cursor_position
+
+ buff.selection_state.original_cursor_position = start
+ buff.cursor_position = end
+
+ # Take selection type from text object.
+ if text_object.type == TextObjectType.LINEWISE:
+ buff.selection_state.type = SelectionType.LINES
+ else:
+ buff.selection_state.type = SelectionType.CHARACTERS
else:
- buff.selection_state.type = SelectionType.CHARACTERS
- else:
- event.current_buffer.cursor_position += text_object.start
+ event.current_buffer.cursor_position += text_object.start
# Make it possible to chain @text_object decorators.
return text_object_func
@@ -1203,15 +1252,20 @@ def load_vi_bindings(registry, enable_visual_key=Always(),
""" Implements 'ch', 'dh', 'h': Cursor left. """
return TextObject(event.current_buffer.document.get_cursor_left_position(count=event.arg))
- @text_object('j', no_move_handler=True)
+ @text_object('j', no_move_handler=True, no_selection_handler=True)
+ # Note: We also need `no_selection_handler`, because we in
+ # selection mode, we prefer the other 'j' binding that keeps
+ # `buffer.preferred_column`.
def _(event):
""" Implements 'cj', 'dj', 'j', ... Cursor up. """
- return TextObject(event.current_buffer.document.get_cursor_down_position(count=event.arg), type=TextObjectType.LINEWISE)
+ return TextObject(event.current_buffer.document.get_cursor_down_position(count=event.arg),
+ type=TextObjectType.LINEWISE)
- @text_object('k', no_move_handler=True)
+ @text_object('k', no_move_handler=True, no_selection_handler=True)
def _(event):
""" Implements 'ck', 'dk', 'k', ... Cursor up. """
- return TextObject(event.current_buffer.document.get_cursor_up_position(count=event.arg), type=TextObjectType.LINEWISE)
+ return TextObject(event.current_buffer.document.get_cursor_up_position(count=event.arg),
+ type=TextObjectType.LINEWISE)
@text_object('l')
@text_object(' ')
@@ -1464,6 +1518,103 @@ def load_vi_bindings(registry, enable_visual_key=Always(),
"""
event.current_buffer.insert_text(event.data, overwrite=True)
+ @handle(Keys.Any, filter=insert_multiple_mode,
+ save_before=(lambda e: not e.is_repeat))
+ def _(event):
+ """
+ Insert data at multiple cursor positions at once.
+ (Usually a result of pressing 'I' or 'A' in block-selection mode.)
+ """
+ buff = event.current_buffer
+ original_text = buff.text
+
+ # Construct new text.
+ text = []
+ p = 0
+
+ for p2 in buff.multiple_cursor_positions:
+ text.append(original_text[p:p2])
+ text.append(event.data)
+ p = p2
+
+ text.append(original_text[p:])
+
+ # Shift all cursor positions.
+ new_cursor_positions = [
+ p + i + 1 for i, p in enumerate(buff.multiple_cursor_positions)]
+
+ # Set result.
+ buff.text = ''.join(text)
+ buff.multiple_cursor_positions = new_cursor_positions
+ buff.cursor_position += 1
+
+ @handle(Keys.Backspace, filter=insert_multiple_mode)
+ def _(event):
+ " Backspace, using multiple cursors. "
+ buff = event.current_buffer
+ original_text = buff.text
+
+ # Construct new text.
+ deleted_something = False
+ text = []
+ p = 0
+
+ for p2 in buff.multiple_cursor_positions:
+ if p2 > 0 and original_text[p2 - 1] != '\n': # Don't delete across lines.
+ text.append(original_text[p:p2 - 1])
+ deleted_something = True
+ else:
+ text.append(original_text[p:p2])
+ p = p2
+
+ text.append(original_text[p:])
+
+ if deleted_something:
+ # Shift all cursor positions.
+ lengths = [len(part) for part in text[:-1]]
+ new_cursor_positions = list(accumulate(lengths))
+
+ # Set result.
+ buff.text = ''.join(text)
+ buff.multiple_cursor_positions = new_cursor_positions
+ buff.cursor_position -= 1
+ else:
+ event.cli.output.bell()
+
+ @handle(Keys.Delete, filter=insert_multiple_mode)
+ def _(event):
+ " Delete, using multiple cursors. "
+ buff = event.current_buffer
+ original_text = buff.text
+
+ # Construct new text.
+ deleted_something = False
+ text = []
+ new_cursor_positions = []
+ p = 0
+
+ for p2 in buff.multiple_cursor_positions:
+ text.append(original_text[p:p2])
+ if original_text[p2] == '\n': # Don't delete across lines.
+ p = p2
+ else:
+ p = p2 + 1
+ deleted_something = True
+
+ text.append(original_text[p:])
+
+ if deleted_something:
+ # Shift all cursor positions.
+ lengths = [len(part) for part in text[:-1]]
+ new_cursor_positions = list(accumulate(lengths))
+
+ # Set result.
+ buff.text = ''.join(text)
+ buff.multiple_cursor_positions = new_cursor_positions
+ else:
+ event.cli.output.bell()
+
+
@handle(Keys.ControlX, Keys.ControlL, filter=insert_mode)
def _(event):
"""
diff --git a/prompt_toolkit/key_binding/vi_state.py b/prompt_toolkit/key_binding/vi_state.py
index 5c556a4..92ce3cb 100644
--- a/prompt_toolkit/key_binding/vi_state.py
+++ b/prompt_toolkit/key_binding/vi_state.py
@@ -9,6 +9,7 @@ __all__ = (
class InputMode(object):
INSERT = 'vi-insert'
+ INSERT_MULTIPLE = 'vi-insert-multiple'
NAVIGATION = 'vi-navigation'
REPLACE = 'vi-replace'
diff --git a/prompt_toolkit/keys.py b/prompt_toolkit/keys.py
index 3c6f4d7..d5df9bf 100644
--- a/prompt_toolkit/keys.py
+++ b/prompt_toolkit/keys.py
@@ -61,6 +61,12 @@ class Keys(object):
Down = Key('<Down>')
Right = Key('<Right>')
Left = Key('<Left>')
+
+ ShiftLeft = Key('<ShiftLeft>')
+ ShiftUp = Key('<ShiftUp>')
+ ShiftDown = Key('<ShiftDown>')
... 468 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/prompt-toolkit.git
More information about the Python-modules-commits
mailing list