summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonathan Slenders <jonathan@slenders.be>2016-06-14 23:31:20 +0200
committerJonathan Slenders <jonathan@slenders.be>2016-06-14 23:31:20 +0200
commit64091c8523488e21d78b8314b68fda5373c85407 (patch)
tree97ad90a301b424c7f59ff2d50084d67cb7cc028b
parent67243b9fee3874bca75f42388fae3e9d93b381e7 (diff)
Emulate bracketed paste on Windows.
When the input stream contains multiple key presses among which a newline and at least one other character, consider this a paste event, and handle as bracketed paste on Unix.
-rw-r--r--prompt_toolkit/eventloop/win32.py7
-rw-r--r--prompt_toolkit/shortcuts.py6
-rw-r--r--prompt_toolkit/terminal/win32_input.py82
3 files changed, 77 insertions, 18 deletions
diff --git a/prompt_toolkit/eventloop/win32.py b/prompt_toolkit/eventloop/win32.py
index 24e6bda1..9d4d6429 100644
--- a/prompt_toolkit/eventloop/win32.py
+++ b/prompt_toolkit/eventloop/win32.py
@@ -29,12 +29,15 @@ INPUT_TIMEOUT_MS = int(1000 * INPUT_TIMEOUT)
class Win32EventLoop(EventLoop):
"""
Event loop for Windows systems.
+
+ :param recognize_paste: When True, try to discover paste actions and turn
+ the event into a BracketedPaste.
"""
- def __init__(self, inputhook=None):
+ def __init__(self, inputhook=None, recognize_paste=True):
assert inputhook is None or callable(inputhook)
self._event = _create_event()
- self._console_input_reader = ConsoleInputReader()
+ self._console_input_reader = ConsoleInputReader(recognize_paste=recognize_paste)
self._calls_from_executor = []
self.closed = False
diff --git a/prompt_toolkit/shortcuts.py b/prompt_toolkit/shortcuts.py
index 76f4e3a0..a47be842 100644
--- a/prompt_toolkit/shortcuts.py
+++ b/prompt_toolkit/shortcuts.py
@@ -82,7 +82,7 @@ __all__ = (
)
-def create_eventloop(inputhook=None):
+def create_eventloop(inputhook=None, recognize_win32_paste=True):
"""
Create and return an
:class:`~prompt_toolkit.eventloop.base.EventLoop` instance for a
@@ -90,10 +90,10 @@ def create_eventloop(inputhook=None):
"""
if is_windows():
from prompt_toolkit.eventloop.win32 import Win32EventLoop as Loop
+ return Loop(inputhook=inputhook, recognize_paste=recognize_win32_paste)
else:
from prompt_toolkit.eventloop.posix import PosixEventLoop as Loop
-
- return Loop(inputhook=inputhook)
+ return Loop(inputhook=inputhook)
def create_output(stdout=None, true_color=False):
diff --git a/prompt_toolkit/terminal/win32_input.py b/prompt_toolkit/terminal/win32_input.py
index 5a3827fa..4aa6edc6 100644
--- a/prompt_toolkit/terminal/win32_input.py
+++ b/prompt_toolkit/terminal/win32_input.py
@@ -11,6 +11,7 @@ from prompt_toolkit.win32_types import EventTypes, KEY_EVENT_RECORD, MOUSE_EVENT
import msvcrt
import os
import sys
+import six
__all__ = (
'ConsoleInputReader',
@@ -20,6 +21,10 @@ __all__ = (
class ConsoleInputReader(object):
+ """
+ :param recognize_paste: When True, try to discover paste actions and turn
+ the event into a BracketedPaste.
+ """
# Keys with character data.
mappings = {
b'\x1b': Keys.Escape,
@@ -99,8 +104,9 @@ class ConsoleInputReader(object):
LEFT_CTRL_PRESSED = 0x0008
RIGHT_CTRL_PRESSED = 0x0004
- def __init__(self):
+ def __init__(self, recognize_paste=True):
self._fdcon = None
+ self.recognize_paste = recognize_paste
# When stdin is a tty, use that handle, otherwise, create a handle from
# CONIN$.
@@ -117,23 +123,51 @@ class ConsoleInputReader(object):
def read(self):
"""
- Read from the Windows console and return a list of `KeyPress` instances.
- It can return an empty list when there was nothing to read. (This
- function doesn't block.)
+ Return a list of `KeyPress` instances. It won't return anything when
+ there was nothing to read. (This function doesn't block.)
http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx
"""
- max_count = 1024 # Read max 1024 events at the same time.
- result = []
+ max_count = 2048 # Max events to read at the same time.
read = DWORD(0)
-
arrtype = INPUT_RECORD * max_count
input_records = arrtype()
# Get next batch of input event.
- windll.kernel32.ReadConsoleInputW(self.handle, pointer(input_records), max_count, pointer(read))
+ windll.kernel32.ReadConsoleInputW(
+ self.handle, pointer(input_records), max_count, pointer(read))
+
+ # First, get all the keys from the input buffer, in order to determine
+ # whether we should consider this a paste event or not.
+ all_keys = list(self._get_keys(read, input_records))
+
+ if self.recognize_paste and self._is_paste(all_keys):
+ gen = iter(all_keys)
+ for k in gen:
+ # Pasting: if the current key consists of text or \n, turn it
+ # into a BracketedPaste.
+ data = []
+ while k and (isinstance(k.key, six.text_type) or
+ k.key == Keys.ControlJ):
+ data.append(k.data)
+ try:
+ k = next(gen)
+ except StopIteration:
+ k = None
+
+ if data:
+ yield KeyPress(Keys.BracketedPaste, ''.join(data))
+ if k is not None:
+ yield k
+ else:
+ for k in all_keys:
+ yield k
+ def _get_keys(self, read, input_records):
+ """
+ Generator that yields `KeyPress` objects from the input records.
+ """
for i in range(read.value):
ir = input_records[i]
@@ -147,14 +181,34 @@ class ConsoleInputReader(object):
# Process if this is a key event. (We also have mouse, menu and
# focus events.)
if type(ev) == KEY_EVENT_RECORD and ev.KeyDown:
- key_presses = self._event_to_key_presses(ev)
- if key_presses:
- result.extend(key_presses)
+ for key_press in self._event_to_key_presses(ev):
+ yield key_press
elif type(ev) == MOUSE_EVENT_RECORD:
- result.extend(self._handle_mouse(ev))
+ for key_press in self._handle_mouse(ev):
+ yield key_press
- return result
+ @staticmethod
+ def _is_paste(keys):
+ """
+ Return `True` when we should consider this list of keys as a paste
+ event. Pasted text on windows will be turned into a
+ `Keys.BracketedPaste` event. (It's not 100% correct, but it is probably
+ the best possible way to detect pasting of text and handle that
+ correctly.)
+ """
+ # Consider paste when it contains at least one newline and at least one
+ # other character.
+ text_count = 0
+ newline_count = 0
+
+ for k in keys:
+ if isinstance(k.key, six.text_type):
+ text_count += 1
+ if k.key == Keys.ControlJ:
+ newline_count += 1
+
+ return newline_count >= 1 and text_count > 1
def _event_to_key_presses(self, ev):
"""
@@ -172,6 +226,8 @@ class ConsoleInputReader(object):
result = KeyPress(self.keycodes[ev.VirtualKeyCode], '')
else:
if ascii_char in self.mappings:
+ if self.mappings[ascii_char] == Keys.ControlJ:
+ u_char = '\n' # Windows sends \n, turn into \r for unix compatibility.
result = KeyPress(self.mappings[ascii_char], u_char)
else:
result = KeyPress(u_char, u_char)