mirror of https://github.com/dbcli/pgcli
Refactoring of the key input system.
- Registry which contains the list of key bindings - Clean up of existing key bindings. - Key binding manager: utility for managing Vi and Emacs bindings - Rename of the `Line` class to `Buffer`. - Added filters.py: for enabling/disabling part of the system at runtime. - Added a focus_stack, a tool for redirecting the input from one buffer to another.
This commit is contained in:
parent
95818c0fbf
commit
0d0a5566a8
|
@ -33,18 +33,18 @@ Architecture
|
|||
| ============ |
|
||||
| - Every key binding consists of a function that |
|
||||
| receives an `Event` and usually it operates on |
|
||||
| the `Line` object. (It could insert data or move |
|
||||
| the cursor for example.) |
|
||||
| the `Buffer` object. (It could insert data or |
|
||||
| move the cursor for example.) |
|
||||
+---------------------------------------------------------------+
|
||||
|
|
||||
| Most of the key bindings operate on a `Line` object, but
|
||||
| Most of the key bindings operate on a `Buffer` object, but
|
||||
| they don't have to. They could also change the visibility
|
||||
| of a menu for instance, or change the color scheme.
|
||||
|
|
||||
v
|
||||
+---------------------------------------------------------------+
|
||||
| Line |
|
||||
| ==== |
|
||||
| Buffer |
|
||||
| ====== |
|
||||
| - Contains a data structure to hold the current |
|
||||
| input (text and cursor position). This class |
|
||||
| implements all text manipulations and cursor |
|
||||
|
@ -55,7 +55,7 @@ Architecture
|
|||
| | Document (text, cursor_position) | |
|
||||
| | ================================ | |
|
||||
| | Accessed as the `document` property of the | |
|
||||
| | `Line` class. This is a wrapper around the | |
|
||||
| | `Buffer` class. This is a wrapper around the | |
|
||||
| | text and cursor position, and contains | |
|
||||
| | methods for querying this data , e.g. to give | |
|
||||
| | the text before the cursor. | |
|
||||
|
|
|
@ -10,7 +10,7 @@ Press [Tab] to complete the current word.
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from prompt_toolkit import CommandLineInterface
|
||||
from prompt_toolkit.line import Line
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.layout.prompt import DefaultPrompt
|
||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||
|
@ -70,7 +70,7 @@ def main():
|
|||
style=AnimalStyle,
|
||||
layout=Layout(before_input=DefaultPrompt('Give some animals: '),
|
||||
menus=[CompletionsMenu()]),
|
||||
line=Line(completer=animal_completer),
|
||||
buffer=Buffer(completer=animal_completer),
|
||||
create_async_autocompleters=True)
|
||||
|
||||
print('Press tab to complete')
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Example of adding a custom key binding to a prompt.
|
||||
"""
|
||||
from prompt_toolkit import CommandLineInterface
|
||||
from prompt_toolkit.key_binding.manager import KeyBindingManager
|
||||
from prompt_toolkit.keys import Keys
|
||||
|
||||
|
||||
def main():
|
||||
# We start with a `KeyBindingManager` instance, because this will already
|
||||
# nicely load all the default key bindings.
|
||||
key_bindings_manager = KeyBindingManager()
|
||||
|
||||
# Add our own key binding to the registry of the key bindings manager.
|
||||
@key_bindings_manager.registry.add_binding(Keys.F4)
|
||||
def _(event):
|
||||
"""
|
||||
When F4 has been pressed. Insert "hello world" as text.
|
||||
"""
|
||||
event.cli.current_buffer.insert_text('hello world')
|
||||
|
||||
@key_bindings_manager.registry.add_binding('x', 'y')
|
||||
def _(event):
|
||||
"""
|
||||
(Useless, but for demoing.)
|
||||
Typing 'xy' will insert 'z'.
|
||||
"""
|
||||
event.cli.current_buffer.insert_text('z')
|
||||
|
||||
# Create a CLI with the key bindings registry of this manager.
|
||||
cli = CommandLineInterface(key_bindings_registry=key_bindings_manager.registry)
|
||||
|
||||
# Read input.
|
||||
print('Press F4 to insert "hello world", type "xy" to insert "z":')
|
||||
code_obj = cli.read_input()
|
||||
print('You said: ' + code_obj.text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -9,7 +9,7 @@ from prompt_toolkit.layout import Layout
|
|||
from prompt_toolkit.layout.prompt import DefaultPrompt
|
||||
from prompt_toolkit.layout.toolbars import ValidationToolbar
|
||||
from prompt_toolkit.validation import Validator, ValidationError
|
||||
from prompt_toolkit.line import Line
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
|
||||
from pygments.token import Token
|
||||
from pygments.style import Style
|
||||
|
@ -27,10 +27,6 @@ class EmailValidator(Validator):
|
|||
raise ValidationError(message='Not a valid e-mail address')
|
||||
|
||||
|
||||
class TestLine(Line):
|
||||
is_multiline = True
|
||||
|
||||
|
||||
class TestStyle(Style):
|
||||
styles = {
|
||||
Token.Toolbar.Validation: 'bg:#aa0000 #ffbbbb',
|
||||
|
@ -38,7 +34,10 @@ class TestStyle(Style):
|
|||
|
||||
|
||||
def main():
|
||||
cli = CommandLineInterface(layout=layout, style=TestStyle, line=Line(validator=EmailValidator()))
|
||||
cli = CommandLineInterface(
|
||||
layout=layout,
|
||||
style=TestStyle,
|
||||
buffer=Buffer(validator=EmailValidator()))
|
||||
|
||||
document = cli.read_input()
|
||||
print('You said: ' + document.text)
|
||||
|
|
|
@ -5,13 +5,14 @@ Simple example of the layout options.
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from prompt_toolkit import CommandLineInterface
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.completion import Completion, Completer
|
||||
from prompt_toolkit.document import Document
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.layout.prompt import DefaultPrompt, Prompt
|
||||
from prompt_toolkit.layout.margins import LeftMarginWithLineNumbers
|
||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||
from prompt_toolkit.layout.prompt import DefaultPrompt, Prompt
|
||||
from prompt_toolkit.layout.toolbars import TextToolbar, ArgToolbar, SearchToolbar, CompletionsToolbar
|
||||
from prompt_toolkit.line import Line
|
||||
from prompt_toolkit.completion import Completion, Completer
|
||||
|
||||
from pygments.token import Token
|
||||
from pygments.style import Style
|
||||
|
@ -86,9 +87,9 @@ def main():
|
|||
|
||||
cli = CommandLineInterface(layout=layout,
|
||||
style=TestStyle,
|
||||
line=Line(is_multiline=True, completer=TestCompleter()))
|
||||
buffer=Buffer(is_multiline=True, completer=TestCompleter()))
|
||||
|
||||
code_obj = cli.read_input(initial_value=lipsum)
|
||||
code_obj = cli.read_input(initial_document=Document(lipsum))
|
||||
print('You said: ' + code_obj.text)
|
||||
|
||||
|
||||
|
|
|
@ -4,12 +4,13 @@ Simple example of a CLI that keeps a persistent history of all the entered
|
|||
strings in a file.
|
||||
"""
|
||||
from prompt_toolkit import CommandLineInterface, AbortAction, Exit
|
||||
from prompt_toolkit.line import Line
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.history import FileHistory
|
||||
|
||||
|
||||
def main():
|
||||
cli = CommandLineInterface(line=Line(history=FileHistory('.example-history-file')))
|
||||
cli = CommandLineInterface(
|
||||
buffer=Buffer(history=FileHistory('.example-history-file')))
|
||||
|
||||
try:
|
||||
while True:
|
||||
|
|
|
@ -21,10 +21,10 @@ from prompt_toolkit.contrib.regular_languages.compiler import compile
|
|||
from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter
|
||||
from prompt_toolkit.contrib.regular_languages.lexer import GrammarLexer
|
||||
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||
from prompt_toolkit.layout.prompt import DefaultPrompt
|
||||
from prompt_toolkit.line import Line
|
||||
|
||||
from pygments.style import Style
|
||||
from pygments.token import Token
|
||||
|
@ -74,7 +74,7 @@ if __name__ == '__main__':
|
|||
'var2': Token.Number
|
||||
}),
|
||||
menus=[CompletionsMenu()]),
|
||||
line=Line(completer=GrammarCompleter(g, {
|
||||
buffer=Buffer(completer=GrammarCompleter(g, {
|
||||
'operator1': WordCompleter(operators1),
|
||||
'operator2': WordCompleter(operators2),
|
||||
})),
|
||||
|
|
|
@ -104,14 +104,14 @@ Let's get started!
|
|||
populate the completion menu with possible candidates from the list
|
||||
of ``keywords``.
|
||||
|
||||
This ``SqlCompleter`` class will be passed into the ``prompt_toolkit.Line`` class
|
||||
This ``SqlCompleter`` class will be passed into the ``prompt_toolkit.Buffer`` class
|
||||
which controls the cusor position and completion of a line.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from prompt_toolkit import CommandLineInterface, AbortAction, Exit
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.line import Line
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.layout.prompt import DefaultPrompt
|
||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||
from prompt_toolkit.completion import Completion, Completer
|
||||
|
@ -131,7 +131,7 @@ Let's get started!
|
|||
def main():
|
||||
layout = Layout(before_input=DefaultPrompt('> '),
|
||||
lexer=SqlLexer, menus=[CompletionsMenu()])
|
||||
line = Line(completer=SqlCompleter())
|
||||
buffer = Buffer(completer=SqlCompleter())
|
||||
cli = CommandLineInterface(layout=layout, line=line)
|
||||
try:
|
||||
while True:
|
||||
|
@ -159,7 +159,7 @@ Let's get started!
|
|||
|
||||
from prompt_toolkit import CommandLineInterface, AbortAction, Exit
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.line import Line
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.layout.prompt import DefaultPrompt
|
||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||
from prompt_toolkit.completion import Completion, Completer
|
||||
|
@ -191,8 +191,8 @@ Let's get started!
|
|||
def main():
|
||||
layout = Layout(before_input=DefaultPrompt('> '),
|
||||
lexer=SqlLexer, menus=[CompletionsMenu()])
|
||||
line = Line(completer=SqlCompleter())
|
||||
cli = CommandLineInterface(style=DocumentStyle, layout=layout, line=line)
|
||||
buffer = Buffer(completer=SqlCompleter())
|
||||
cli = CommandLineInterface(style=DocumentStyle, layout=layout, buffer=buffer)
|
||||
try:
|
||||
while True:
|
||||
document = cli.read_input(on_exit=AbortAction.RAISE_EXCEPTION)
|
||||
|
@ -223,7 +223,7 @@ Let's get started!
|
|||
|
||||
from prompt_toolkit import CommandLineInterface, AbortAction, Exit
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.line import Line
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.layout.prompt import DefaultPrompt
|
||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||
from prompt_toolkit.completion import Completion, Completer
|
||||
|
@ -256,8 +256,8 @@ Let's get started!
|
|||
connection = sqlite3.connect(database)
|
||||
layout = Layout(before_input=DefaultPrompt('> '),
|
||||
lexer=SqlLexer, menus=[CompletionsMenu()])
|
||||
line = Line(completer=SqlCompleter())
|
||||
cli = CommandLineInterface(style=DocumentStyle, layout=layout, line=line)
|
||||
buffer = Buffer(completer=SqlCompleter())
|
||||
cli = CommandLineInterface(style=DocumentStyle, layout=layout, buffer=buffer)
|
||||
try:
|
||||
while True:
|
||||
document = cli.read_input(on_exit=AbortAction.RAISE_EXCEPTION)
|
||||
|
|
|
@ -4,7 +4,7 @@ import sqlite3
|
|||
|
||||
from prompt_toolkit import CommandLineInterface, AbortAction, Exit
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.line import Line
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.layout.prompt import DefaultPrompt
|
||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||
from prompt_toolkit.completion import Completion, Completer
|
||||
|
@ -41,8 +41,8 @@ def main(database):
|
|||
connection = sqlite3.connect(database)
|
||||
layout = Layout(before_input=DefaultPrompt('> '),
|
||||
lexer=SqlLexer, menus=[CompletionsMenu()])
|
||||
line = Line(completer=SqlCompleter())
|
||||
cli = CommandLineInterface(style=DocumentStyle, layout=layout, line=line)
|
||||
buffer = Buffer(completer=SqlCompleter())
|
||||
cli = CommandLineInterface(style=DocumentStyle, layout=layout, buffer=buffer)
|
||||
try:
|
||||
while True:
|
||||
document = cli.read_input(on_exit=AbortAction.RAISE_EXCEPTION)
|
||||
|
|
|
@ -12,14 +12,14 @@ import sys
|
|||
import weakref
|
||||
|
||||
from .completion import CompleteEvent
|
||||
from .enums import InputMode
|
||||
from .focus_stack import FocusStack
|
||||
from .history import History
|
||||
from .key_binding import InputProcessor
|
||||
from .key_binding import Registry
|
||||
from .key_bindings.emacs import emacs_bindings
|
||||
from .key_binding.input_processor import InputProcessor
|
||||
from .key_binding.registry import Registry
|
||||
from .key_binding.bindings.emacs import load_emacs_bindings
|
||||
from .layout import Layout
|
||||
from .layout.prompt import DefaultPrompt
|
||||
from .line import Line
|
||||
from .buffer import Buffer
|
||||
from .renderer import Renderer
|
||||
from .utils import EventHook, DummyContext
|
||||
|
||||
|
@ -81,48 +81,20 @@ class CommandLineInterface(object):
|
|||
"""
|
||||
def __init__(self, stdin=None, stdout=None,
|
||||
layout=None,
|
||||
line=None,
|
||||
buffer=None,
|
||||
buffers=None,
|
||||
style=DefaultStyle,
|
||||
key_binding_factories=None,
|
||||
key_bindings_registry=None,
|
||||
create_async_autocompleters=True,
|
||||
renderer_factory=Renderer):
|
||||
|
||||
assert buffer is None or isinstance(buffer, Buffer)
|
||||
assert buffers is None or isinstance(buffers, dict)
|
||||
|
||||
self.stdin = stdin or sys.__stdin__
|
||||
self.stdout = stdout or sys.__stdout__
|
||||
self.style = style
|
||||
|
||||
#: The `Line` instance.
|
||||
line = line or Line()
|
||||
self.lines = {
|
||||
'default': line,
|
||||
'search': Line(history=History()),
|
||||
'system': Line(history=History()),
|
||||
}
|
||||
|
||||
#: The `Layout` instance.
|
||||
self.layout = layout or Layout(before_input=DefaultPrompt())
|
||||
|
||||
#: The `Renderer` instance.
|
||||
self.renderer = renderer_factory(layout=self.layout,
|
||||
stdout=self.stdout,
|
||||
style=self.style)
|
||||
|
||||
key_binding_factories = key_binding_factories or [emacs_bindings]
|
||||
|
||||
#: The `InputProcessor` instance.
|
||||
self.input_processor = self._create_input_processor(key_binding_factories)
|
||||
|
||||
# Handle events.
|
||||
if create_async_autocompleters:
|
||||
for l in self.lines.values():
|
||||
if l.completer:
|
||||
l.onTextInsert += self._create_async_completer(l)
|
||||
|
||||
self._reset()
|
||||
|
||||
# Event loop.
|
||||
self.eventloop = None
|
||||
|
||||
# Events
|
||||
|
||||
#: Called when there is no input for x seconds.
|
||||
|
@ -131,27 +103,55 @@ class CommandLineInterface(object):
|
|||
|
||||
self.onReadInputStart = EventHook()
|
||||
self.onReadInputEnd = EventHook()
|
||||
self.onReset = EventHook()
|
||||
|
||||
# Focus stack.
|
||||
self.focus_stack = FocusStack(initial='default')
|
||||
|
||||
#: The input buffers.
|
||||
self.buffers = {
|
||||
'default': (buffer or Buffer()),
|
||||
'search': Buffer(history=History()),
|
||||
'system': Buffer(history=History()),
|
||||
}
|
||||
if buffers:
|
||||
self.buffers.update(buffers)
|
||||
|
||||
#: The `Layout` instance.
|
||||
self.layout = layout or Layout(before_input=DefaultPrompt())
|
||||
|
||||
#: The `Renderer` instance.
|
||||
self.renderer = renderer_factory(layout=self.layout,
|
||||
stdout=self.stdout,
|
||||
style=self.style)
|
||||
|
||||
if key_bindings_registry is None:
|
||||
key_bindings_registry = Registry()
|
||||
load_emacs_bindings(key_bindings_registry)
|
||||
|
||||
#: The `InputProcessor` instance.
|
||||
self.input_processor = InputProcessor(key_bindings_registry, weakref.ref(self))
|
||||
|
||||
# Handle events.
|
||||
if create_async_autocompleters:
|
||||
for b in self.buffers.values():
|
||||
if b.completer:
|
||||
b.onTextInsert += self._create_async_completer(b)
|
||||
|
||||
self._reset()
|
||||
|
||||
# Event loop.
|
||||
self.eventloop = None
|
||||
|
||||
@property
|
||||
def is_reading_input(self):
|
||||
return bool(self.eventloop)
|
||||
|
||||
@property
|
||||
def line(self):
|
||||
return self.lines['default']
|
||||
def current_buffer(self):
|
||||
return self.buffers[self.focus_stack.current]
|
||||
|
||||
def _create_input_processor(self, key_binding_factories):
|
||||
"""
|
||||
Create :class:`InputProcessor` instance.
|
||||
"""
|
||||
key_registry = Registry()
|
||||
for kb in key_binding_factories:
|
||||
kb(key_registry, weakref.ref(self))
|
||||
|
||||
#: The `InputProcessor` instance.
|
||||
return InputProcessor(key_registry)
|
||||
|
||||
def _reset(self, initial_document=None, initial_input_mode=InputMode.INSERT):
|
||||
def _reset(self, initial_document=None):
|
||||
"""
|
||||
Reset everything.
|
||||
"""
|
||||
|
@ -159,14 +159,18 @@ class CommandLineInterface(object):
|
|||
self._abort_flag = False
|
||||
self._return_value = None
|
||||
|
||||
for l in self.lines.values():
|
||||
l.reset()
|
||||
for b in self.buffers.values():
|
||||
b.reset()
|
||||
|
||||
self.line.reset(initial_document=initial_document)
|
||||
self.focus_stack.reset()
|
||||
self.current_buffer.reset(initial_document=initial_document)
|
||||
self.renderer.reset()
|
||||
self.input_processor.reset(initial_input_mode=initial_input_mode)
|
||||
self.input_processor.reset()
|
||||
self.layout.reset()
|
||||
|
||||
# Trigger reset event.
|
||||
self.onReset.fire()
|
||||
|
||||
def request_redraw(self):
|
||||
"""
|
||||
Thread safe way of sending a repaint trigger to the input event loop.
|
||||
|
@ -220,7 +224,7 @@ class CommandLineInterface(object):
|
|||
self.renderer.request_absolute_cursor_position()
|
||||
self.call_from_executor(do_in_event_loop)
|
||||
|
||||
def read_input(self, initial_document=None, initial_input_mode=InputMode.INSERT,
|
||||
def read_input(self, initial_document=None,
|
||||
on_abort=AbortAction.RETRY, on_exit=AbortAction.IGNORE):
|
||||
"""
|
||||
Read input string from command line.
|
||||
|
@ -231,7 +235,6 @@ class CommandLineInterface(object):
|
|||
"""
|
||||
eventloop = EventLoop(self.input_processor, self.stdin)
|
||||
g = self._read_input(initial_document=initial_document,
|
||||
initial_input_mode=initial_input_mode,
|
||||
on_abort=on_abort,
|
||||
on_exit=on_exit,
|
||||
eventloop=eventloop,
|
||||
|
@ -244,7 +247,7 @@ class CommandLineInterface(object):
|
|||
except StopIteration as e:
|
||||
return e.args[0]
|
||||
|
||||
def read_input_async(self, initial_document=None, initial_input_mode=InputMode.INSERT,
|
||||
def read_input_async(self, initial_document=None,
|
||||
on_abort=AbortAction.RETRY, on_exit=AbortAction.IGNORE):
|
||||
"""
|
||||
Same as `read_input`, but this returns an asyncio coroutine.
|
||||
|
@ -259,13 +262,12 @@ class CommandLineInterface(object):
|
|||
|
||||
eventloop = AsyncioEventLoop(self.input_processor, self.stdin)
|
||||
return self._read_input(initial_document=initial_document,
|
||||
initial_input_mode=initial_input_mode,
|
||||
on_abort=on_abort,
|
||||
on_exit=on_exit,
|
||||
eventloop=eventloop,
|
||||
return_corountine=True)
|
||||
|
||||
def _read_input(self, initial_document, initial_input_mode, on_abort, on_exit,
|
||||
def _read_input(self, initial_document, on_abort, on_exit,
|
||||
eventloop, return_corountine):
|
||||
"""
|
||||
The implementation of ``read_input`` which can be called both
|
||||
|
@ -283,8 +285,7 @@ class CommandLineInterface(object):
|
|||
|
||||
try:
|
||||
def reset():
|
||||
self._reset(initial_document=initial_document,
|
||||
initial_input_mode=initial_input_mode)
|
||||
self._reset(initial_document=initial_document)
|
||||
reset()
|
||||
|
||||
# Trigger onReadInputStart event.
|
||||
|
@ -435,7 +436,7 @@ class CommandLineInterface(object):
|
|||
"""
|
||||
return self._return_value is not None
|
||||
|
||||
def _create_async_completer(self, line):
|
||||
def _create_async_completer(self, buffer):
|
||||
"""
|
||||
Create function for asynchronous autocompletion while typing.
|
||||
(Autocomplete in other thread.)
|
||||
|
@ -443,25 +444,25 @@ class CommandLineInterface(object):
|
|||
complete_thread_running = [False] # By ref.
|
||||
|
||||
def async_completer():
|
||||
document = line.document
|
||||
document = buffer.document
|
||||
|
||||
# Don't start two threads at the same time.
|
||||
if complete_thread_running[0]:
|
||||
return
|
||||
|
||||
# Don't complete when we already have completions.
|
||||
if line.complete_state:
|
||||
if buffer.complete_state:
|
||||
return
|
||||
|
||||
# Don't automatically complete on empty inputs.
|
||||
if not line.text:
|
||||
if not buffer.text:
|
||||
return
|
||||
|
||||
# Otherwise, get completions in other thread.
|
||||
complete_thread_running[0] = True
|
||||
|
||||
def run():
|
||||
completions = list(line.completer.get_completions(
|
||||
completions = list(buffer.completer.get_completions(
|
||||
document,
|
||||
CompleteEvent(text_inserted=True)))
|
||||
complete_thread_running[0] = False
|
||||
|
@ -474,10 +475,10 @@ class CommandLineInterface(object):
|
|||
was changed in the meantime.
|
||||
"""
|
||||
# Set completions if the text was not yet changed.
|
||||
if line.text == document.text and \
|
||||
line.cursor_position == document.cursor_position and \
|
||||
not line.complete_state:
|
||||
line._start_complete(go_to_first=False, completions=completions)
|
||||
if buffer.text == document.text and \
|
||||
buffer.cursor_position == document.cursor_position and \
|
||||
not buffer.complete_state:
|
||||
buffer._start_complete(go_to_first=False, completions=completions)
|
||||
self._redraw()
|
||||
else:
|
||||
# Otherwise, restart thread.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Data structures for the line input.
|
||||
Data structures for the Buffer.
|
||||
It holds the text, cursor position, history, etc...
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
@ -19,7 +19,7 @@ import tempfile
|
|||
|
||||
__all__ = (
|
||||
'ClipboardData',
|
||||
'Line',
|
||||
'Buffer',
|
||||
'SelectionType',
|
||||
'indent',
|
||||
'unindent',
|
||||
|
@ -104,7 +104,7 @@ class _IncrementalSearchState(object):
|
|||
self.isearch_direction = direction
|
||||
|
||||
|
||||
class Line(object):
|
||||
class Buffer(object):
|
||||
"""
|
||||
The core data structure that holds the text and cursor position of the
|
||||
current input line and implements all text manupulations on top of it. It
|
||||
|
@ -118,17 +118,30 @@ class Line(object):
|
|||
:attr tempfile_suffix: Suffix to be appended to the tempfile for the 'open
|
||||
in editor' function.
|
||||
:attr is_multiline: Boolean to indicate whether we should consider this
|
||||
line a multiline input. If so, the `InputStreamHandler`
|
||||
buffer a multiline input. If so, the `InputStreamHandler`
|
||||
can decide to insert newlines when pressing [Enter].
|
||||
(Instead of accepting the input.)
|
||||
|
||||
This can also be a callable that takes a `Document` and
|
||||
returns either True or False,
|
||||
"""
|
||||
def __init__(self, completer=None, history=None, validator=None, tempfile_suffix='', is_multiline=False):
|
||||
def __init__(self, completer=None, history=None, validator=None, tempfile_suffix='', is_multiline=None):
|
||||
assert is_multiline is None or callable(is_multiline) or isinstance(is_multiline, bool)
|
||||
|
||||
self.completer = completer
|
||||
self.validator = validator
|
||||
self.is_multiline = is_multiline
|
||||
self.tempfile_suffix = tempfile_suffix
|
||||
|
||||
#: The command line history.
|
||||
# Is multiline. (can be dynamic or static.)
|
||||
if is_multiline is not None:
|
||||
if callable(is_multiline):
|
||||
self._is_multiline = is_multiline
|
||||
elif isinstance(is_multiline, bool):
|
||||
self._is_multiline = lambda document: is_multiline
|
||||
else:
|
||||
self._is_multiline = lambda document: False
|
||||
|
||||
#: The command buffer history.
|
||||
# Note that we shouldn't use a lazy 'or' here. bool(history) could be
|
||||
# False when empty.
|
||||
self._history = History() if history is None else history
|
||||
|
@ -144,7 +157,17 @@ class Line(object):
|
|||
|
||||
self.reset()
|
||||
|
||||
def reset(self, initial_document=None):
|
||||
@property
|
||||
def is_multiline(self):
|
||||
return self._is_multiline(self.document)
|
||||
|
||||
def reset(self, initial_document=None, append_to_history=False):
|
||||
"""
|
||||
:param append_to_history: Append current input to history first.
|
||||
"""
|
||||
if append_to_history:
|
||||
self.add_to_history()
|
||||
|
||||
initial_document = initial_document or Document()
|
||||
|
||||
self.cursor_position = initial_document.cursor_position
|
||||
|
@ -196,15 +219,16 @@ class Line(object):
|
|||
def cursor_position(self, value):
|
||||
self.__cursor_position = max(0, value)
|
||||
|
||||
# Remove any validation errors and complete state.
|
||||
self.validation_error = None
|
||||
self.complete_state = None
|
||||
if value != self.__cursor_position:
|
||||
# Remove any validation errors and complete state.
|
||||
self.validation_error = None
|
||||
self.complete_state = None
|
||||
|
||||
# Note that the cursor position can change if we have a selection the
|
||||
# new position of the cursor determines the end of the selection.
|
||||
# Note that the cursor position can change if we have a selection the
|
||||
# new position of the cursor determines the end of the selection.
|
||||
|
||||
# fire 'onCursorPositionChanged' event.
|
||||
self.onCursorPositionChanged.fire()
|
||||
# fire 'onCursorPositionChanged' event.
|
||||
self.onCursorPositionChanged.fire()
|
||||
|
||||
@property
|
||||
def working_index(self):
|
||||
|
@ -849,24 +873,24 @@ class Line(object):
|
|||
pass
|
||||
|
||||
|
||||
def indent(line, from_row, to_row, count=1):
|
||||
def indent(buffer, from_row, to_row, count=1):
|
||||
"""
|
||||
Indent text of the `Line` object.
|
||||
Indent text of the `Buffer` object.
|
||||
"""
|
||||
current_row = line.document.cursor_position_row
|
||||
current_row = buffer.document.cursor_position_row
|
||||
line_range = range(from_row, to_row)
|
||||
|
||||
line.transform_lines(line_range, lambda l: ' ' * count + l)
|
||||
buffer.transform_lines(line_range, lambda l: ' ' * count + l)
|
||||
|
||||
line.cursor_position = line.document.translate_row_col_to_index(current_row, 0)
|
||||
line.cursor_position += line.document.get_start_of_line_position(after_whitespace=True)
|
||||
buffer.cursor_position = buffer.document.translate_row_col_to_index(current_row, 0)
|
||||
buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True)
|
||||
|
||||
|
||||
def unindent(line, from_row, to_row, count=1):
|
||||
def unindent(buffer, from_row, to_row, count=1):
|
||||
"""
|
||||
Unindent text of the `Line` object.
|
||||
Unindent text of the `Buffer` object.
|
||||
"""
|
||||
current_row = line.document.cursor_position_row
|
||||
current_row = buffer.document.cursor_position_row
|
||||
line_range = range(from_row, to_row)
|
||||
|
||||
def transform(text):
|
||||
|
@ -876,7 +900,7 @@ def unindent(line, from_row, to_row, count=1):
|
|||
else:
|
||||
return text.lstrip()
|
||||
|
||||
line.transform_lines(line_range, transform)
|
||||
buffer.transform_lines(line_range, transform)
|
||||
|
||||
line.cursor_position = line.document.translate_row_col_to_index(current_row, 0)
|
||||
line.cursor_position += line.document.get_start_of_line_position(after_whitespace=True)
|
||||
buffer.cursor_position = buffer.document.translate_row_col_to_index(current_row, 0)
|
||||
buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True)
|
|
@ -65,7 +65,7 @@ class Completer(object):
|
|||
"""
|
||||
Base class for Code implementations.
|
||||
|
||||
The methods in here are methods that are expected to exist for the `Line`
|
||||
The methods in here are methods that are expected to exist for the `Buffer`
|
||||
and `Renderer` classes.
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
grammar = """
|
||||
# Ignore leading whitespace.
|
||||
.*
|
||||
|
||||
# The command itself.
|
||||
(?P<command>[a-z-]+)
|
||||
|
||||
# Any number between 0-8 parameters.
|
||||
(\s+ (?P<var1>[^\s]+)
|
||||
(\s+ (?P<var2>[^\s]+)
|
||||
(\s+ (?P<var3>[^\s]+)
|
||||
(\s+ (?P<var4>[^\s]+)
|
||||
(\s+ (?P<var5>[^\s]+)
|
||||
(\s+ (?P<var6>[^\s]+)
|
||||
(\s+ (?P<var7>[^\s]+)
|
||||
(\s+ (?P<var8>[^\s]+)?)
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
# Ignore trailing whitespace.
|
||||
.*
|
||||
"""
|
|
@ -14,25 +14,27 @@ from pygments.token import Token
|
|||
|
||||
from prompt_toolkit import CommandLineInterface, AbortAction, Exit
|
||||
|
||||
from prompt_toolkit.contrib.python_input import PythonCompleter, PythonValidator
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.contrib.python_input import PythonCompleter, PythonValidator, document_is_multiline_python, PythonToolbar, PythonCLISettings, load_python_bindings
|
||||
from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter
|
||||
from prompt_toolkit.contrib.regular_languages.lexer import GrammarLexer
|
||||
from prompt_toolkit.contrib.regular_languages.validation import GrammarValidator
|
||||
from prompt_toolkit.document import Document
|
||||
from prompt_toolkit.history import FileHistory
|
||||
from prompt_toolkit.key_bindings.emacs import emacs_bindings
|
||||
from prompt_toolkit.key_binding.manager import KeyBindingManager
|
||||
from prompt_toolkit.keys import Keys
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||
from prompt_toolkit.layout.toolbars import SystemToolbar, ValidationToolbar, ArgToolbar, SearchToolbar
|
||||
from prompt_toolkit.line import Line
|
||||
|
||||
from .commands import commands_with_help, shortcuts
|
||||
from .completers import PythonFileCompleter, PythonFunctionCompleter, BreakPointListCompleter, AliasCompleter, PdbCommandsCompleter
|
||||
from .completion_hints import CompletionHint
|
||||
from .grammar import create_pdb_grammar
|
||||
from .key_bindings import custom_pdb_key_bindings
|
||||
from .prompt import PdbPrompt
|
||||
from .key_bindings import load_custom_pdb_key_bindings
|
||||
from .layout import PdbLeftMargin
|
||||
from .style import PdbStyle
|
||||
from .toolbars import SourceCodeToolbar, ShortcutsToolbar, PdbStatusToolbar
|
||||
from .toolbars import SourceCodeToolbar, PdbStatusToolbar
|
||||
|
||||
import os
|
||||
import pdb
|
||||
|
@ -55,6 +57,19 @@ class PtPdb(pdb.Pdb):
|
|||
|
||||
self._cli_history = FileHistory(os.path.expanduser('~/.ptpdb_history'))
|
||||
|
||||
self.python_cli_settings = PythonCLISettings()
|
||||
|
||||
# The key bindings manager. We reuse it between Pdb calls, in order to
|
||||
# remember vi/emacs state, etc..)
|
||||
self.key_bindings_manager = self._create_key_bindings_manager(self.python_cli_settings)
|
||||
|
||||
def _create_key_bindings_manager(self, settings):
|
||||
key_bindings_manager = KeyBindingManager()
|
||||
load_custom_pdb_key_bindings(key_bindings_manager.registry)
|
||||
load_python_bindings(key_bindings_manager, settings)
|
||||
|
||||
return key_bindings_manager
|
||||
|
||||
def cmdloop(self, intro=None):
|
||||
"""
|
||||
Copy/Paste of pdb.Pdb.cmdloop. But using our own CommandLineInterface
|
||||
|
@ -109,9 +124,28 @@ class PtPdb(pdb.Pdb):
|
|||
"""
|
||||
g = self._create_grammar()
|
||||
|
||||
# Reset multiline/paste mode every time.
|
||||
self.python_cli_settings.paste_mode = False
|
||||
self.python_cli_settings.currently_multiline = False
|
||||
|
||||
# Make sure not to start in Vi navigation mode.
|
||||
self.key_bindings_manager.reset()
|
||||
|
||||
def is_multiline(document):
|
||||
if (self.python_cli_settings.paste_mode or
|
||||
self.python_cli_settings.currently_multiline):
|
||||
return True
|
||||
|
||||
match = g.match_prefix(document.text)
|
||||
if match:
|
||||
for v in match.variables().getall('python_code'):
|
||||
if document_is_multiline_python(Document(v)):
|
||||
return True
|
||||
return False
|
||||
|
||||
cli = CommandLineInterface(
|
||||
layout=Layout(
|
||||
before_input=PdbPrompt(self._get_current_pdb_commands()),
|
||||
left_margin=PdbLeftMargin(self._get_current_pdb_commands()),
|
||||
show_tildes=True,
|
||||
min_height=15,
|
||||
lexer=GrammarLexer(
|
||||
|
@ -128,10 +162,10 @@ class PtPdb(pdb.Pdb):
|
|||
SearchToolbar(),
|
||||
SourceCodeToolbar(weakref.ref(self)),
|
||||
ValidationToolbar(),
|
||||
ShortcutsToolbar(),
|
||||
PdbStatusToolbar(weakref.ref(self)),
|
||||
PdbStatusToolbar(weakref.ref(self), self.key_bindings_manager),
|
||||
PythonToolbar(self.key_bindings_manager, self.python_cli_settings),
|
||||
]),
|
||||
line=Line(
|
||||
buffer=Buffer(
|
||||
completer=GrammarCompleter(g, completers={
|
||||
'enabled_breakpoint': BreakPointListCompleter(only_enabled=True),
|
||||
'disabled_breakpoint': BreakPointListCompleter(only_disabled=True),
|
||||
|
@ -146,8 +180,9 @@ class PtPdb(pdb.Pdb):
|
|||
validator=GrammarValidator(g, {
|
||||
'python_code': PythonValidator()
|
||||
}),
|
||||
is_multiline=is_multiline,
|
||||
),
|
||||
key_binding_factories=[emacs_bindings, custom_pdb_key_bindings],
|
||||
key_bindings_registry=self.key_bindings_manager.registry,
|
||||
style=PdbStyle)
|
||||
|
||||
try:
|
||||
|
|
|
@ -12,7 +12,7 @@ class CompletionHint(object):
|
|||
screen.write_highlighted(self._tokens(cli))
|
||||
|
||||
def _tokens(self, cli):
|
||||
words = cli.line.document.text.split()
|
||||
words = cli.buffers['default'].document.text.split()
|
||||
if len(words) == 1:
|
||||
word = words[0]
|
||||
|
||||
|
|
|
@ -2,31 +2,26 @@ from __future__ import unicode_literals
|
|||
from prompt_toolkit.keys import Keys
|
||||
|
||||
|
||||
def custom_pdb_key_bindings(registry, cli_ref):
|
||||
def load_custom_pdb_key_bindings(registry):
|
||||
"""
|
||||
Custom key bindings.
|
||||
"""
|
||||
line = cli_ref().line
|
||||
handle = registry.add_binding
|
||||
|
||||
def return_text(text):
|
||||
line.text = text
|
||||
line.cursor_position = len(text)
|
||||
cli_ref().set_return_value(line.document)
|
||||
|
||||
@handle(Keys.F6)
|
||||
def _(event):
|
||||
# TODO: Open REPL
|
||||
pass
|
||||
|
||||
@handle(Keys.F7)
|
||||
def _(event):
|
||||
return_text('step')
|
||||
def return_text(event, text):
|
||||
buffer = event.cli.buffers['default']
|
||||
buffer.text = text
|
||||
buffer.cursor_position = len(text)
|
||||
event.cli.set_return_value(buffer.document)
|
||||
|
||||
@handle(Keys.F8)
|
||||
def _(event):
|
||||
return_text('next')
|
||||
return_text(event, 'step')
|
||||
|
||||
@handle(Keys.F9)
|
||||
def _(event):
|
||||
return_text('continue')
|
||||
return_text(event, 'next')
|
||||
|
||||
@handle(Keys.F10)
|
||||
def _(event):
|
||||
return_text(event, 'continue')
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
from prompt_toolkit.layout.margins import LeftMarginWithLineNumbers
|
||||
from pygments.token import Token
|
||||
|
||||
|
||||
class PdbLeftMargin(LeftMarginWithLineNumbers):
|
||||
"""
|
||||
Pdb prompt.
|
||||
|
||||
Show "(pdb)" when we have a pdb command or '>>>' when the user types a
|
||||
Python command.
|
||||
"""
|
||||
def __init__(self, pdb_commands):
|
||||
super(PdbLeftMargin, self).__init__()
|
||||
self.pdb_commands = pdb_commands
|
||||
|
||||
def width(self, cli):
|
||||
return 6
|
||||
|
||||
def write(self, cli, screen, y, line_number):
|
||||
if y == 0:
|
||||
screen.write_highlighted(self._first_line(cli))
|
||||
else:
|
||||
super(PdbLeftMargin, self).write(cli, screen, y, line_number)
|
||||
|
||||
def _first_line(self, cli):
|
||||
# Get the first word entered.
|
||||
command = cli.buffers['default'].document.text.lstrip()
|
||||
if command:
|
||||
command = command.split()[0]
|
||||
|
||||
if any(c.startswith(command) for c in self.pdb_commands):
|
||||
return [(Token.Prompt, '(pdb) ')]
|
||||
else:
|
||||
return [(Token.Prompt, ' >>> ')]
|
|
@ -1,25 +0,0 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
from prompt_toolkit.layout.prompt import Prompt
|
||||
|
||||
|
||||
class PdbPrompt(Prompt):
|
||||
"""
|
||||
Pdb prompt.
|
||||
|
||||
Show "(pdb)" when we have a pdb command or '>>>' when the user types a
|
||||
Python command.
|
||||
"""
|
||||
def __init__(self, pdb_commands):
|
||||
super(PdbPrompt, self).__init__()
|
||||
self.pdb_commands = pdb_commands
|
||||
|
||||
def tokens(self, cli):
|
||||
# Get the first word entered.
|
||||
command = cli.line.document.text.lstrip()
|
||||
if command:
|
||||
command = command.split()[0]
|
||||
|
||||
if any(c.startswith(command) for c in self.pdb_commands):
|
||||
return [(self.token, '(pdb) ')]
|
||||
else:
|
||||
return [(self.token, ' >>> ')]
|
|
@ -6,14 +6,13 @@ from pygments.token import Token
|
|||
class PdbStyle(PythonStyle):
|
||||
styles = {
|
||||
# Pdb tokens.
|
||||
Token.Prompt.BeforeInput: 'bold #008800',
|
||||
Token.PdbCommand: 'bold',
|
||||
Token.CompletionHint.Symbol: '#9a8888',
|
||||
Token.CompletionHint.Parameter: '#ba4444 bold',
|
||||
Token.Toolbar.Status.Pdb.Filename: 'bg:#222222 #aaaaaa',
|
||||
Token.Toolbar.Status.Pdb.Lineno: 'bg:#222222 #ffffff',
|
||||
|
||||
Token.X.Shortcut.Key: 'bold noinherit',
|
||||
Token.X.Shortcut.Description: 'noinherit',
|
||||
Token.Prompt.BeforeInput: 'bold #008800',
|
||||
Token.PdbCommand: 'bold',
|
||||
Token.CompletionHint.Symbol: '#9a8888',
|
||||
Token.CompletionHint.Parameter: '#ba4444 bold',
|
||||
Token.Toolbar.Status.Pdb.Filename: 'bg:#222222 #aaaaaa',
|
||||
Token.Toolbar.Status.Pdb.Lineno: 'bg:#222222 #ffffff',
|
||||
Token.Toolbar.Status.Pdb.Shortcut.Key: 'bg:#222222 #aaaaaa',
|
||||
Token.Toolbar.Status.Pdb.Shortcut.Description: 'bg:#222222 #aaaaaa',
|
||||
}
|
||||
styles.update(PythonStyle.styles)
|
||||
|
|
|
@ -56,34 +56,13 @@ class SourceCodeToolbar(TextToolbar):
|
|||
return ''.join(result)
|
||||
|
||||
|
||||
class ShortcutsToolbar(Toolbar):
|
||||
"""
|
||||
Display shortcuts.
|
||||
"""
|
||||
def get_tokens(self, cli, width):
|
||||
result = []
|
||||
append = result.append
|
||||
TB = Token.X.Shortcut
|
||||
|
||||
append((TB, ' '))
|
||||
append((TB.Key, '[F6]'))
|
||||
append((TB.Description, ' Open Repl '))
|
||||
append((TB.Key, '[F7]'))
|
||||
append((TB.Description, ' Step '))
|
||||
append((TB.Key, '[F8]'))
|
||||
append((TB.Description, ' Next '))
|
||||
append((TB.Key, '[F9]'))
|
||||
append((TB.Description, ' Continue '))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class PdbStatusToolbar(Toolbar):
|
||||
"""
|
||||
Toolbar which shows the Pdb status. (current line and line number.)
|
||||
"""
|
||||
def __init__(self, pdb_ref, token=None):
|
||||
def __init__(self, pdb_ref, key_bindings_manager, token=None):
|
||||
self._pdb_ref = pdb_ref
|
||||
self.key_bindings_manager = key_bindings_manager
|
||||
token = token or Token.Toolbar.Status
|
||||
super(PdbStatusToolbar, self).__init__(token=token)
|
||||
|
||||
|
@ -93,19 +72,17 @@ class PdbStatusToolbar(Toolbar):
|
|||
TB = self.token
|
||||
pdb = self._pdb_ref()
|
||||
|
||||
# Show current input mode.
|
||||
result.extend(get_inputmode_tokens(self.token, False, cli))
|
||||
# Shortcuts
|
||||
append((TB.Pdb.Shortcut.Key, ' [F8]'))
|
||||
append((TB.Pdb.Shortcut.Description, ' Step '))
|
||||
append((TB.Pdb.Shortcut.Key, '[F9]'))
|
||||
append((TB.Pdb.Shortcut.Description, ' Next '))
|
||||
append((TB.Pdb.Shortcut.Key, '[F10]'))
|
||||
append((TB.Pdb.Shortcut.Description, ' Continue'))
|
||||
|
||||
# Filename and line number.
|
||||
append((TB, ' Break at: '))
|
||||
append((TB, ' | At: '))
|
||||
append((TB.Pdb.Filename, pdb.curframe.f_code.co_filename or 'None'))
|
||||
append((TB, ' '))
|
||||
append((TB.Pdb.Lineno, ': %s' % pdb.curframe.f_lineno))
|
||||
|
||||
# Python version
|
||||
version = sys.version_info
|
||||
append((TB, ' - '))
|
||||
append((TB.PythonVersion, '%s %i.%i.%i' % (platform.python_implementation(),
|
||||
version[0], version[1], version[2])))
|
||||
append((TB.Pdb.Lineno, ':%s' % pdb.curframe.f_lineno))
|
||||
|
||||
return result
|
||||
|
|
|
@ -14,26 +14,28 @@ from pygments.style import Style
|
|||
from pygments.token import Keyword, Operator, Number, Name, Error, Comment, Token, String
|
||||
|
||||
from prompt_toolkit import CommandLineInterface
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.completion import Completer, Completion
|
||||
from prompt_toolkit.enums import InputMode
|
||||
from prompt_toolkit.history import FileHistory, History
|
||||
from prompt_toolkit.key_bindings.emacs import emacs_bindings
|
||||
from prompt_toolkit.key_bindings.vi import vi_bindings
|
||||
from prompt_toolkit.key_binding.bindings.vi import ViStateFilter
|
||||
from prompt_toolkit.key_binding.manager import KeyBindingManager, ViModeEnabled
|
||||
from prompt_toolkit.key_binding.vi_state import InputMode
|
||||
from prompt_toolkit.keys import Keys
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.layout.margins import LeftMarginWithLineNumbers
|
||||
from prompt_toolkit.layout.menus import CompletionsMenu
|
||||
from prompt_toolkit.layout.processors import BracketsMismatchProcessor
|
||||
from prompt_toolkit.layout.toolbars import CompletionsToolbar, ArgToolbar, SearchToolbar, ValidationToolbar, SystemToolbar
|
||||
from prompt_toolkit.layout.toolbars import Toolbar
|
||||
from prompt_toolkit.line import Line
|
||||
from prompt_toolkit.selection import SelectionType
|
||||
from prompt_toolkit.validation import Validator, ValidationError
|
||||
from prompt_toolkit.layout.margins import LeftMarginWithLineNumbers
|
||||
|
||||
from .regular_languages.compiler import compile as compile_grammar
|
||||
from .regular_languages.completion import GrammarCompleter
|
||||
from .completers import PathCompleter
|
||||
|
||||
import prompt_toolkit.filters as filters
|
||||
|
||||
import jedi
|
||||
import platform
|
||||
import re
|
||||
|
@ -166,94 +168,114 @@ def _has_unclosed_brackets(text):
|
|||
return False
|
||||
|
||||
|
||||
def python_bindings(registry, cli_ref):
|
||||
def load_python_bindings(key_bindings_manager, settings, always_multiline=False):
|
||||
"""
|
||||
Custom key bindings.
|
||||
"""
|
||||
line = cli_ref().line
|
||||
handle = registry.add_binding
|
||||
handle = key_bindings_manager.registry.add_binding
|
||||
has_selection = filters.HasSelection()
|
||||
|
||||
@handle(Keys.F4)
|
||||
def _(event):
|
||||
"""
|
||||
Toggle between Vi and Emacs mode.
|
||||
"""
|
||||
key_bindings_manager.enable_vi_mode = not key_bindings_manager.enable_vi_mode
|
||||
|
||||
@handle(Keys.F6)
|
||||
def _(event):
|
||||
"""
|
||||
Enable/Disable paste mode.
|
||||
"""
|
||||
line.paste_mode = not line.paste_mode
|
||||
if line.paste_mode:
|
||||
line.is_multiline = True
|
||||
settings.paste_mode = not settings.paste_mode
|
||||
|
||||
if not cli_ref().line.always_multiline:
|
||||
@handle(Keys.F7)
|
||||
def _(event):
|
||||
"""
|
||||
Enable/Disable multiline mode.
|
||||
"""
|
||||
line.always_multiline = not line.always_multiline
|
||||
@handle(Keys.F7)
|
||||
def _(event):
|
||||
"""
|
||||
Enable/Disable multiline mode.
|
||||
"""
|
||||
settings.currently_multiline = not settings.currently_multiline
|
||||
|
||||
@handle(Keys.Tab, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Tab, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
When the 'tab' key is pressed with only whitespace character before the
|
||||
cursor, do autocompletion. Otherwise, insert indentation.
|
||||
"""
|
||||
current_char = line.document.current_line_before_cursor
|
||||
buffer = event.cli.buffers['default']
|
||||
current_char = buffer.document.current_line_before_cursor
|
||||
|
||||
if not current_char or current_char.isspace():
|
||||
line.insert_text(' ')
|
||||
buffer.insert_text(' ')
|
||||
else:
|
||||
line.complete_next()
|
||||
buffer.complete_next()
|
||||
|
||||
@handle(Keys.ControlJ, filter= ~has_selection &
|
||||
~(ViModeEnabled(key_bindings_manager) &
|
||||
ViStateFilter(key_bindings_manager.vi_state, InputMode.NAVIGATION)) &
|
||||
filters.HasFocus('default') & filters.IsMultiline())
|
||||
def _(event):
|
||||
"""
|
||||
Auto indent after newline/Enter.
|
||||
(When not in Vi navigaton mode, and when multiline is enabled.)
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
|
||||
if settings.paste_mode:
|
||||
buffer.insert_text('\n')
|
||||
else:
|
||||
auto_newline(buffer)
|
||||
|
||||
|
||||
class PythonLine(Line):
|
||||
def auto_newline(buffer):
|
||||
r"""
|
||||
Insert \n at the cursor position. Also add necessary padding.
|
||||
"""
|
||||
Custom `Line` class with some helper functions.
|
||||
insert_text = buffer.insert_text
|
||||
|
||||
if buffer.document.current_line_after_cursor:
|
||||
insert_text('\n')
|
||||
else:
|
||||
# Go to new line, but also add indentation.
|
||||
current_line = buffer.document.current_line_before_cursor.rstrip()
|
||||
insert_text('\n')
|
||||
|
||||
# Copy whitespace from current line
|
||||
for c in current_line:
|
||||
if c.isspace():
|
||||
insert_text(c)
|
||||
else:
|
||||
break
|
||||
|
||||
# If the last line ends with a colon, add four extra spaces.
|
||||
if current_line[-1:] == ':':
|
||||
for x in range(4):
|
||||
insert_text(' ')
|
||||
|
||||
|
||||
class PythonBuffer(Buffer):
|
||||
"""
|
||||
Custom `Buffer` class with some helper functions.
|
||||
"""
|
||||
_multiline_string_delims = re.compile('''[']{3}|["]{3}''')
|
||||
|
||||
def __init__(self, always_multiline, *a, **kw):
|
||||
self.always_multiline = always_multiline
|
||||
super(PythonLine, self).__init__(*a, **kw)
|
||||
|
||||
def reset(self, *a, **kw):
|
||||
super(PythonLine, self).reset(*a, **kw)
|
||||
|
||||
#: Boolean `paste` flag. If True, don't insert whitespace after a
|
||||
#: newline.
|
||||
self.paste_mode = False
|
||||
super(PythonBuffer, self).reset(*a, **kw)
|
||||
|
||||
# Code signatures. (This is set asynchronously after a timeout.)
|
||||
self.signatures = []
|
||||
|
||||
def newline(self):
|
||||
r"""
|
||||
Insert \n at the cursor position. Also add necessary padding.
|
||||
"""
|
||||
insert_text = super(PythonLine, self).insert_text
|
||||
|
||||
if self.paste_mode or self.document.current_line_after_cursor:
|
||||
insert_text('\n')
|
||||
else:
|
||||
# Go to new line, but also add indentation.
|
||||
current_line = self.document.current_line_before_cursor.rstrip()
|
||||
insert_text('\n')
|
||||
_multiline_string_delims = re.compile('''[']{3}|["]{3}''')
|
||||
|
||||
# Copy whitespace from current line
|
||||
for c in current_line:
|
||||
if c.isspace():
|
||||
insert_text(c)
|
||||
else:
|
||||
break
|
||||
|
||||
# If the last line ends with a colon, add four extra spaces.
|
||||
if current_line[-1:] == ':':
|
||||
for x in range(4):
|
||||
insert_text(' ')
|
||||
|
||||
@property
|
||||
def _ends_in_multiline_string(self):
|
||||
def document_is_multiline_python(document):
|
||||
"""
|
||||
Determine whether this is a multiline Python document.
|
||||
"""
|
||||
def ends_in_multiline_string():
|
||||
"""
|
||||
``True`` if we're inside a multiline string at the end of the text.
|
||||
"""
|
||||
delims = self._multiline_string_delims.findall(self.text)
|
||||
delims = _multiline_string_delims.findall(document.text)
|
||||
opening = None
|
||||
for delim in delims:
|
||||
if opening is None:
|
||||
|
@ -262,49 +284,35 @@ class PythonLine(Line):
|
|||
opening = None
|
||||
return bool(opening)
|
||||
|
||||
@property
|
||||
def is_multiline(self):
|
||||
"""
|
||||
Dynamically determine whether we're in multiline mode.
|
||||
"""
|
||||
if any([
|
||||
self.always_multiline,
|
||||
self.paste_mode,
|
||||
'\n' in self.text,
|
||||
self._ends_in_multiline_string]):
|
||||
return True
|
||||
if '\n' in document.text or ends_in_multiline_string():
|
||||
return True
|
||||
|
||||
# If we just typed a colon, or still have open brackets, always insert a real newline.
|
||||
if self.document.text_before_cursor.rstrip()[-1:] == ':' or \
|
||||
(self.document.is_cursor_at_the_end and
|
||||
_has_unclosed_brackets(self.document.text_before_cursor)) or \
|
||||
self.text.startswith('@'):
|
||||
return True
|
||||
# If we just typed a colon, or still have open brackets, always insert a real newline.
|
||||
if document.text_before_cursor.rstrip()[-1:] == ':' or \
|
||||
(document.is_cursor_at_the_end and
|
||||
_has_unclosed_brackets(document.text_before_cursor)) or \
|
||||
document.text.startswith('@'):
|
||||
return True
|
||||
|
||||
# If the character before the cursor is a backslash (line continuation
|
||||
# char), insert a new line.
|
||||
elif self.document.text_before_cursor[-1:] == '\\':
|
||||
return True
|
||||
# If the character before the cursor is a backslash (line continuation
|
||||
# char), insert a new line.
|
||||
elif document.text_before_cursor[-1:] == '\\':
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@is_multiline.setter
|
||||
def is_multiline(self, value):
|
||||
""" Ignore setter. """
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
class SignatureToolbar(Toolbar):
|
||||
def is_visible(self, cli):
|
||||
return super(SignatureToolbar, self).is_visible(cli) and bool(cli.line.signatures)
|
||||
return super(SignatureToolbar, self).is_visible(cli) and bool(cli.buffers['default'].signatures)
|
||||
|
||||
def get_tokens(self, cli, width):
|
||||
result = []
|
||||
append = result.append
|
||||
Signature = Token.Toolbar.Signature
|
||||
|
||||
if cli.line.signatures:
|
||||
sig = cli.line.signatures[0] # Always take the first one.
|
||||
if cli.buffers['default'].signatures:
|
||||
sig = cli.buffers['default'].signatures[0] # Always take the first one.
|
||||
|
||||
append((Token, ' '))
|
||||
try:
|
||||
|
@ -331,7 +339,7 @@ class SignatureToolbar(Toolbar):
|
|||
return result
|
||||
|
||||
|
||||
def get_inputmode_tokens(token, vi_mode, cli):
|
||||
def get_inputmode_tokens(token, key_bindings_manager, cli):
|
||||
"""
|
||||
Return current input mode as a list of (token, text) tuples for use in a
|
||||
toolbar.
|
||||
|
@ -339,79 +347,74 @@ def get_inputmode_tokens(token, vi_mode, cli):
|
|||
:param vi_mode: (bool) True when vi mode is enabled.
|
||||
:param cli: `CommandLineInterface` instance.
|
||||
"""
|
||||
mode = cli.input_processor.input_mode
|
||||
mode = key_bindings_manager.vi_state.input_mode
|
||||
result = []
|
||||
append = result.append
|
||||
|
||||
# InputMode
|
||||
if mode == InputMode.INCREMENTAL_SEARCH:
|
||||
append((token.InputMode, '(SEARCH)'))
|
||||
append((token, ' '))
|
||||
elif vi_mode:
|
||||
if mode == InputMode.INSERT:
|
||||
append((token.InputMode, '(INSERT)'))
|
||||
append((token, ' '))
|
||||
elif mode == InputMode.VI_SEARCH:
|
||||
append((token.InputMode, '(SEARCH)'))
|
||||
append((token, ' '))
|
||||
elif mode == InputMode.VI_NAVIGATION:
|
||||
append((token.InputMode, '(NAV)'))
|
||||
append((token, ' '))
|
||||
elif mode == InputMode.VI_REPLACE:
|
||||
append((token.InputMode, '(REPLACE)'))
|
||||
if key_bindings_manager.enable_vi_mode:
|
||||
if bool(cli.buffers['default'].selection_state):
|
||||
if cli.buffers['default'].selection_state.type == SelectionType.LINES:
|
||||
append((token.InputMode, '[F4] Vi (VISUAL LINE)'))
|
||||
append((token, ' '))
|
||||
elif cli.buffers['default'].selection_state.type == SelectionType.CHARACTERS:
|
||||
append((token.InputMode, '[F4] Vi (VISUAL)'))
|
||||
append((token, ' '))
|
||||
elif mode == InputMode.INSERT:
|
||||
append((token.InputMode, '[F4] Vi (INSERT)'))
|
||||
append((token, ' '))
|
||||
elif mode == InputMode.SELECTION and cli.line.selection_state:
|
||||
if cli.line.selection_state.type == SelectionType.LINES:
|
||||
append((token.InputMode, '(VISUAL LINE)'))
|
||||
append((token, ' '))
|
||||
elif cli.line.selection_state.type == SelectionType.CHARACTERS:
|
||||
append((token.InputMode, '(VISUAL)'))
|
||||
append((token, ' '))
|
||||
|
||||
elif mode == InputMode.NAVIGATION:
|
||||
append((token.InputMode, '[F4] Vi (NAV)'))
|
||||
append((token, ' '))
|
||||
elif mode == InputMode.REPLACE:
|
||||
append((token.InputMode, '[F4] Vi (REPLACE)'))
|
||||
append((token, ' '))
|
||||
else:
|
||||
append((token.InputMode, '(emacs)'))
|
||||
append((token.InputMode, '[F4] Emacs'))
|
||||
append((token, ' '))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class PythonToolbar(Toolbar):
|
||||
def __init__(self, vi_mode, token=None):
|
||||
def __init__(self, key_bindings_manager, settings, token=None):
|
||||
self.key_bindings_manager = key_bindings_manager
|
||||
self.settings = settings
|
||||
|
||||
token = token or Token.Toolbar.Status
|
||||
self.vi_mode = vi_mode
|
||||
super(PythonToolbar, self).__init__(token=token)
|
||||
|
||||
def get_tokens(self, cli, width):
|
||||
TB = self.token
|
||||
mode = cli.input_processor.input_mode
|
||||
result = []
|
||||
append = result.append
|
||||
|
||||
append((TB, ' '))
|
||||
result.extend(get_inputmode_tokens(TB, self.vi_mode, cli))
|
||||
result.extend(get_inputmode_tokens(TB, self.key_bindings_manager, cli))
|
||||
|
||||
# Position in history.
|
||||
append((TB, '%i/%i ' % (cli.line.working_index + 1, len(cli.line._working_lines))))
|
||||
append((TB, '%i/%i ' % (cli.buffers['default'].working_index + 1, len(cli.buffers['default']._working_lines))))
|
||||
|
||||
# Shortcuts.
|
||||
if mode == InputMode.INCREMENTAL_SEARCH:
|
||||
if not self.key_bindings_manager.enable_vi_mode and cli.focus_stack.current == 'search':
|
||||
append((TB, '[Ctrl-G] Cancel search [Enter] Go to this position.'))
|
||||
elif mode == InputMode.SELECTION and not self.vi_mode:
|
||||
elif bool(cli.buffers['default'].selection_state) and not self.key_bindings_manager.enable_vi_mode:
|
||||
# Emacs cut/copy keys.
|
||||
append((TB, '[Ctrl-W] Cut [Meta-W] Copy [Ctrl-Y] Paste [Ctrl-G] Cancel'))
|
||||
else:
|
||||
if cli.line.paste_mode:
|
||||
if self.settings.paste_mode:
|
||||
append((TB.On, '[F6] Paste mode (on) '))
|
||||
else:
|
||||
append((TB.Off, '[F6] Paste mode (off) '))
|
||||
|
||||
if not cli.always_multiline:
|
||||
if cli.line.is_multiline:
|
||||
if not self.settings.always_multiline:
|
||||
if self.settings.currently_multiline or \
|
||||
document_is_multiline_python(cli.buffers['default'].document):
|
||||
append((TB.On, '[F7] Multiline (on)'))
|
||||
else:
|
||||
append((TB.Off, '[F7] Multiline (off)'))
|
||||
|
||||
if cli.line.is_multiline:
|
||||
if cli.buffers['default'].is_multiline:
|
||||
append((TB, ' [Meta+Enter] Execute'))
|
||||
|
||||
# Python version
|
||||
|
@ -588,6 +591,21 @@ class PythonCompleter(Completer):
|
|||
display=c.name_with_symbols)
|
||||
|
||||
|
||||
class PythonCLISettings(object):
|
||||
"""
|
||||
Settings for the Python REPL which can change at runtime.
|
||||
"""
|
||||
def __init__(self,
|
||||
always_multiline=False,
|
||||
paste_mode=False):
|
||||
self.always_multiline = always_multiline
|
||||
self.currently_multiline = False
|
||||
|
||||
#: Boolean `paste` flag. If True, don't insert whitespace after a
|
||||
#: newline.
|
||||
self.paste_mode = paste_mode
|
||||
|
||||
|
||||
class PythonCommandLineInterface(CommandLineInterface):
|
||||
def __init__(self,
|
||||
get_globals=None, get_locals=None,
|
||||
|
@ -602,15 +620,24 @@ class PythonCommandLineInterface(CommandLineInterface):
|
|||
_completer=None,
|
||||
_validator=None):
|
||||
|
||||
self.settings = PythonCLISettings(always_multiline=always_multiline)
|
||||
|
||||
self.get_globals = get_globals or (lambda: {})
|
||||
self.get_locals = get_locals or self.get_globals
|
||||
self.always_multiline = always_multiline
|
||||
self.autocompletion_style = autocompletion_style
|
||||
|
||||
left_margin = _left_margin or PythonLeftMargin()
|
||||
self.completer = _completer or PythonCompleter(self.get_globals, self.get_locals)
|
||||
validator = _validator or PythonValidator()
|
||||
|
||||
if history_filename:
|
||||
history = FileHistory(history_filename)
|
||||
else:
|
||||
history = History()
|
||||
|
||||
# Use a KeyBindingManager for loading the key bindings.
|
||||
self.key_bindings_manager = KeyBindingManager(enable_vi_mode=vi_mode, enable_system_prompt=True)
|
||||
load_python_bindings(self.key_bindings_manager, self.settings, always_multiline=always_multiline)
|
||||
|
||||
layout = Layout(
|
||||
input_processors=[BracketsMismatchProcessor()],
|
||||
min_height=7,
|
||||
|
@ -626,21 +653,18 @@ class PythonCommandLineInterface(CommandLineInterface):
|
|||
] +
|
||||
([CompletionsToolbar()] if autocompletion_style == AutoCompletionStyle.HORIZONTAL_MENU else []) +
|
||||
[
|
||||
PythonToolbar(vi_mode=vi_mode),
|
||||
PythonToolbar(self.key_bindings_manager, self.settings),
|
||||
],
|
||||
show_tildes=True)
|
||||
|
||||
if history_filename:
|
||||
history = FileHistory(history_filename)
|
||||
else:
|
||||
history = History()
|
||||
def is_buffer_multiline(document):
|
||||
return (self.settings.paste_mode or
|
||||
self.settings.always_multiline or
|
||||
self.settings.currently_multiline or
|
||||
document_is_multiline_python(document))
|
||||
|
||||
if vi_mode:
|
||||
key_binding_factories = [vi_bindings, python_bindings]
|
||||
else:
|
||||
key_binding_factories = [emacs_bindings, python_bindings]
|
||||
|
||||
line=PythonLine(always_multiline=always_multiline,
|
||||
buffer=PythonBuffer(
|
||||
is_multiline=is_buffer_multiline,
|
||||
tempfile_suffix='.py',
|
||||
history=history,
|
||||
completer=self.completer,
|
||||
|
@ -654,8 +678,8 @@ class PythonCommandLineInterface(CommandLineInterface):
|
|||
super(PythonCommandLineInterface, self).__init__(
|
||||
layout=layout,
|
||||
style=style,
|
||||
key_binding_factories=key_binding_factories,
|
||||
line=line,
|
||||
key_bindings_registry=self.key_bindings_manager.registry,
|
||||
buffer=buffer,
|
||||
create_async_autocompleters=True)
|
||||
|
||||
def on_input_timeout():
|
||||
|
@ -663,12 +687,15 @@ class PythonCommandLineInterface(CommandLineInterface):
|
|||
When there is no input activity,
|
||||
in another thread, get the signature of the current code.
|
||||
"""
|
||||
if self.focus_stack.current != 'default':
|
||||
return
|
||||
|
||||
# Never run multiple get-signature threads.
|
||||
if self.get_signatures_thread_running:
|
||||
return
|
||||
self.get_signatures_thread_running = True
|
||||
|
||||
document = self.line.document
|
||||
document = self.buffers['default'].document
|
||||
|
||||
def run():
|
||||
script = get_jedi_script_from_document(document, self.get_locals(), self.get_globals())
|
||||
|
@ -692,8 +719,8 @@ class PythonCommandLineInterface(CommandLineInterface):
|
|||
|
||||
# Set signatures and redraw if the text didn't change in the
|
||||
# meantime. Otherwise request new signatures.
|
||||
if self.line.text == document.text:
|
||||
self.line.signatures = signatures
|
||||
if self.buffers['default'].text == document.text:
|
||||
self.buffers['default'].signatures = signatures
|
||||
self.request_redraw()
|
||||
else:
|
||||
on_input_timeout()
|
||||
|
@ -701,3 +728,4 @@ class PythonCommandLineInterface(CommandLineInterface):
|
|||
self.run_in_executor(run)
|
||||
|
||||
self.onInputTimeout += on_input_timeout
|
||||
self.onReset += self.key_bindings_manager.reset
|
||||
|
|
|
@ -78,8 +78,9 @@ class _CompiledGrammar(object):
|
|||
self._re_prefix_patterns = list(self._transform_prefix(root_node, create_group_func))
|
||||
|
||||
# Compile the regex itself.
|
||||
self._re = re.compile(self._re_pattern)
|
||||
self._re_prefix = [re.compile(t) for t in self._re_prefix_patterns]
|
||||
flags = re.MULTILINE | re.DOTALL
|
||||
self._re = re.compile(self._re_pattern, flags)
|
||||
self._re_prefix = [re.compile(t, flags) for t in self._re_prefix_patterns]
|
||||
|
||||
def escape(self, varname, value):
|
||||
"""
|
||||
|
|
|
@ -3,13 +3,15 @@ Useful shortcuts.
|
|||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .. import CommandLineInterface, AbortAction
|
||||
from ..key_bindings.emacs import emacs_bindings
|
||||
from ..key_bindings.vi import vi_bindings
|
||||
from ..line import Line
|
||||
from ..layout import Layout
|
||||
from ..layout.processors import PasswordProcessor
|
||||
from ..layout.prompt import DefaultPrompt
|
||||
from prompt_toolkit import CommandLineInterface, AbortAction
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.key_binding.registry import Registry
|
||||
from prompt_toolkit.key_binding.vi_state import ViState
|
||||
from prompt_toolkit.key_binding.bindings.emacs import load_emacs_bindings, load_emacs_search_bindings
|
||||
from prompt_toolkit.key_binding.bindings.vi import load_vi_bindings, load_vi_search_bindings
|
||||
from prompt_toolkit.layout import Layout
|
||||
from prompt_toolkit.layout.processors import PasswordProcessor
|
||||
from prompt_toolkit.layout.prompt import DefaultPrompt
|
||||
|
||||
|
||||
def get_input(message, raise_exception_on_abort=False, multiline=False, is_password=False, vi_mode=False):
|
||||
|
@ -22,10 +24,19 @@ def get_input(message, raise_exception_on_abort=False, multiline=False, is_passw
|
|||
before_input=DefaultPrompt(message),
|
||||
input_processors=([PasswordProcessor()] if is_password else []))
|
||||
|
||||
registry = Registry()
|
||||
if vi_mode:
|
||||
vi_state = ViState()
|
||||
load_vi_bindings(registry, vi_state)
|
||||
load_vi_search_bindings(registry, vi_state)
|
||||
else:
|
||||
load_emacs_bindings(registry)
|
||||
load_emacs_search_bindings(registry)
|
||||
|
||||
cli = CommandLineInterface(
|
||||
layout=layout,
|
||||
line=Line(is_multiline=multiline),
|
||||
key_binding_factories=[(vi_bindings if vi_mode else emacs_bindings)])
|
||||
buffer=Buffer(is_multiline=multiline),
|
||||
key_bindings_registry=registry)
|
||||
|
||||
on_abort = AbortAction.RAISE_EXCEPTION if raise_exception_on_abort else AbortAction.RETURN_NONE
|
||||
code = cli.read_input(on_abort=on_abort, on_exit=AbortAction.IGNORE)
|
||||
|
|
|
@ -29,7 +29,7 @@ class Document(object):
|
|||
This is a immutable class around the text and cursor position, and contains
|
||||
methods for querying this data, e.g. to give the text before the cursor.
|
||||
|
||||
This class is usually instantiated by a :class:`~prompt_toolkit.line.Line`
|
||||
This class is usually instantiated by a :class:`~prompt_toolkit.buffer.Buffer`
|
||||
object, and accessed as the `document` property of that class.
|
||||
|
||||
:param text: string
|
||||
|
|
|
@ -6,21 +6,3 @@ class IncrementalSearchDirection:
|
|||
BACKWARD = 'backward'
|
||||
|
||||
|
||||
class InputMode(object):
|
||||
INSERT = 'vi-insert'
|
||||
|
||||
VI_NAVIGATION = 'vi-navigation'
|
||||
VI_REPLACE = 'vi-replace'
|
||||
|
||||
# Selection mode. The type of selection (characters/lines/block is saved in
|
||||
# the line object.)
|
||||
SELECTION = 'selection'
|
||||
|
||||
#: Ctrl-R/Ctrl-S incremental search.
|
||||
INCREMENTAL_SEARCH = 'incremental-search'
|
||||
|
||||
#: Vi-style forward search. Usually with a '/' or '?' prompt.
|
||||
VI_SEARCH = 'vi-forward-search'
|
||||
|
||||
#: When the system prompt (after typing '!' or M-!) has the focus.
|
||||
SYSTEM = 'system'
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
"""
|
||||
Filters decide whether something is active or not, given the state of the
|
||||
CommandLineInterface. This can be used to enable/disable key bindings, as well
|
||||
as to hide/show parts of the layout.
|
||||
|
||||
When Filter.__call__ returns True for a centain key, this is considered active.
|
||||
|
||||
Filters can be chained using ``&`` and ``|`` operations, and inverted using the
|
||||
``~`` operator::
|
||||
|
||||
filter = HasFocus('default') & ~ HasSelection()
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
__all__ = (
|
||||
'HasFocus',
|
||||
'HasSelection',
|
||||
'IsMultiline',
|
||||
'NoFilter',
|
||||
)
|
||||
|
||||
|
||||
class Filter(object):
|
||||
"""
|
||||
Filter to activate/deactivate a key binding, depending on a condition.
|
||||
The return value of ``__call__`` will tell if the key binding should be active.
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def __call__(self, cli):
|
||||
return True
|
||||
|
||||
def __and__(self, other):
|
||||
if other is None:
|
||||
return self
|
||||
else:
|
||||
assert isinstance(other, Filter), 'Expecting filter, got %r' % other
|
||||
return _AndList([self, other])
|
||||
|
||||
def __or__(self, other):
|
||||
if other is None:
|
||||
return self
|
||||
else:
|
||||
assert isinstance(other, Filter), 'Expecting filter, got %r' % other
|
||||
return _OrList([self, other])
|
||||
|
||||
def __invert__(self):
|
||||
return _Invert(self)
|
||||
|
||||
|
||||
class _AndList(Filter):
|
||||
""" Result of &-operation between several filters. """
|
||||
def __init__(self, filters):
|
||||
self.filters = filters
|
||||
|
||||
def __call__(self, cli):
|
||||
return all(f(cli) for f in self.filters)
|
||||
|
||||
def __repr__(self):
|
||||
return '&'.join(repr(f) for f in self.filters)
|
||||
|
||||
|
||||
class _OrList(Filter):
|
||||
""" Result of |-operation between several filters. """
|
||||
def __init__(self, filters):
|
||||
self.filters = filters
|
||||
|
||||
def __call__(self, cli):
|
||||
return any(f(cli) for f in self.filters)
|
||||
|
||||
def __repr__(self):
|
||||
return '|'.join(repr(f) for f in self.filters)
|
||||
|
||||
|
||||
class _Invert(Filter):
|
||||
""" Negation of another filter. """
|
||||
def __init__(self, filter):
|
||||
self.filter = filter
|
||||
|
||||
def __call__(self, cli):
|
||||
return not self.filter(cli)
|
||||
|
||||
def __repr__(self):
|
||||
return '~%r' % self.filter
|
||||
|
||||
|
||||
class HasFocus(Filter):
|
||||
"""
|
||||
Enable when this buffer has the focus.
|
||||
"""
|
||||
def __init__(self, buffer_name):
|
||||
self.buffer_name = buffer_name
|
||||
|
||||
def __call__(self, cli):
|
||||
return cli.focus_stack.current == self.buffer_name
|
||||
|
||||
|
||||
class HasSelection(Filter):
|
||||
"""
|
||||
Enable when the current buffer has a selection.
|
||||
"""
|
||||
def __call__(self, cli):
|
||||
return bool(cli.buffers[cli.focus_stack.current].selection_state)
|
||||
|
||||
|
||||
class IsMultiline(Filter):
|
||||
"""
|
||||
Enable in multiline mode.
|
||||
"""
|
||||
def __call__(self, cli):
|
||||
return cli.buffers[cli.focus_stack.current].is_multiline
|
||||
|
||||
|
||||
class NoFilter(Filter):
|
||||
"""
|
||||
Always enable key binding.
|
||||
"""
|
||||
def __call__(self, cli):
|
||||
return True
|
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Push/pop stack of buffer names. The top buffer of the stack is the one that
|
||||
currently has the focus.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from six import string_types
|
||||
|
||||
__all__ = (
|
||||
'FocusStack',
|
||||
)
|
||||
|
||||
|
||||
class FocusStack(object):
|
||||
def __init__(self, initial='default'):
|
||||
self._initial = initial
|
||||
|
||||
def reset(self):
|
||||
self._stack = [self._initial]
|
||||
|
||||
def pop(self):
|
||||
if len(self._stack) > 1:
|
||||
self._stack.pop()
|
||||
else:
|
||||
raise Exception('Cannot pop last item from the focus stack.')
|
||||
|
||||
def push(self, buffer_name):
|
||||
assert isinstance(buffer_name, string_types)
|
||||
self._stack.append(buffer_name)
|
||||
|
||||
@property
|
||||
def current(self):
|
||||
return self._stack[-1]
|
||||
|
||||
@property
|
||||
def previous(self):
|
||||
return self._stack[-2]
|
|
@ -0,0 +1 @@
|
|||
from __future__ import unicode_literals
|
|
@ -0,0 +1,280 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from prompt_toolkit.keys import Keys
|
||||
from prompt_toolkit.buffer import ClipboardData
|
||||
import prompt_toolkit.filters as filters
|
||||
|
||||
from .utils import create_handle_decorator
|
||||
|
||||
|
||||
__all__ = (
|
||||
'load_basic_bindings',
|
||||
)
|
||||
|
||||
|
||||
def load_basic_bindings(registry, filter=None):
|
||||
handle = create_handle_decorator(registry, filter)
|
||||
has_selection = filters.HasSelection()
|
||||
|
||||
@handle(Keys.ControlA)
|
||||
@handle(Keys.ControlB)
|
||||
@handle(Keys.ControlC)
|
||||
@handle(Keys.ControlD)
|
||||
@handle(Keys.ControlE)
|
||||
@handle(Keys.ControlF)
|
||||
@handle(Keys.ControlG)
|
||||
@handle(Keys.ControlH)
|
||||
@handle(Keys.ControlI)
|
||||
@handle(Keys.ControlJ)
|
||||
@handle(Keys.ControlK)
|
||||
@handle(Keys.ControlL)
|
||||
@handle(Keys.ControlM)
|
||||
@handle(Keys.ControlN)
|
||||
@handle(Keys.ControlO)
|
||||
@handle(Keys.ControlP)
|
||||
@handle(Keys.ControlQ)
|
||||
@handle(Keys.ControlR)
|
||||
@handle(Keys.ControlS)
|
||||
@handle(Keys.ControlT)
|
||||
@handle(Keys.ControlU)
|
||||
@handle(Keys.ControlV)
|
||||
@handle(Keys.ControlW)
|
||||
@handle(Keys.ControlX)
|
||||
@handle(Keys.ControlY)
|
||||
@handle(Keys.ControlZ)
|
||||
@handle(Keys.F1)
|
||||
@handle(Keys.F2)
|
||||
@handle(Keys.F3)
|
||||
@handle(Keys.F4)
|
||||
@handle(Keys.F5)
|
||||
@handle(Keys.F6)
|
||||
@handle(Keys.F7)
|
||||
@handle(Keys.F8)
|
||||
@handle(Keys.F9)
|
||||
@handle(Keys.F10)
|
||||
@handle(Keys.F11)
|
||||
@handle(Keys.F12)
|
||||
@handle(Keys.F13)
|
||||
@handle(Keys.F14)
|
||||
@handle(Keys.F15)
|
||||
@handle(Keys.F16)
|
||||
@handle(Keys.F17)
|
||||
@handle(Keys.F18)
|
||||
@handle(Keys.F19)
|
||||
@handle(Keys.F20)
|
||||
@handle(Keys.ControlSpace)
|
||||
@handle(Keys.ControlBackslash)
|
||||
@handle(Keys.ControlSquareClose)
|
||||
@handle(Keys.ControlCircumflex)
|
||||
@handle(Keys.ControlUnderscore)
|
||||
@handle(Keys.Backspace)
|
||||
@handle(Keys.Up)
|
||||
@handle(Keys.Down)
|
||||
@handle(Keys.Right)
|
||||
@handle(Keys.Left)
|
||||
@handle(Keys.Home)
|
||||
@handle(Keys.End)
|
||||
@handle(Keys.Delete)
|
||||
@handle(Keys.ShiftDelete)
|
||||
@handle(Keys.PageUp)
|
||||
@handle(Keys.PageDown)
|
||||
@handle(Keys.BackTab)
|
||||
@handle(Keys.Tab)
|
||||
def _(event):
|
||||
"""
|
||||
First, for any of these keys, Don't do anything by default. Also don't
|
||||
catch them in the 'Any' handler which will insert them as data.
|
||||
|
||||
If people want to insert these characters as a literal, they can always
|
||||
do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi
|
||||
mode.)
|
||||
"""
|
||||
pass
|
||||
|
||||
@handle(Keys.Home)
|
||||
def _(event):
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.home_position
|
||||
|
||||
@handle(Keys.End)
|
||||
def _(event):
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.end_position
|
||||
|
||||
# CTRL keys.
|
||||
|
||||
@handle(Keys.ControlC)
|
||||
def _(event):
|
||||
event.cli.set_abort()
|
||||
|
||||
@handle(Keys.ControlD) # XXX: this is emacs behaviour.
|
||||
def _(event):
|
||||
buffer = event.current_buffer
|
||||
|
||||
# When there is text, act as delete, otherwise call exit.
|
||||
if buffer.text:
|
||||
buffer.delete()
|
||||
else:
|
||||
event.cli.set_exit()
|
||||
|
||||
@handle(Keys.ControlI, filter= ~has_selection)
|
||||
def _(event):
|
||||
r"""
|
||||
Ctrl-I is identical to "\t"
|
||||
|
||||
Traditional tab-completion, where the first tab completes the common
|
||||
suffix and the second tab lists all the completions.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
|
||||
def second_tab():
|
||||
buffer.complete_next(start_at_first=False)
|
||||
|
||||
# On the second tab-press, or when already navigating through
|
||||
# completions.
|
||||
if event.second_press or (buffer.complete_state and buffer.complete_state.complete_index is not None):
|
||||
second_tab()
|
||||
else:
|
||||
# On the first tab press, only complete the common parts of all completions.
|
||||
has_common = buffer.complete_common()
|
||||
if not has_common:
|
||||
second_tab()
|
||||
|
||||
@handle(Keys.BackTab, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Shift+Tab: go to previous completion.
|
||||
"""
|
||||
event.current_buffer.complete_previous()
|
||||
|
||||
@handle(Keys.ControlM)
|
||||
def _(event):
|
||||
"""
|
||||
Transform ControlM (\r) by default into ControlJ (\n).
|
||||
(This way it is sufficient for other key bindings to handle only ControlJ.)
|
||||
"""
|
||||
event.cli.input_processor.feed_key(Keys.ControlJ)
|
||||
|
||||
@handle(Keys.ControlJ, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Newline/Enter. (Or return input.)
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
|
||||
if buffer.is_multiline:
|
||||
buffer.newline()
|
||||
else:
|
||||
if buffer.validate():
|
||||
event.current_buffer.add_to_history()
|
||||
event.cli.set_return_value(buffer.document)
|
||||
|
||||
@handle(Keys.ControlK, filter= ~has_selection)
|
||||
def _(event):
|
||||
buffer = event.current_buffer
|
||||
deleted = buffer.delete(count=buffer.document.get_end_of_line_position())
|
||||
buffer.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.ControlL)
|
||||
def _(event):
|
||||
event.cli.renderer.clear()
|
||||
|
||||
@handle(Keys.ControlT, filter= ~has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.swap_characters_before_cursor()
|
||||
|
||||
@handle(Keys.ControlU, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Clears the line before the cursor position. If you are at the end of
|
||||
the line, clears the entire line.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
deleted = buffer.delete_before_cursor(count=-buffer.document.get_start_of_line_position())
|
||||
buffer.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.ControlW, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Delete the word before the cursor.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
pos = buffer.document.find_start_of_previous_word(count=event.arg)
|
||||
|
||||
if pos:
|
||||
deleted = buffer.delete_before_cursor(count=-pos)
|
||||
buffer.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.PageUp, filter= ~has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.history_backward()
|
||||
|
||||
@handle(Keys.PageDown, filter= ~has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.history_forward()
|
||||
|
||||
@handle(Keys.Left)
|
||||
def _(event):
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.get_cursor_left_position(count=event.arg)
|
||||
|
||||
@handle(Keys.Right)
|
||||
def _(event):
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.get_cursor_right_position(count=event.arg)
|
||||
|
||||
@handle(Keys.Up, filter= ~has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.auto_up(count=event.arg)
|
||||
|
||||
@handle(Keys.Up, filter=has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.cursor_up(count=event.arg)
|
||||
|
||||
@handle(Keys.Down, filter= ~has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.auto_down(count=event.arg)
|
||||
|
||||
@handle(Keys.Down, filter=has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.cursor_down(count=event.arg)
|
||||
|
||||
@handle(Keys.ControlH, filter= ~has_selection)
|
||||
@handle(Keys.Backspace, filter= ~has_selection)
|
||||
def _(event):
|
||||
buffer = event.current_buffer
|
||||
buffer.delete_before_cursor(count=event.arg)
|
||||
|
||||
@handle(Keys.Delete, filter= ~has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.delete(count=event.arg)
|
||||
|
||||
@handle(Keys.ShiftDelete, filter= ~has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.delete(count=event.arg)
|
||||
|
||||
@handle(Keys.Any, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Insert data at cursor position.
|
||||
"""
|
||||
event.current_buffer.insert_text(event.data * event.arg)
|
||||
|
||||
@handle(Keys.CPRResponse)
|
||||
def _(event):
|
||||
"""
|
||||
Handle incoming Cursor-Position-Request response.
|
||||
"""
|
||||
# The incoming data looks like u'\x1b[35;1R'
|
||||
# Parse row/col information.
|
||||
row, col = map(int, event.data[2:-1].split(';'))
|
||||
|
||||
# Report absolute cursor position to the renderer.
|
||||
event.cli.renderer.report_absolute_cursor_row(row)
|
||||
|
||||
@handle(Keys.ControlZ)
|
||||
def _(event):
|
||||
"""
|
||||
Suspend process to background.
|
||||
"""
|
||||
event.cli.suspend_to_background()
|
|
@ -0,0 +1,527 @@
|
|||
from __future__ import unicode_literals
|
||||
from prompt_toolkit.buffer import ClipboardData, SelectionType, indent, unindent
|
||||
from prompt_toolkit.keys import Keys
|
||||
from prompt_toolkit.enums import IncrementalSearchDirection
|
||||
|
||||
from .basic import load_basic_bindings
|
||||
from .utils import create_handle_decorator
|
||||
|
||||
import prompt_toolkit.filters as filters
|
||||
|
||||
__all__ = (
|
||||
'load_emacs_bindings',
|
||||
'load_emacs_search_bindings',
|
||||
'load_emacs_system_bindings',
|
||||
)
|
||||
|
||||
|
||||
def load_emacs_bindings(registry, filter=None):
|
||||
"""
|
||||
Some e-macs extensions.
|
||||
"""
|
||||
# Overview of Readline emacs commands:
|
||||
# http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf
|
||||
|
||||
load_basic_bindings(registry, filter)
|
||||
|
||||
handle = create_handle_decorator(registry, filter)
|
||||
has_selection = filters.HasSelection()
|
||||
|
||||
@handle(Keys.Escape)
|
||||
def _(event):
|
||||
"""
|
||||
By default, ignore escape key.
|
||||
|
||||
(If we don't put this here, and Esc is followed by a key which sequence
|
||||
is not handled, we'll insert an Escape character in the input stream.
|
||||
Something we don't want and happens to easily in emacs mode.
|
||||
Further, people can always use ControlQ to do a quoted insert.)
|
||||
"""
|
||||
pass
|
||||
|
||||
@handle(Keys.ControlA)
|
||||
def _(event):
|
||||
"""
|
||||
Start of line.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=False)
|
||||
|
||||
@handle(Keys.ControlB)
|
||||
def _(event):
|
||||
"""
|
||||
Character back.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.get_cursor_left_position(count=event.arg)
|
||||
|
||||
@handle(Keys.ControlE)
|
||||
def _(event):
|
||||
"""
|
||||
End of line.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.get_end_of_line_position()
|
||||
|
||||
@handle(Keys.ControlF)
|
||||
def _(event):
|
||||
"""
|
||||
Character forward.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.get_cursor_right_position(count=event.arg)
|
||||
|
||||
@handle(Keys.ControlN, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Next line.
|
||||
"""
|
||||
event.current_buffer.auto_down()
|
||||
|
||||
@handle(Keys.ControlN, filter=has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Next line.
|
||||
"""
|
||||
event.current_buffer.cursor_down()
|
||||
|
||||
@handle(Keys.ControlO, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Insert newline, but don't move the cursor.
|
||||
"""
|
||||
event.current_buffer.insert_text('\n', move_cursor=False)
|
||||
|
||||
@handle(Keys.ControlP, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Previous line.
|
||||
"""
|
||||
event.current_buffer.auto_up()
|
||||
|
||||
@handle(Keys.ControlP, filter=has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Previous line.
|
||||
"""
|
||||
event.current_buffer.cursor_up()
|
||||
|
||||
@handle(Keys.ControlQ, Keys.Any, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Quoted insert.
|
||||
"""
|
||||
event.current_buffer.insert_text(event.data, overwrite=False)
|
||||
|
||||
@handle(Keys.ControlY, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Paste before cursor.
|
||||
"""
|
||||
for i in range(event.arg):
|
||||
event.current_buffer.paste_from_clipboard(before=True)
|
||||
|
||||
@handle(Keys.ControlUnderscore, save_before=False, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Undo.
|
||||
"""
|
||||
event.current_buffer.undo()
|
||||
|
||||
def handle_digit(c):
|
||||
"""
|
||||
Handle Alt + digit in the `meta_digit` method.
|
||||
"""
|
||||
@handle(Keys.Escape, c)
|
||||
def _(event):
|
||||
event.append_to_arg_count(c)
|
||||
|
||||
for c in '0123456789':
|
||||
handle_digit(c)
|
||||
|
||||
@handle(Keys.Escape, '-')
|
||||
def _(event):
|
||||
"""
|
||||
"""
|
||||
if event._arg is None:
|
||||
event.append_to_arg_count('-')
|
||||
|
||||
@handle(Keys.Escape, Keys.ControlJ, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Meta + Newline: always accept input.
|
||||
"""
|
||||
if event.current_buffer.validate():
|
||||
event.current_buffer.add_to_history()
|
||||
event.cli.set_return_value(event.current_buffer.document)
|
||||
|
||||
@handle(Keys.ControlSquareClose, Keys.Any)
|
||||
def _(event):
|
||||
"""
|
||||
When Ctl-] + a character is pressed. go to that character.
|
||||
"""
|
||||
match = event.current_buffer.document.find(event.data, in_current_line=True, count=(event.arg))
|
||||
if match is not None:
|
||||
event.current_buffer.cursor_position += match
|
||||
|
||||
@handle(Keys.Escape, Keys.Backspace, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Delete word backwards.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
pos = buffer.document.find_start_of_previous_word(count=event.arg)
|
||||
|
||||
if pos:
|
||||
deleted = buffer.delete_before_cursor(count=-pos)
|
||||
buffer.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.Escape, 'a', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Previous sentence.
|
||||
"""
|
||||
# TODO:
|
||||
pass
|
||||
|
||||
@handle(Keys.Escape, 'c', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Capitalize the current (or following) word.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
|
||||
for i in range(event.arg):
|
||||
pos = buffer.document.find_next_word_ending()
|
||||
words = buffer.document.text_after_cursor[:pos]
|
||||
buffer.insert_text(words.title(), overwrite=True)
|
||||
|
||||
@handle(Keys.Escape, 'd', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Delete word forwards.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
pos = buffer.document.find_next_word_ending(count=event.arg)
|
||||
|
||||
if pos:
|
||||
deleted = buffer.delete(count=pos)
|
||||
buffer.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.Escape, 'e', filter= ~has_selection)
|
||||
def _(event):
|
||||
""" Move to end of sentence. """
|
||||
# TODO:
|
||||
pass
|
||||
|
||||
@handle(Keys.Escape, 'f')
|
||||
def _(event):
|
||||
"""
|
||||
Cursor to end of next word.
|
||||
"""
|
||||
buffer= event.current_buffer
|
||||
pos = buffer.document.find_next_word_ending(count=event.arg)
|
||||
|
||||
if pos:
|
||||
buffer.cursor_position += pos
|
||||
|
||||
@handle(Keys.Escape, 'b')
|
||||
def _(event):
|
||||
"""
|
||||
Cursor to start of previous word.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
pos = buffer.document.find_previous_word_beginning(count=event.arg)
|
||||
if pos:
|
||||
buffer.cursor_position += pos
|
||||
|
||||
@handle(Keys.Escape, 'l', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Lowercase the current (or following) word.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
|
||||
for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!!
|
||||
pos = buffer.document.find_next_word_ending()
|
||||
words = buffer.document.text_after_cursor[:pos]
|
||||
buffer.insert_text(words.lower(), overwrite=True)
|
||||
|
||||
@handle(Keys.Escape, 't', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Swap the last two words before the cursor.
|
||||
"""
|
||||
# TODO
|
||||
|
||||
@handle(Keys.Escape, 'u', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Uppercase the current (or following) word.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
|
||||
for i in range(event.arg):
|
||||
pos = buffer.document.find_next_word_ending()
|
||||
words = buffer.document.text_after_cursor[:pos]
|
||||
buffer.insert_text(words.upper(), overwrite=True)
|
||||
|
||||
@handle(Keys.Escape, '.', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Rotate through the last word (white-space delimited) of the previous lines in history.
|
||||
"""
|
||||
# TODO
|
||||
|
||||
@handle(Keys.Escape, '\\', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Delete all spaces and tabs around point.
|
||||
(delete-horizontal-space)
|
||||
"""
|
||||
|
||||
@handle(Keys.Escape, '*', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
`meta-*`: Insert all possible completions of the preceding text.
|
||||
"""
|
||||
|
||||
@handle(Keys.ControlX, Keys.ControlE, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Open editor.
|
||||
"""
|
||||
event.current_buffer.open_in_editor()
|
||||
|
||||
@handle(Keys.ControlX, Keys.ControlU, save_before=False, filter= ~has_selection)
|
||||
def _(event):
|
||||
event.current_buffer.undo()
|
||||
|
||||
@handle(Keys.ControlX, Keys.ControlX)
|
||||
def _(event):
|
||||
"""
|
||||
Move cursor back and forth between the start and end of the current
|
||||
line.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
|
||||
if buffer.document.current_char == '\n':
|
||||
buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=False)
|
||||
else:
|
||||
buffer.cursor_position += buffer.document.get_end_of_line_position()
|
||||
|
||||
@handle(Keys.ControlSpace)
|
||||
def _(event):
|
||||
"""
|
||||
Start of the selection.
|
||||
"""
|
||||
# Take the current cursor position as the start of this selection.
|
||||
event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS)
|
||||
|
||||
@handle(Keys.ControlG, filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Control + G: Cancel completion menu and validation state.
|
||||
"""
|
||||
event.current_buffer.complete_state = None
|
||||
event.current_buffer.validation_error = None
|
||||
|
||||
@handle(Keys.ControlG, filter=has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Cancel selection.
|
||||
"""
|
||||
event.current_buffer.exit_selection()
|
||||
|
||||
@handle(Keys.ControlW, filter=has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Cut selected text.
|
||||
"""
|
||||
deleted = event.current_buffer.cut_selection()
|
||||
event.current_buffer.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.Escape, 'w', filter=has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Copy selected text.
|
||||
"""
|
||||
text = event.current_buffer.copy_selection()
|
||||
event.current_buffer.set_clipboard(ClipboardData(text))
|
||||
|
||||
@handle(Keys.Escape, '<', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Move to the first line in the history.
|
||||
"""
|
||||
event.current_buffer.go_to_history(0)
|
||||
|
||||
@handle(Keys.Escape, '>', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Move to the end of the input history.
|
||||
This is the line we are editing.
|
||||
"""
|
||||
buffer= event.current_buffer
|
||||
buffer.go_to_history(len(buffer._working_lines) - 1)
|
||||
|
||||
@handle(Keys.Escape, Keys.Left)
|
||||
def _(event):
|
||||
"""
|
||||
Cursor to start of previous word.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.find_previous_word_beginning(count=event.arg) or 0
|
||||
|
||||
@handle(Keys.Escape, Keys.Right)
|
||||
def _(event):
|
||||
"""
|
||||
Cursor to start of next word.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
buffer.cursor_position += buffer.document.find_next_word_beginning(count=event.arg) or \
|
||||
buffer.document.end_position
|
||||
|
||||
@handle(Keys.Escape, '/', filter= ~has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
M-/: Complete.
|
||||
"""
|
||||
event.current_buffer.complete_next()
|
||||
|
||||
@handle(Keys.ControlC, '>', filter=has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Indent selected text.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
|
||||
buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True)
|
||||
|
||||
from_, to = buffer.document.selection_range()
|
||||
from_, _ = buffer.document.translate_index_to_position(from_)
|
||||
to, _ = buffer.document.translate_index_to_position(to)
|
||||
|
||||
indent(buffer, from_ - 1, to, count=event.arg) # XXX: why does translate_index_to_position return 1-based indexing???
|
||||
|
||||
@handle(Keys.ControlC, '<', filter=has_selection)
|
||||
def _(event):
|
||||
"""
|
||||
Unindent selected text.
|
||||
"""
|
||||
buffer = event.current_buffer
|
||||
|
||||
from_, to = buffer.document.selection_range()
|
||||
from_, _ = buffer.document.translate_index_to_position(from_)
|
||||
to, _ = buffer.document.translate_index_to_position(to)
|
||||
|
||||
unindent(buffer, from_ - 1, to, count=event.arg)
|
||||
|
||||
|
||||
def load_emacs_system_bindings(registry, filter=None, system_buffer_name='system'):
|
||||
handle = create_handle_decorator(registry, filter)
|
||||
has_focus = filters.HasFocus(system_buffer_name)
|
||||
|
||||
@handle(Keys.Escape, '!', filter= ~has_focus)
|
||||
def _(event):
|
||||
"""
|
||||
M-'!' opens the system prompt.
|
||||
"""
|
||||
event.cli.focus_stack.push(system_buffer_name)
|
||||
|
||||
@handle(Keys.Escape, filter=has_focus)
|
||||
@handle(Keys.ControlG, filter=has_focus)
|
||||
def _(event):
|
||||
"""
|
||||
Cancel system prompt.
|
||||
"""
|
||||
event.cli.buffers[system_buffer_name].reset()
|
||||
event.cli.focus_stack.pop()
|
||||
|
||||
@handle(Keys.ControlJ, filter=has_focus)
|
||||
def _(event):
|
||||
"""
|
||||
Run system command.
|
||||
"""
|
||||
system_line = event.cli.buffers[system_buffer_name]
|
||||
event.cli.run_system_command(system_line.text)
|
||||
system_line.reset(append_to_history=True)
|
||||
|
||||
# Focus previous buffer again.
|
||||
event.cli.focus_stack.pop()
|
||||
|
||||
|
||||
def load_emacs_search_bindings(registry, filter=None, search_buffer_name='search'):
|
||||
handle = create_handle_decorator(registry, filter)
|
||||
has_focus = filters.HasFocus(search_buffer_name)
|
||||
|
||||
@handle(Keys.ControlG, filter=has_focus)
|
||||
# NOTE: the reason for not also binding Escape to this one, is that we want
|
||||
# Alt+Enter to accept input directly in incremental search mode.
|
||||
def _(event):
|
||||
"""
|
||||
Abort an incremental search and restore the original line.
|
||||
"""
|
||||
search_line = event.cli.buffers[search_buffer_name]
|
||||
|
||||
event.current_buffer.exit_isearch(restore_original_line=True)
|
||||
search_line.reset()
|
||||
|
||||
@handle(Keys.ControlH, filter=has_focus)
|
||||
@handle(Keys.Backspace, filter=has_focus)
|
||||
def _(event):
|
||||
search_line = event.cli.buffers[search_buffer_name]
|
||||
|
||||
search_line.delete_before_cursor()
|
||||
event.current_buffer.set_search_text(search_line.text)
|
||||
|
||||
@handle(Keys.Any, filter=has_focus)
|
||||
def _(event):
|
||||
"""
|
||||
Insert isearch string.
|
||||
"""
|
||||
# Insert text in search line.
|
||||
search_line = event.cli.buffers[search_buffer_name]
|
||||
search_line.insert_text(event.data)
|
||||
|
||||
# Set current search search text as line search text.
|
||||
buffer = event.cli.buffers[event.cli.focus_stack.previous]
|
||||
buffer.set_search_text(search_line.text)
|
||||
|
||||
@handle(Keys.ControlJ, filter=has_focus)
|
||||
def _(event):
|
||||
"""
|
||||
When enter pressed in isearch, quit isearch mode. (Multiline
|
||||
isearch would be too complicated.)
|
||||
"""
|
||||
search_line = event.cli.buffers[search_buffer_name]
|
||||
search_line.reset()
|
||||
|
||||
event.cli.buffers[event.cli.focus_stack.previous].exit_isearch()
|
||||
event.cli.focus_stack.pop()
|
||||
|
||||
@handle(Keys.ControlR, filter= ~has_focus)
|
||||
def _(event):
|
||||
event.cli.focus_stack.push(search_buffer_name)
|
||||
|
||||
buffer = event.cli.buffers[event.cli.focus_stack.previous]
|
||||
buffer.incremental_search(IncrementalSearchDirection.BACKWARD)
|
||||
|
||||
@handle(Keys.ControlS, filter= ~has_focus)
|
||||
def _(event):
|
||||
event.cli.focus_stack.push(search_buffer_name)
|
||||
|
||||
buffer = event.cli.buffers[event.cli.focus_stack.previous]
|
||||
buffer.incremental_search(IncrementalSearchDirection.FORWARD)
|
||||
|
||||
@handle(Keys.ControlR, filter=has_focus)
|
||||
@handle(Keys.Up, filter=has_focus)
|
||||
def _(event):
|
||||
buffer = event.cli.buffers[event.cli.focus_stack.previous]
|
||||
buffer.incremental_search(IncrementalSearchDirection.BACKWARD)
|
||||
|
||||
@handle(Keys.ControlS, filter=has_focus)
|
||||
@handle(Keys.Down, filter=has_focus)
|
||||
def _(event):
|
||||
buffer = event.cli.buffers[event.cli.focus_stack.previous]
|
||||
buffer.incremental_search(IncrementalSearchDirection.FORWARD)
|
|
@ -1,20 +1,29 @@
|
|||
from __future__ import unicode_literals
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def create_handle_decorator(registry, line):
|
||||
def create_handle_decorator(registry, filter):
|
||||
"""
|
||||
Create a key handle decorator, which is compatible with `Registry.handle`
|
||||
but has a `save_before` option, which will make sure that undo changes are
|
||||
saved to the undo stack of the `Line` object before every key press event.
|
||||
saved to the undo stack of the `Buffer` object before every key press
|
||||
event.
|
||||
"""
|
||||
def handle(*keys, **kw):
|
||||
safe_before = kw.pop('save_before', True)
|
||||
|
||||
# Chain the given filter to the filter of this specific binding.
|
||||
if 'filter' in kw:
|
||||
kw['filter'] = kw['filter'] & filter
|
||||
else:
|
||||
kw['filter'] = filter
|
||||
|
||||
def decorator(handler_func):
|
||||
@registry.add_binding(*keys, **kw)
|
||||
@wraps(handler_func)
|
||||
def wrapper(event):
|
||||
if safe_before:
|
||||
line.save_to_undo_stack()
|
||||
event.cli.current_buffer.save_to_undo_stack()
|
||||
handler_func(event)
|
||||
return handler_func
|
||||
return decorator
|
File diff suppressed because it is too large
Load Diff
|
@ -7,15 +7,13 @@ The `InputProcessor` will according to the implemented keybindings call the
|
|||
correct callbacks when new key presses are feed through `feed_key`.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from .keys import Keys
|
||||
from .enums import InputMode
|
||||
from ..keys import Keys
|
||||
|
||||
import weakref
|
||||
|
||||
__all__ = (
|
||||
'InputProcessor',
|
||||
'KeyPress',
|
||||
'Registry',
|
||||
)
|
||||
|
||||
|
||||
|
@ -51,16 +49,18 @@ class InputProcessor(object):
|
|||
# Now the ControlX-ControlC callback will be called if this sequence is
|
||||
# registered in the registry.
|
||||
|
||||
:param registry: `Registry` instance.
|
||||
:param cli_ref: weakref to `CommandLineInterface`.
|
||||
"""
|
||||
def __init__(self, registry):
|
||||
def __init__(self, registry, cli_ref):
|
||||
self._registry = registry
|
||||
self._cli_ref = cli_ref
|
||||
self.reset()
|
||||
|
||||
# print(' '.join(set(''.join(map(str, kb.keys)) for kb in registry.key_bindings if all(isinstance(X, unicode) for X in kb.keys))))
|
||||
|
||||
def reset(self, initial_input_mode=InputMode.INSERT):
|
||||
def reset(self):
|
||||
self._previous_key_sequence = None
|
||||
self._input_mode_stack = [initial_input_mode]
|
||||
|
||||
self._process_coroutine = self._process()
|
||||
self._process_coroutine.send(None)
|
||||
|
@ -69,33 +69,6 @@ class InputProcessor(object):
|
|||
#: https://www.gnu.org/software/bash/manual/html_node/Readline-Arguments.html
|
||||
self.arg = None
|
||||
|
||||
@property
|
||||
def input_mode(self):
|
||||
"""
|
||||
Current :class:`~prompt_toolkit.enums.InputMode`
|
||||
"""
|
||||
return self._input_mode_stack[-1]
|
||||
|
||||
@input_mode.setter
|
||||
def input_mode(self, value):
|
||||
self._input_mode_stack[-1] = value
|
||||
|
||||
def push_input_mode(self, value):
|
||||
"""
|
||||
Push new :class:`~prompt_toolkit.enums.InputMode` on the stack.
|
||||
"""
|
||||
self._input_mode_stack.append(value)
|
||||
|
||||
def pop_input_mode(self):
|
||||
"""
|
||||
Push new :class:`~prompt_toolkit.enums.InputMode` on the stack.
|
||||
"""
|
||||
# You can't pop the last item.
|
||||
if len(self._input_mode_stack) > 1:
|
||||
return self._input_mode_stack.pop()
|
||||
else:
|
||||
raise IndexError('Cannot pop the last InputMode.')
|
||||
|
||||
def _get_matches(self, key_presses):
|
||||
"""
|
||||
For a list of :class:`KeyPress` instances. Give the matching handlers
|
||||
|
@ -105,27 +78,17 @@ class InputProcessor(object):
|
|||
bindings = self._registry.key_bindings
|
||||
|
||||
# Try match, with mode flag
|
||||
with_mode = [b for b in bindings if b.keys == keys and b.input_mode == self.input_mode]
|
||||
with_mode = [b for b in bindings if b.keys == keys and b.filter(self._cli_ref())]
|
||||
if with_mode:
|
||||
return with_mode
|
||||
|
||||
# Try match without mode.
|
||||
without_mode = [b for b in bindings if b.keys == keys and b.input_mode is None]
|
||||
if without_mode:
|
||||
return without_mode
|
||||
|
||||
# Try match, where the last key is replaced with 'Any', with mode.
|
||||
keys_any = tuple(keys[:-1] + (Keys.Any,))
|
||||
|
||||
with_mode_any = [b for b in bindings if b.keys == keys_any and b.input_mode == self.input_mode]
|
||||
with_mode_any = [b for b in bindings if b.keys == keys_any and b.filter(self._cli_ref())]
|
||||
if with_mode_any:
|
||||
return with_mode_any
|
||||
|
||||
# Try match, where the last key is replaced with 'Any', without mode.
|
||||
without_mode_any = [b for b in bindings if b.keys == keys_any and b.input_mode is None]
|
||||
if without_mode_any:
|
||||
return without_mode_any
|
||||
|
||||
return []
|
||||
|
||||
def _is_prefix_of_longer_match(self, key_presses):
|
||||
|
@ -136,7 +99,7 @@ class InputProcessor(object):
|
|||
keys = [k.key for k in key_presses]
|
||||
|
||||
for b in self._registry.key_bindings:
|
||||
if b.input_mode in (None, self.input_mode):
|
||||
if b.filter(self._cli_ref()):
|
||||
if len(b.keys) > len(keys) and list(b.keys[:len(key_presses)]) == keys:
|
||||
return True
|
||||
|
||||
|
@ -191,9 +154,7 @@ class InputProcessor(object):
|
|||
event = Event(weakref.ref(self), arg=arg, key_sequence=key_sequence,
|
||||
previous_key_sequence=self._previous_key_sequence)
|
||||
handler.call(event)
|
||||
|
||||
for h in self._registry.after_handler_callbacks:
|
||||
h(event)
|
||||
self._registry.onHandlerCalled.fire(event)
|
||||
|
||||
self._previous_key_sequence = key_sequence
|
||||
|
||||
|
@ -213,6 +174,20 @@ class Event(object):
|
|||
def input_processor(self):
|
||||
return self._input_processor_ref()
|
||||
|
||||
@property
|
||||
def cli(self):
|
||||
"""
|
||||
Command line interface.
|
||||
"""
|
||||
return self.input_processor._cli_ref()
|
||||
|
||||
@property
|
||||
def current_buffer(self):
|
||||
"""
|
||||
The current buffer.
|
||||
"""
|
||||
return self.cli.current_buffer
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self._arg or 1
|
||||
|
@ -242,50 +217,3 @@ class Event(object):
|
|||
result = None
|
||||
|
||||
self.input_processor.arg = result
|
||||
|
||||
|
||||
class _Binding(object):
|
||||
def __init__(self, keys, callable, input_mode=None):
|
||||
self.keys = keys
|
||||
self._callable = callable
|
||||
self.input_mode = input_mode
|
||||
|
||||
def call(self, event):
|
||||
return self._callable(event)
|
||||
|
||||
def __repr__(self):
|
||||
return '_Binding(keys=%r, callable=%r)' % (self.keys, self._callable)
|
||||
|
||||
|
||||
class Registry(object):
|
||||
"""
|
||||
Key binding registry.
|
||||
|
||||
::
|
||||
|
||||
r = Registry()
|
||||
|
||||
@r.add_binding(Keys.ControlX, Keys.ControlC, in_mode=INSERT)
|
||||
def handler(event):
|
||||
# Handle ControlX-ControlC key sequence.
|
||||
pass
|
||||
"""
|
||||
def __init__(self):
|
||||
self.key_bindings = []
|
||||
self.after_handler_callbacks = []
|
||||
|
||||
def add_binding(self, *keys, **kwargs):
|
||||
"""
|
||||
Decorator for annotating key bindings.
|
||||
"""
|
||||
input_mode = kwargs.pop('in_mode', None)
|
||||
assert not kwargs
|
||||
assert keys
|
||||
|
||||
def decorator(func):
|
||||
self.key_bindings.append(_Binding(keys, func, input_mode=input_mode))
|
||||
return func
|
||||
return decorator
|
||||
|
||||
def add_after_handler_callback(self, callback):
|
||||
self.after_handler_callbacks.append(callback)
|
|
@ -0,0 +1,81 @@
|
|||
"""
|
||||
:class:`KeyBindingManager` is a utility (or shortcut) for loading all the key
|
||||
bindings in a key binding registry, with a logic set of filters to quickly to
|
||||
quickly change from Vi to Emacs key bindings at runtime.
|
||||
|
||||
You don't have to use this, but it's practical.
|
||||
|
||||
Usage::
|
||||
|
||||
manager = KeyBindingManager()
|
||||
cli = CommandLineInterface(key_bindings_registry=manager.registry)
|
||||
manager.enable_vi_mode = True
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
from prompt_toolkit.filters import Filter
|
||||
from prompt_toolkit.key_binding.registry import Registry
|
||||
from prompt_toolkit.key_binding.vi_state import ViState
|
||||
from prompt_toolkit.key_binding.bindings.emacs import load_emacs_bindings, load_emacs_system_bindings, load_emacs_search_bindings
|
||||
from prompt_toolkit.key_binding.bindings.vi import load_vi_bindings, load_vi_system_bindings, load_vi_search_bindings
|
||||
|
||||
__all__ = (
|
||||
'KeyBindingManager',
|
||||
)
|
||||
|
||||
|
||||
class ManagerFilter(Filter):
|
||||
def __init__(self, manager):
|
||||
self.manager = manager
|
||||
|
||||
|
||||
class ViModeEnabled(ManagerFilter):
|
||||
def __call__(self, cli):
|
||||
return self.manager.enable_vi_mode
|
||||
|
||||
|
||||
class SystemPromptEnabled(ManagerFilter):
|
||||
def __call__(self, cli):
|
||||
return self.manager.enable_system_prompt
|
||||
|
||||
|
||||
class SearchEnabled(ManagerFilter):
|
||||
def __call__(self, cli):
|
||||
return self.manager.enable_search
|
||||
|
||||
|
||||
class KeyBindingManager(object):
|
||||
def __init__(self, registry=None, enable_vi_mode=False,
|
||||
enable_system_prompt=False, enable_search=True):
|
||||
|
||||
self.registry = registry or Registry()
|
||||
|
||||
# Flags. You can change these anytime.
|
||||
self.enable_vi_mode = enable_vi_mode
|
||||
self.enable_system_prompt = enable_system_prompt
|
||||
self.enable_search = enable_search
|
||||
|
||||
# Create set of filters to enable/disable sets of key bindings at
|
||||
# runtime.
|
||||
vi_mode_enabled = ViModeEnabled(self)
|
||||
emacs_mode_enabled = ~ vi_mode_enabled
|
||||
system_prompt_enabled = SystemPromptEnabled(self)
|
||||
search_enabled = SearchEnabled(self)
|
||||
|
||||
# Vi state. (Object to keep track of in which Vi mode we are.)
|
||||
self.vi_state = ViState()
|
||||
|
||||
# Load all bindings in the registry with the correct filters.
|
||||
load_emacs_bindings(self.registry, emacs_mode_enabled)
|
||||
load_emacs_search_bindings(self.registry,
|
||||
emacs_mode_enabled & search_enabled)
|
||||
load_emacs_system_bindings(self.registry,
|
||||
emacs_mode_enabled & system_prompt_enabled)
|
||||
|
||||
load_vi_bindings(self.registry, self.vi_state, vi_mode_enabled)
|
||||
load_vi_search_bindings(self.registry, self.vi_state,
|
||||
vi_mode_enabled & search_enabled)
|
||||
load_vi_system_bindings(self.registry, self.vi_state,
|
||||
vi_mode_enabled & system_prompt_enabled)
|
||||
|
||||
def reset(self):
|
||||
self.vi_state.reset()
|
|
@ -0,0 +1,53 @@
|
|||
from __future__ import unicode_literals
|
||||
from ..filters import NoFilter, Filter
|
||||
from ..utils import EventHook
|
||||
|
||||
__all__ = (
|
||||
'Registry',
|
||||
)
|
||||
|
||||
|
||||
class _Binding(object):
|
||||
def __init__(self, keys, callable, filter=None):
|
||||
self.keys = keys
|
||||
self._callable = callable
|
||||
self.filter = filter
|
||||
|
||||
def call(self, event):
|
||||
return self._callable(event)
|
||||
|
||||
def __repr__(self):
|
||||
return '_Binding(keys=%r, callable=%r)' % (self.keys, self._callable)
|
||||
|
||||
|
||||
|
||||
class Registry(object):
|
||||
"""
|
||||
Key binding registry.
|
||||
|
||||
::
|
||||
|
||||
r = Registry()
|
||||
|
||||
@r.add_binding(Keys.ControlX, Keys.ControlC, filter=INSERT)
|
||||
def handler(event):
|
||||
# Handle ControlX-ControlC key sequence.
|
||||
pass
|
||||
"""
|
||||
def __init__(self):
|
||||
self.key_bindings = []
|
||||
self.onHandlerCalled = EventHook()
|
||||
|
||||
def add_binding(self, *keys, **kwargs):
|
||||
"""
|
||||
Decorator for annotating key bindings.
|
||||
"""
|
||||
filter = kwargs.pop('filter', None) or NoFilter()
|
||||
assert not kwargs
|
||||
assert keys
|
||||
assert isinstance(filter, Filter), 'Expected Filter instance, got %r' % filter
|
||||
|
||||
def decorator(func):
|
||||
self.key_bindings.append(_Binding(keys, func, filter=filter))
|
||||
return func
|
||||
return decorator
|
|
@ -0,0 +1,39 @@
|
|||
from __future__ import unicode_literals
|
||||
from ..enums import IncrementalSearchDirection
|
||||
|
||||
__all__ = (
|
||||
'InputMode',
|
||||
'CharacterFind',
|
||||
'ViState',
|
||||
)
|
||||
|
||||
|
||||
class InputMode(object):
|
||||
INSERT = 'vi-insert'
|
||||
NAVIGATION = 'vi-navigation'
|
||||
REPLACE = 'vi-replace'
|
||||
|
||||
|
||||
class CharacterFind(object):
|
||||
def __init__(self, character, backwards=False):
|
||||
self.character = character
|
||||
self.backwards = backwards
|
||||
|
||||
|
||||
class ViState(object):
|
||||
"""
|
||||
Mutable class to hold the state of the Vi navigation.
|
||||
"""
|
||||
def __init__(self):
|
||||
#: None or CharacterFind instance. (This is used to repeat the last
|
||||
#: search in Vi mode, by pressing the 'n' or 'N' in navigation mode.)
|
||||
self.last_character_find = None
|
||||
|
||||
self.search_direction = IncrementalSearchDirection.FORWARD
|
||||
|
||||
#: The Vi mode we're currently in to.
|
||||
self.input_mode = InputMode.INSERT
|
||||
|
||||
def reset(self):
|
||||
# Go back to insert mode.
|
||||
self.input_mode = InputMode.INSERT
|
|
@ -1,333 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from ..keys import Keys
|
||||
from ..enums import InputMode
|
||||
from ..line import ClipboardData
|
||||
|
||||
from .utils import create_handle_decorator
|
||||
|
||||
|
||||
def basic_bindings(registry, cli_ref):
|
||||
cli = cli_ref()
|
||||
line = cli_ref().line
|
||||
system_line = cli_ref().lines['system']
|
||||
renderer = cli_ref().renderer
|
||||
handle = create_handle_decorator(registry, line)
|
||||
|
||||
@handle(Keys.ControlA)
|
||||
@handle(Keys.ControlB)
|
||||
@handle(Keys.ControlC)
|
||||
@handle(Keys.ControlD)
|
||||
@handle(Keys.ControlE)
|
||||
@handle(Keys.ControlF)
|
||||
@handle(Keys.ControlG)
|
||||
@handle(Keys.ControlH)
|
||||
@handle(Keys.ControlI)
|
||||
@handle(Keys.ControlJ)
|
||||
@handle(Keys.ControlK)
|
||||
@handle(Keys.ControlL)
|
||||
@handle(Keys.ControlM)
|
||||
@handle(Keys.ControlN)
|
||||
@handle(Keys.ControlO)
|
||||
@handle(Keys.ControlP)
|
||||
@handle(Keys.ControlQ)
|
||||
@handle(Keys.ControlR)
|
||||
@handle(Keys.ControlS)
|
||||
@handle(Keys.ControlT)
|
||||
@handle(Keys.ControlU)
|
||||
@handle(Keys.ControlV)
|
||||
@handle(Keys.ControlW)
|
||||
@handle(Keys.ControlX)
|
||||
@handle(Keys.ControlY)
|
||||
@handle(Keys.ControlZ)
|
||||
@handle(Keys.F1)
|
||||
@handle(Keys.F2)
|
||||
@handle(Keys.F3)
|
||||
@handle(Keys.F4)
|
||||
@handle(Keys.F5)
|
||||
@handle(Keys.F6)
|
||||
@handle(Keys.F7)
|
||||
@handle(Keys.F8)
|
||||
@handle(Keys.F9)
|
||||
@handle(Keys.F10)
|
||||
@handle(Keys.F11)
|
||||
@handle(Keys.F12)
|
||||
@handle(Keys.F13)
|
||||
@handle(Keys.F14)
|
||||
@handle(Keys.F15)
|
||||
@handle(Keys.F16)
|
||||
@handle(Keys.F17)
|
||||
@handle(Keys.F18)
|
||||
@handle(Keys.F19)
|
||||
@handle(Keys.F20)
|
||||
@handle(Keys.ControlSpace)
|
||||
@handle(Keys.ControlBackslash)
|
||||
@handle(Keys.ControlSquareClose)
|
||||
@handle(Keys.ControlCircumflex)
|
||||
@handle(Keys.ControlUnderscore)
|
||||
@handle(Keys.Backspace)
|
||||
@handle(Keys.Up)
|
||||
@handle(Keys.Down)
|
||||
@handle(Keys.Right)
|
||||
@handle(Keys.Left)
|
||||
@handle(Keys.Home)
|
||||
@handle(Keys.End)
|
||||
@handle(Keys.Delete)
|
||||
@handle(Keys.ShiftDelete)
|
||||
@handle(Keys.PageUp)
|
||||
@handle(Keys.PageDown)
|
||||
@handle(Keys.BackTab)
|
||||
@handle(Keys.Tab)
|
||||
def _(event):
|
||||
"""
|
||||
First, for any of these keys, Don't do anything by default. Also don't
|
||||
catch them in the 'Any' handler which will insert them as data.
|
||||
|
||||
If people want to insert these characters as a literal, they can always
|
||||
do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi
|
||||
mode.)
|
||||
"""
|
||||
pass
|
||||
|
||||
@handle(Keys.Home, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Home, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
line.cursor_position += line.document.home_position
|
||||
|
||||
@handle(Keys.End, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.End, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
line.cursor_position += line.document.end_position
|
||||
|
||||
# CTRL keys.
|
||||
|
||||
@handle(Keys.ControlC)
|
||||
def _(event):
|
||||
cli.set_abort()
|
||||
|
||||
@handle(Keys.ControlD)
|
||||
def _(event):
|
||||
# When there is text, act as delete, otherwise call exit.
|
||||
if line.text:
|
||||
line.delete()
|
||||
else:
|
||||
cli.set_exit()
|
||||
|
||||
@handle(Keys.ControlI, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
r"""
|
||||
Ctrl-I is identical to "\t"
|
||||
|
||||
Traditional tab-completion, where the first tab completes the common
|
||||
suffix and the second tab lists all the completions.
|
||||
"""
|
||||
def second_tab():
|
||||
line.complete_next(start_at_first=False)
|
||||
|
||||
# On the second tab-press, or when already navigating through
|
||||
# completions.
|
||||
if event.second_press or (line.complete_state and line.complete_state.complete_index is not None):
|
||||
second_tab()
|
||||
else:
|
||||
# On the first tab press, only complete the common parts of all completions.
|
||||
has_common = line.complete_common()
|
||||
if not has_common:
|
||||
second_tab()
|
||||
|
||||
@handle(Keys.BackTab, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Shift+Tab: go to previous completion.
|
||||
"""
|
||||
line.complete_previous()
|
||||
|
||||
@handle(Keys.ControlJ, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlM, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Newline/Enter. (Or return input.)
|
||||
"""
|
||||
if line.is_multiline:
|
||||
line.newline()
|
||||
else:
|
||||
if line.validate():
|
||||
cli_ref().line.add_to_history()
|
||||
cli_ref().set_return_value(line.document)
|
||||
|
||||
@handle(Keys.ControlK, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
deleted = line.delete(count=line.document.get_end_of_line_position())
|
||||
line.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.ControlL)
|
||||
def _(event):
|
||||
renderer.clear()
|
||||
|
||||
@handle(Keys.ControlT, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
line.swap_characters_before_cursor()
|
||||
|
||||
@handle(Keys.ControlU, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Clears the line before the cursor position. If you are at the end of
|
||||
the line, clears the entire line.
|
||||
"""
|
||||
deleted = line.delete_before_cursor(count=-line.document.get_start_of_line_position())
|
||||
line.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.ControlW, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Delete the word before the cursor.
|
||||
"""
|
||||
pos = line.document.find_start_of_previous_word(count=event.arg)
|
||||
if pos:
|
||||
deleted = line.delete_before_cursor(count=-pos)
|
||||
line.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.PageUp, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
line.history_backward()
|
||||
|
||||
@handle(Keys.PageDown, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
line.history_forward()
|
||||
|
||||
@handle(Keys.Left, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Left, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
line.cursor_position += line.document.get_cursor_left_position(count=event.arg)
|
||||
|
||||
@handle(Keys.Right, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Right, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
line.cursor_position += line.document.get_cursor_right_position(count=event.arg)
|
||||
|
||||
@handle(Keys.Up, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
line.auto_up(count=event.arg)
|
||||
|
||||
@handle(Keys.Down, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
line.auto_down(count=event.arg)
|
||||
|
||||
@handle(Keys.Up, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
line.cursor_up(count=event.arg)
|
||||
|
||||
@handle(Keys.Down, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
line.cursor_down(count=event.arg)
|
||||
|
||||
@handle(Keys.ControlH, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Backspace, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
line.delete_before_cursor(count=event.arg)
|
||||
|
||||
@handle(Keys.Delete, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
line.delete(count=event.arg)
|
||||
|
||||
@handle(Keys.ShiftDelete, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
line.delete(count=event.arg)
|
||||
|
||||
@handle(Keys.Any, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Insert data at cursor position.
|
||||
"""
|
||||
line.insert_text(event.data * event.arg)
|
||||
|
||||
@handle(Keys.CPRResponse)
|
||||
def _(event):
|
||||
"""
|
||||
Handle incoming Cursor-Position-Request response.
|
||||
"""
|
||||
# The incoming data looks like u'\x1b[35;1R'
|
||||
# Parse row/col information.
|
||||
row, col = map(int, event.data[2:-1].split(';'))
|
||||
|
||||
# Report absolute cursor position to the renderer.
|
||||
cli_ref().renderer.report_absolute_cursor_row(row)
|
||||
|
||||
@handle(Keys.Up, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Previous history item at system prompt.
|
||||
"""
|
||||
system_line.auto_up()
|
||||
|
||||
@handle(Keys.Down, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Next history item at system prompt.
|
||||
"""
|
||||
system_line.auto_down()
|
||||
|
||||
@handle(Keys.Left, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Arrow left at the system prompt.
|
||||
"""
|
||||
system_line.cursor_left()
|
||||
|
||||
@handle(Keys.Right, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Arrow right at the system prompt.
|
||||
"""
|
||||
system_line.cursor_right()
|
||||
|
||||
@handle(Keys.ControlC, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Cancel system prompt.
|
||||
"""
|
||||
system_line.reset()
|
||||
event.input_processor.pop_input_mode()
|
||||
|
||||
@handle(Keys.ControlM, in_mode=InputMode.SYSTEM)
|
||||
@handle(Keys.ControlJ, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Run system command.
|
||||
"""
|
||||
cli_ref().run_system_command(system_line.text)
|
||||
system_line.add_to_history()
|
||||
system_line.reset()
|
||||
event.input_processor.pop_input_mode()
|
||||
|
||||
@handle(Keys.Backspace, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Backspace at the system prompt.
|
||||
"""
|
||||
if system_line.text:
|
||||
system_line.delete_before_cursor()
|
||||
else:
|
||||
# If no text after the prompt, cancel.
|
||||
system_line.reset()
|
||||
event.input_processor.pop_input_mode()
|
||||
|
||||
@handle(Keys.Delete, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Delete at the system prompt.
|
||||
"""
|
||||
system_line.delete()
|
||||
|
||||
@handle(Keys.Any, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Insert text after the system prompt.
|
||||
"""
|
||||
system_line.insert_text(event.data)
|
||||
|
||||
@handle(Keys.ControlZ)
|
||||
def _(event):
|
||||
"""
|
||||
Suspend process to background.
|
||||
"""
|
||||
cli_ref().suspend_to_background()
|
|
@ -1,490 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from ..line import ClipboardData, SelectionType, indent, unindent
|
||||
from ..keys import Keys
|
||||
from ..enums import InputMode, IncrementalSearchDirection
|
||||
|
||||
from .basic import basic_bindings
|
||||
from .utils import create_handle_decorator
|
||||
|
||||
|
||||
def emacs_bindings(registry, cli_ref):
|
||||
"""
|
||||
Some e-macs extensions.
|
||||
"""
|
||||
# Overview of Readline emacs commands:
|
||||
# http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf
|
||||
basic_bindings(registry, cli_ref)
|
||||
line = cli_ref().line
|
||||
search_line = cli_ref().lines['search']
|
||||
system_line = cli_ref().lines['system']
|
||||
handle = create_handle_decorator(registry, line)
|
||||
|
||||
@handle(Keys.Escape)
|
||||
def _(event):
|
||||
"""
|
||||
By default, ignore escape key.
|
||||
|
||||
(If we don't put this here, and Esc is followed by a key which sequence
|
||||
is not handled, we'll insert an Escape character in the input stream.
|
||||
Something we don't want and happens to easily in emacs mode.
|
||||
Further, people can always use ControlQ to do a quoted insert.)
|
||||
"""
|
||||
pass
|
||||
|
||||
@handle(Keys.ControlA, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlA, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Start of line.
|
||||
"""
|
||||
line.cursor_position += line.document.get_start_of_line_position(after_whitespace=False)
|
||||
|
||||
@handle(Keys.ControlA, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Start of system line.
|
||||
"""
|
||||
system_line.cursor_position += system_line.document.get_start_of_line_position(after_whitespace=False)
|
||||
|
||||
@handle(Keys.ControlB, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlB, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Character back.
|
||||
"""
|
||||
line.cursor_position += line.document.get_cursor_left_position(count=event.arg)
|
||||
|
||||
@handle(Keys.ControlE, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlE, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
End of line.
|
||||
"""
|
||||
line.cursor_position += line.document.get_end_of_line_position()
|
||||
|
||||
@handle(Keys.ControlE, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
End of "system" line.
|
||||
"""
|
||||
system_line.cursor_position += system_line.document.get_end_of_line_position()
|
||||
|
||||
@handle(Keys.ControlF, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlF, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Character forward.
|
||||
"""
|
||||
line.cursor_position += line.document.get_cursor_right_position(count=event.arg)
|
||||
|
||||
@handle(Keys.ControlN, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Next line.
|
||||
"""
|
||||
line.auto_down()
|
||||
|
||||
@handle(Keys.ControlN, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Next line.
|
||||
"""
|
||||
line.cursor_down()
|
||||
|
||||
@handle(Keys.ControlO, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Insert newline, but don't move the cursor.
|
||||
"""
|
||||
line.insert_text('\n', move_cursor=False)
|
||||
|
||||
@handle(Keys.ControlP, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Previous line.
|
||||
"""
|
||||
line.auto_up()
|
||||
|
||||
@handle(Keys.ControlP, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Previous line.
|
||||
"""
|
||||
line.cursor_up()
|
||||
|
||||
@handle(Keys.ControlQ, Keys.Any, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Quoted insert.
|
||||
"""
|
||||
line.insert_text(event.data, overwrite=False)
|
||||
|
||||
@handle(Keys.ControlY, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Paste before cursor.
|
||||
"""
|
||||
for i in range(event.arg):
|
||||
line.paste_from_clipboard(before=True)
|
||||
|
||||
@handle(Keys.ControlUnderscore, save_before=False, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Undo.
|
||||
"""
|
||||
line.undo()
|
||||
|
||||
def handle_digit(c):
|
||||
"""
|
||||
Handle Alt + digit in the `meta_digit` method.
|
||||
"""
|
||||
@handle(Keys.Escape, c, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Escape, c, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
event.append_to_arg_count(c)
|
||||
|
||||
for c in '0123456789':
|
||||
handle_digit(c)
|
||||
|
||||
@handle(Keys.Escape, '-', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
"""
|
||||
if event._arg is None:
|
||||
event.append_to_arg_count('-')
|
||||
|
||||
@handle(Keys.Escape, Keys.ControlJ, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Escape, Keys.ControlM, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Escape, Keys.ControlJ, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
@handle(Keys.Escape, Keys.ControlM, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
def _(event):
|
||||
"""
|
||||
Meta + Newline: always accept input.
|
||||
"""
|
||||
if line.validate():
|
||||
cli_ref().line.add_to_history()
|
||||
cli_ref().set_return_value(line.document)
|
||||
|
||||
@handle(Keys.ControlSquareClose, Keys.Any, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlSquareClose, Keys.Any, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
When Ctl-] + a character is pressed. go to that character.
|
||||
"""
|
||||
match = line.document.find(event.data, in_current_line=True, count=(event.arg))
|
||||
if match is not None:
|
||||
line.cursor_position += match
|
||||
|
||||
@handle(Keys.Escape, Keys.Backspace, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Delete word backwards.
|
||||
"""
|
||||
pos = line.document.find_start_of_previous_word(count=event.arg)
|
||||
if pos:
|
||||
deleted = line.delete_before_cursor(count=-pos)
|
||||
line.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.Escape, 'a', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Previous sentence.
|
||||
"""
|
||||
# TODO:
|
||||
pass
|
||||
|
||||
@handle(Keys.Escape, 'c', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Capitalize the current (or following) word.
|
||||
"""
|
||||
for i in range(event.arg):
|
||||
pos = line.document.find_next_word_ending()
|
||||
words = line.document.text_after_cursor[:pos]
|
||||
line.insert_text(words.title(), overwrite=True)
|
||||
|
||||
@handle(Keys.Escape, 'd', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Delete word forwards.
|
||||
"""
|
||||
pos = line.document.find_next_word_ending(count=event.arg)
|
||||
if pos:
|
||||
deleted = line.delete(count=pos)
|
||||
line.set_clipboard(ClipboardData(deleted))
|
||||
|
||||
@handle(Keys.Escape, 'e', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
""" Move to end of sentence. """
|
||||
# TODO:
|
||||
pass
|
||||
|
||||
@handle(Keys.Escape, 'f', in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Escape, 'f', in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Cursor to end of next word.
|
||||
"""
|
||||
pos = line.document.find_next_word_ending(count=event.arg)
|
||||
if pos:
|
||||
line.cursor_position += pos
|
||||
|
||||
@handle(Keys.Escape, 'b', in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Escape, 'b', in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Cursor to start of previous word.
|
||||
"""
|
||||
pos = line.document.find_previous_word_beginning(count=event.arg)
|
||||
if pos:
|
||||
line.cursor_position += pos
|
||||
|
||||
@handle(Keys.Escape, 'l', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Lowercase the current (or following) word.
|
||||
"""
|
||||
for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!!
|
||||
pos = line.document.find_next_word_ending()
|
||||
words = line.document.text_after_cursor[:pos]
|
||||
line.insert_text(words.lower(), overwrite=True)
|
||||
|
||||
@handle(Keys.Escape, 't', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Swap the last two words before the cursor.
|
||||
"""
|
||||
# TODO
|
||||
|
||||
@handle(Keys.Escape, 'u', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Uppercase the current (or following) word.
|
||||
"""
|
||||
for i in range(event.arg):
|
||||
pos = line.document.find_next_word_ending()
|
||||
words = line.document.text_after_cursor[:pos]
|
||||
line.insert_text(words.upper(), overwrite=True)
|
||||
|
||||
@handle(Keys.Escape, '.', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Rotate through the last word (white-space delimited) of the previous lines in history.
|
||||
"""
|
||||
# TODO
|
||||
|
||||
@handle(Keys.Escape, '\\', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Delete all spaces and tabs around point.
|
||||
(delete-horizontal-space)
|
||||
"""
|
||||
|
||||
@handle(Keys.Escape, '*', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
`meta-*`: Insert all possible completions of the preceding text.
|
||||
"""
|
||||
|
||||
@handle(Keys.ControlX, Keys.ControlE, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Open editor.
|
||||
"""
|
||||
line.open_in_editor()
|
||||
|
||||
@handle(Keys.ControlX, Keys.ControlU, save_before=False, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
line.undo()
|
||||
|
||||
@handle(Keys.ControlX, Keys.ControlX, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlX, Keys.ControlX, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Move cursor back and forth between the start and end of the current
|
||||
line.
|
||||
"""
|
||||
if line.document.current_char == '\n':
|
||||
line.cursor_position += line.document.get_start_of_line_position(after_whitespace=False)
|
||||
else:
|
||||
line.cursor_position += line.document.get_end_of_line_position()
|
||||
|
||||
@handle(Keys.ControlSpace, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlSpace, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Start of the selection.
|
||||
"""
|
||||
# Take the current cursor position as the start of this selection.
|
||||
line.start_selection(selection_type=SelectionType.CHARACTERS)
|
||||
|
||||
if event.input_processor.input_mode != InputMode.SELECTION:
|
||||
event.input_processor.push_input_mode(InputMode.SELECTION)
|
||||
|
||||
@handle(Keys.ControlG, in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Control + G: Cancel completion menu and validation state.
|
||||
"""
|
||||
line.complete_state = None
|
||||
line.validation_error = None
|
||||
|
||||
@handle(Keys.ControlG, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Cancel selection.
|
||||
"""
|
||||
event.input_processor.pop_input_mode()
|
||||
line.exit_selection()
|
||||
|
||||
@handle(Keys.ControlG, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
# NOTE: the reason for not also binding Escape to this one, is that we want
|
||||
# Alt+Enter to accept input directly in incremental search mode.
|
||||
def _(event):
|
||||
"""
|
||||
Abort an incremental search and restore the original line.
|
||||
"""
|
||||
line.exit_isearch(restore_original_line=True)
|
||||
event.input_processor.pop_input_mode()
|
||||
search_line.reset()
|
||||
|
||||
@handle(Keys.ControlG, in_mode=InputMode.SYSTEM)
|
||||
def _(event):
|
||||
"""
|
||||
Abort system prompt.
|
||||
"""
|
||||
system_line.reset()
|
||||
event.input_processor.pop_input_mode()
|
||||
|
||||
@handle(Keys.ControlW, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Cut selected text.
|
||||
"""
|
||||
deleted = line.cut_selection()
|
||||
line.set_clipboard(ClipboardData(deleted))
|
||||
event.input_processor.pop_input_mode()
|
||||
|
||||
@handle(Keys.Escape, 'w', in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Copy selected text.
|
||||
"""
|
||||
text = line.copy_selection()
|
||||
line.set_clipboard(ClipboardData(text))
|
||||
event.input_processor.pop_input_mode()
|
||||
|
||||
@handle(Keys.Escape, '<', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Move to the first line in the history.
|
||||
"""
|
||||
line.go_to_history(0)
|
||||
|
||||
@handle(Keys.Escape, '>', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
Move to the end of the input history.
|
||||
This is the line we are editing.
|
||||
"""
|
||||
line.go_to_history(len(line._working_lines) - 1)
|
||||
|
||||
@handle(Keys.ControlH, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
@handle(Keys.Backspace, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
def _(event):
|
||||
search_line.delete_before_cursor()
|
||||
line.set_search_text(search_line.text)
|
||||
|
||||
@handle(Keys.Any, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
def _(event):
|
||||
"""
|
||||
Insert isearch string.
|
||||
"""
|
||||
search_line.insert_text(event.data)
|
||||
line.set_search_text(search_line.text)
|
||||
|
||||
@handle(Keys.ControlJ, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
@handle(Keys.ControlM, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
def _(event):
|
||||
"""
|
||||
When enter pressed in isearch, quit isearch mode. (Multiline
|
||||
isearch would be too complicated.)
|
||||
"""
|
||||
search_line.reset()
|
||||
line.exit_isearch()
|
||||
event.input_processor.pop_input_mode()
|
||||
|
||||
@handle(Keys.ControlR, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlR, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
@handle(Keys.Up, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
def _(event):
|
||||
line.incremental_search(IncrementalSearchDirection.BACKWARD)
|
||||
|
||||
if event.input_processor.input_mode != InputMode.INCREMENTAL_SEARCH:
|
||||
event.input_processor.push_input_mode(InputMode.INCREMENTAL_SEARCH)
|
||||
|
||||
@handle(Keys.ControlS, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.ControlS, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
@handle(Keys.Down, in_mode=InputMode.INCREMENTAL_SEARCH)
|
||||
def _(event):
|
||||
line.incremental_search(IncrementalSearchDirection.FORWARD)
|
||||
|
||||
if event.input_processor.input_mode != InputMode.INCREMENTAL_SEARCH:
|
||||
event.input_processor.push_input_mode(InputMode.INCREMENTAL_SEARCH)
|
||||
|
||||
@handle(Keys.Escape, Keys.Left, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Escape, Keys.Left, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Cursor to start of previous word.
|
||||
"""
|
||||
line.cursor_position += line.document.find_previous_word_beginning(count=event.arg) or 0
|
||||
|
||||
@handle(Keys.Escape, Keys.Right, in_mode=InputMode.INSERT)
|
||||
@handle(Keys.Escape, Keys.Right, in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Cursor to start of next word.
|
||||
"""
|
||||
line.cursor_position += line.document.find_next_word_beginning(count=event.arg) or \
|
||||
line.document.end_position
|
||||
|
||||
@handle(Keys.Escape, '/', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
M-/: Complete.
|
||||
"""
|
||||
line.complete_next()
|
||||
|
||||
@handle(Keys.Escape, '!', in_mode=InputMode.INSERT)
|
||||
def _(event):
|
||||
"""
|
||||
M-'!' opens the system prompt.
|
||||
"""
|
||||
event.input_processor.push_input_mode(InputMode.SYSTEM)
|
||||
|
||||
@handle(Keys.ControlC, '>', in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Indent selected text.
|
||||
"""
|
||||
line.cursor_position += line.document.get_start_of_line_position(after_whitespace=True)
|
||||
|
||||
from_, to = line.document.selection_range()
|
||||
from_, _ = line.document.translate_index_to_position(from_)
|
||||
to, _ = line.document.translate_index_to_position(to)
|
||||
|
||||
indent(line, from_ - 1, to, count=event.arg) # XXX: why does translate_index_to_position return 1-based indexing???
|
||||
event.input_processor.pop_input_mode()
|
||||
|
||||
@handle(Keys.ControlC, '<', in_mode=InputMode.SELECTION)
|
||||
def _(event):
|
||||
"""
|
||||
Unindent selected text.
|
||||
"""
|
||||
from_, to = line.document.selection_range()
|
||||
from_, _ = line.document.translate_index_to_position(from_)
|
||||
to, _ = line.document.translate_index_to_position(to)
|
||||
|
||||
unindent(line, from_ - 1, to, count=event.arg)
|
||||
event.input_processor.pop_input_mode()
|
File diff suppressed because it is too large
Load Diff
|
@ -66,7 +66,7 @@ class Layout(object):
|
|||
lexer=None,
|
||||
min_height=0,
|
||||
show_tildes=False,
|
||||
line_name='default'):
|
||||
buffer_name='default'):
|
||||
|
||||
self.before_input = before_input
|
||||
self.after_input = after_input
|
||||
|
@ -77,7 +77,7 @@ class Layout(object):
|
|||
self.menus = menus or []
|
||||
self.min_height = min_height
|
||||
self.show_tildes = show_tildes
|
||||
self.line_name = line_name
|
||||
self.buffer_name = buffer_name
|
||||
|
||||
if lexer:
|
||||
self.lexer = lexer(
|
||||
|
@ -96,11 +96,11 @@ class Layout(object):
|
|||
|
||||
self.reset()
|
||||
|
||||
def _line(self, cli):
|
||||
def _buffer(self, cli):
|
||||
"""
|
||||
The line object that contains the 'main' content.
|
||||
The buffer object that contains the 'main' content.
|
||||
"""
|
||||
return cli.lines[self.line_name]
|
||||
return cli.buffers[self.buffer_name]
|
||||
|
||||
def reset(self):
|
||||
#: Vertical scrolling position of the main content.
|
||||
|
@ -110,21 +110,21 @@ class Layout(object):
|
|||
"""
|
||||
Tokenize input text for highlighting.
|
||||
"""
|
||||
line = self._line(cli)
|
||||
buffer = self._buffer(cli)
|
||||
|
||||
def get():
|
||||
if self.lexer:
|
||||
tokens = list(self.lexer.get_tokens(line.text))
|
||||
tokens = list(self.lexer.get_tokens(buffer.text))
|
||||
else:
|
||||
tokens = [(Token, line.text)]
|
||||
tokens = [(Token, buffer.text)]
|
||||
|
||||
for p in self.input_processors:
|
||||
tokens = p.process_tokens(tokens)
|
||||
return tokens
|
||||
|
||||
return self._token_lru_cache.get(line.text, get)
|
||||
return self._token_lru_cache.get(buffer.text, get)
|
||||
|
||||
def get_highlighted_characters(self, line):
|
||||
def get_highlighted_characters(self, buffer):
|
||||
"""
|
||||
Return a dictionary that maps the index of input string characters to
|
||||
their Token in case of highlighting.
|
||||
|
@ -132,19 +132,19 @@ class Layout(object):
|
|||
highlighted_characters = {}
|
||||
|
||||
# In case of incremental search, highlight all matches.
|
||||
if line.isearch_state:
|
||||
for index in line.document.find_all(line.isearch_state.isearch_text):
|
||||
if index == line.cursor_position:
|
||||
if buffer.isearch_state:
|
||||
for index in buffer.document.find_all(buffer.isearch_state.isearch_text):
|
||||
if index == buffer.cursor_position:
|
||||
token = Token.SearchMatch.Current
|
||||
else:
|
||||
token = Token.SearchMatch
|
||||
|
||||
highlighted_characters.update(dict([
|
||||
(x, token) for x in range(index, index + len(line.isearch_state.isearch_text))
|
||||
(x, token) for x in range(index, index + len(buffer.isearch_state.isearch_text))
|
||||
]))
|
||||
|
||||
# In case of selection, highlight all matches.
|
||||
selection_range = line.document.selection_range()
|
||||
selection_range = buffer.document.selection_range()
|
||||
if selection_range:
|
||||
from_, to = selection_range
|
||||
|
||||
|
@ -164,7 +164,7 @@ class Layout(object):
|
|||
|
||||
# Apply highlighting.
|
||||
if not (cli.is_exiting or cli.is_aborting or cli.is_returning):
|
||||
highlighted_characters = self.get_highlighted_characters(self._line(cli))
|
||||
highlighted_characters = self.get_highlighted_characters(self._buffer(cli))
|
||||
|
||||
for index, token in highlighted_characters.items():
|
||||
input_tokens[index] = (token, input_tokens[index][1])
|
||||
|
@ -173,7 +173,7 @@ class Layout(object):
|
|||
# Insert char.
|
||||
screen.write_char(c, token,
|
||||
string_index=index,
|
||||
set_cursor_position=(index == self._line(cli).cursor_position))
|
||||
set_cursor_position=(index == self._buffer(cli).cursor_position))
|
||||
|
||||
def write_input_scrolled(self, cli, screen, write_content,
|
||||
min_height=1, top_margin=0, bottom_margin=0):
|
||||
|
@ -216,7 +216,7 @@ class Layout(object):
|
|||
|
||||
# Scroll down if we need space for the menu.
|
||||
if self._need_to_show_completion_menu(cli):
|
||||
menu_size = self.menus[0].get_height(self._line(cli).complete_state)
|
||||
menu_size = self.menus[0].get_height(self._buffer(cli).complete_state)
|
||||
if temp_screen.cursor_position.y - self.vertical_scroll >= max_height - menu_size:
|
||||
self.vertical_scroll = (temp_screen.cursor_position.y + 1) - (max_height - menu_size)
|
||||
|
||||
|
@ -241,7 +241,7 @@ class Layout(object):
|
|||
# Show completion menu.
|
||||
if not is_done and self._need_to_show_completion_menu(cli):
|
||||
try:
|
||||
y, x = temp_screen._cursor_mappings[self._line(cli).complete_state.original_document.cursor_position]
|
||||
y, x = temp_screen._cursor_mappings[self._buffer(cli).complete_state.original_document.cursor_position]
|
||||
except KeyError:
|
||||
# This happens when the new, completed string is shorter than
|
||||
# the original string. (e.g. in case of useless backslash
|
||||
|
@ -249,7 +249,7 @@ class Layout(object):
|
|||
# Not worth fixing at the moment. Just don't show the menu.
|
||||
pass
|
||||
else:
|
||||
self.menus[0].write(screen, (y - self.vertical_scroll + top_margin, x + left_margin_width), self._line(cli).complete_state)
|
||||
self.menus[0].write(screen, (y - self.vertical_scroll + top_margin, x + left_margin_width), self._buffer(cli).complete_state)
|
||||
|
||||
return_value = max([min_height + top_margin, screen.current_height])
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from pygments.token import Token
|
||||
from ..enums import InputMode
|
||||
|
||||
__all__ = (
|
||||
'CompletionsMenu',
|
||||
|
@ -12,19 +11,17 @@ class CompletionsMenu(object):
|
|||
"""
|
||||
Helper for drawing the complete menu to the screen.
|
||||
"""
|
||||
def __init__(self, max_height=5, line_name='default'):
|
||||
def __init__(self, max_height=5, buffer_name='default'):
|
||||
self.max_height = max_height
|
||||
self.line_name = line_name
|
||||
self.buffer_name = buffer_name
|
||||
self.token = Token.Menu.Completions
|
||||
|
||||
def is_visible(self, cli):
|
||||
"""
|
||||
True when this menu is visible.
|
||||
"""
|
||||
if cli.input_processor.input_mode in (InputMode.SYSTEM, InputMode.INCREMENTAL_SEARCH):
|
||||
return False
|
||||
|
||||
return bool(cli.lines[self.line_name].complete_state)
|
||||
return (cli.focus_stack.current == self.buffer_name and
|
||||
bool(cli.buffers[self.buffer_name].complete_state))
|
||||
|
||||
def get_height(self, complete_state):
|
||||
"""
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from pygments.token import Token
|
||||
from ..enums import IncrementalSearchDirection, InputMode
|
||||
from ..enums import IncrementalSearchDirection
|
||||
|
||||
__all__ = (
|
||||
'Prompt',
|
||||
|
@ -73,11 +73,10 @@ class DefaultPrompt(Prompt):
|
|||
"""
|
||||
List of (Token, text) tuples.
|
||||
"""
|
||||
if cli.input_processor.input_mode == InputMode.INCREMENTAL_SEARCH and cli.line.isearch_state:
|
||||
return self.isearch_prompt(cli.line.isearch_state)
|
||||
buffer = cli.buffers['default']
|
||||
|
||||
elif cli.input_processor.input_mode == InputMode.VI_SEARCH:
|
||||
return self.vi_search_prompt()
|
||||
if buffer.isearch_state:
|
||||
return self.isearch_prompt(buffer.isearch_state)
|
||||
|
||||
elif cli.input_processor.arg is not None:
|
||||
return self.arg_prompt(cli.input_processor.arg)
|
||||
|
@ -100,7 +99,3 @@ class DefaultPrompt(Prompt):
|
|||
Tokens for the prompt when we go in reverse-i-search mode.
|
||||
"""
|
||||
return self.isearch_composer(isearch_state).get_tokens()
|
||||
|
||||
def vi_search_prompt(self):
|
||||
# TODO
|
||||
return []
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||
from pygments.lexers import BashLexer
|
||||
from pygments.token import Token
|
||||
|
||||
from ..enums import InputMode, IncrementalSearchDirection
|
||||
from ..enums import IncrementalSearchDirection
|
||||
from ..layout import Layout
|
||||
from ..layout.prompt import Prompt
|
||||
|
||||
|
@ -95,18 +95,19 @@ class SystemToolbar(Toolbar):
|
|||
"""
|
||||
The system toolbar. Shows the '!'-prompt.
|
||||
"""
|
||||
def __init__(self):
|
||||
def __init__(self, buffer_name='system'):
|
||||
token = Token.Toolbar.System
|
||||
super(SystemToolbar, self).__init__(token=token)
|
||||
|
||||
# We use a nested single-line-no-wrap layout for this.
|
||||
self.layout = Layout(before_input=Prompt('Shell command: ', token=token.Prefix),
|
||||
lexer=BashLexer,
|
||||
line_name='system')
|
||||
buffer_name='system')
|
||||
self.buffer_name = buffer_name
|
||||
|
||||
def is_visible(self, cli):
|
||||
return super(SystemToolbar, self).is_visible(cli) and \
|
||||
cli.input_processor.input_mode == InputMode.SYSTEM
|
||||
cli.focus_stack.current == self.buffer_name
|
||||
|
||||
def write(self, cli, screen):
|
||||
# Just write this layout at the current position.
|
||||
|
@ -115,41 +116,46 @@ class SystemToolbar(Toolbar):
|
|||
|
||||
|
||||
class SearchToolbar(Toolbar):
|
||||
def __init__(self, token=None):
|
||||
def __init__(self, token=None, buffer_name='search'):
|
||||
token = token or Token.Toolbar.Search
|
||||
super(SearchToolbar, self).__init__(token=token)
|
||||
|
||||
self.buffer_name = buffer_name
|
||||
|
||||
class Prefix(Prompt):
|
||||
""" Search prompt. """
|
||||
def tokens(self, cli):
|
||||
vi = cli.input_processor.input_mode == InputMode.VI_SEARCH
|
||||
buffer = cli.buffers[cli.focus_stack.previous]
|
||||
|
||||
if cli.line.isearch_state.isearch_direction == IncrementalSearchDirection.BACKWARD:
|
||||
text = '?' if vi else 'I-search backward: '
|
||||
if buffer.isearch_state is None:
|
||||
text = ''
|
||||
elif buffer.isearch_state.isearch_direction == IncrementalSearchDirection.BACKWARD:
|
||||
text = 'I-search backward: '
|
||||
else:
|
||||
text = '/' if vi else 'I-search: '
|
||||
text = 'I-search: '
|
||||
|
||||
return [(token, text)]
|
||||
|
||||
class SearchLayout(Layout):
|
||||
def get_input_tokens(self, cli):
|
||||
line = cli.line
|
||||
search_line = cli.lines['search']
|
||||
index = line.isearch_state and line.isearch_state.no_match_from_index
|
||||
buffer = cli.buffers[cli.focus_stack.previous]
|
||||
|
||||
search_buffer = cli.buffers[self.buffer_name]
|
||||
index = buffer.isearch_state and buffer.isearch_state.no_match_from_index
|
||||
|
||||
if index is None:
|
||||
return [(token.Text, search_line.text)]
|
||||
return [(token.Text, search_buffer.text)]
|
||||
else:
|
||||
return [
|
||||
(token.Text, search_line.text[:index]),
|
||||
(token.Text.NoMatch, search_line.text[index:]),
|
||||
(token.Text, search_buffer.text[:index]),
|
||||
(token.Text.NoMatch, search_buffer.text[index:]),
|
||||
]
|
||||
|
||||
self.layout = SearchLayout(before_input=Prefix(), line_name='search')
|
||||
self.layout = SearchLayout(before_input=Prefix(), buffer_name=self.buffer_name)
|
||||
|
||||
def is_visible(self, cli):
|
||||
return super(SearchToolbar, self).is_visible(cli) and \
|
||||
cli.input_processor.input_mode in (InputMode.INCREMENTAL_SEARCH, InputMode.VI_SEARCH)
|
||||
cli.focus_stack.current == self.buffer_name
|
||||
|
||||
def write(self, cli, screen):
|
||||
self.layout.write_content(cli, screen)
|
||||
|
@ -160,19 +166,21 @@ class CompletionsToolbar(Toolbar):
|
|||
Helper for drawing the completion menu 'wildmenu'-style.
|
||||
(Similar to Vim's wildmenu.)
|
||||
"""
|
||||
def __init__(self, token=None):
|
||||
def __init__(self, token=None, buffer_name='default'):
|
||||
token = token or Token.Toolbar.Completions
|
||||
super(CompletionsToolbar, self).__init__(token=token)
|
||||
self.buffer_name = buffer_name
|
||||
|
||||
def is_visible(self, cli):
|
||||
return super(CompletionsToolbar, self).is_visible(cli) and \
|
||||
bool(cli.line.complete_state) and len(cli.line.complete_state.current_completions) >= 1
|
||||
bool(cli.buffers[self.buffer_name].complete_state) and \
|
||||
len(cli.buffers[self.buffer_name].complete_state.current_completions) >= 1
|
||||
|
||||
def get_tokens(self, cli, width):
|
||||
"""
|
||||
Write the menu to the screen object.
|
||||
"""
|
||||
complete_state = cli.line.complete_state
|
||||
complete_state = cli.buffers[self.buffer_name].complete_state
|
||||
completions = complete_state.current_completions
|
||||
index = complete_state.complete_index # Can be None!
|
||||
|
||||
|
@ -225,21 +233,24 @@ class ValidationToolbar(Toolbar):
|
|||
"""
|
||||
Toolbar for displaying validation errors.
|
||||
"""
|
||||
def __init__(self, token=None):
|
||||
def __init__(self, token=None, buffer_name='default'):
|
||||
token = token or Token.Toolbar.Validation
|
||||
super(ValidationToolbar, self).__init__(token=token)
|
||||
self.buffer_name = buffer_name
|
||||
|
||||
def is_visible(self, cli):
|
||||
return super(ValidationToolbar, self).is_visible(cli) and \
|
||||
bool(cli.line.validation_error)
|
||||
bool(cli.current_buffer.validation_error)
|
||||
|
||||
def get_tokens(self, cli, width):
|
||||
if cli.line.validation_error:
|
||||
row, column = cli.line.document.translate_index_to_position(
|
||||
cli.line.validation_error.index)
|
||||
buffer = cli.buffers[self.buffer_name]
|
||||
|
||||
if buffer.validation_error:
|
||||
row, column = buffer.document.translate_index_to_position(
|
||||
buffer.validation_error.index)
|
||||
|
||||
text = '%s (line=%s column=%s)' % (
|
||||
cli.line.validation_error.message, row, column)
|
||||
buffer.validation_error.message, row, column)
|
||||
return [(self.token, text)]
|
||||
else:
|
||||
return []
|
||||
|
|
|
@ -390,8 +390,8 @@ class Renderer(object):
|
|||
#: We don't know this until a `report_absolute_cursor_row` call.
|
||||
self._min_available_height = 0
|
||||
|
||||
# In case of Windown, also make sure to scroll to the current cursor
|
||||
# position.
|
||||
# In case of Windown, also make sure to scroll to the current cursor
|
||||
# position.
|
||||
if sys.platform == 'win32':
|
||||
Output(self.stdout).scroll_buffer_to_prompt()
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import termios
|
|||
import tty
|
||||
|
||||
from ..keys import Keys
|
||||
from ..key_binding import KeyPress
|
||||
from ..key_binding.input_processor import KeyPress
|
||||
|
||||
__all__ = (
|
||||
'InputStream',
|
||||
|
|
|
@ -13,6 +13,10 @@ class ValidationError(Exception):
|
|||
self.index = index
|
||||
self.message = message
|
||||
|
||||
def __repr__(self):
|
||||
return 'ValidationError(index=%r, message=%r)' % (
|
||||
self.index, self.message)
|
||||
|
||||
|
||||
class Validator(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from prompt_toolkit.buffer import Buffer
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class BufferTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.buffer = Buffer()
|
||||
|
||||
def test_initial(self):
|
||||
self.assertEqual(self.buffer.text, '')
|
||||
self.assertEqual(self.buffer.cursor_position, 0)
|
||||
|
||||
def test_insert_text(self):
|
||||
self.buffer.insert_text('some_text')
|
||||
self.assertEqual(self.buffer.text, 'some_text')
|
||||
self.assertEqual(self.buffer.cursor_position, len('some_text'))
|
||||
|
||||
def test_cursor_movement(self):
|
||||
self.buffer.insert_text('some_text')
|
||||
self.buffer.cursor_left()
|
||||
self.buffer.cursor_left()
|
||||
self.buffer.cursor_left()
|
||||
self.buffer.cursor_right()
|
||||
self.buffer.insert_text('A')
|
||||
|
||||
self.assertEqual(self.buffer.text, 'some_teAxt')
|
||||
self.assertEqual(self.buffer.cursor_position, len('some_teA'))
|
||||
|
||||
def test_backspace(self):
|
||||
self.buffer.insert_text('some_text')
|
||||
self.buffer.cursor_left()
|
||||
self.buffer.cursor_left()
|
||||
self.buffer.delete_before_cursor()
|
||||
|
||||
self.assertEqual(self.buffer.text, 'some_txt')
|
||||
self.assertEqual(self.buffer.cursor_position, len('some_t'))
|
||||
|
||||
def test_cursor_up(self):
|
||||
# Cursor up to a line thats longer.
|
||||
self.buffer.insert_text('long line1\nline2')
|
||||
self.buffer.cursor_up()
|
||||
|
||||
self.assertEqual(self.buffer.document.cursor_position, 5)
|
||||
|
||||
# Going up when already at the top.
|
||||
self.buffer.cursor_up()
|
||||
self.assertEqual(self.buffer.document.cursor_position, 5)
|
||||
|
||||
# Going up to a line that's shorter.
|
||||
self.buffer.reset()
|
||||
self.buffer.insert_text('line1\nlong line2')
|
||||
|
||||
self.buffer.cursor_up()
|
||||
self.assertEqual(self.buffer.document.cursor_position, 5)
|
||||
|
||||
def test_cursor_down(self):
|
||||
self.buffer.insert_text('line1\nline2')
|
||||
self.buffer.cursor_position = 3
|
||||
|
||||
# Normally going down
|
||||
self.buffer.cursor_down()
|
||||
self.assertEqual(self.buffer.document.cursor_position, len('line1\nlin'))
|
||||
|
||||
# Going down to a line that's storter.
|
||||
self.buffer.reset()
|
||||
self.buffer.insert_text('long line1\na\nb')
|
||||
self.buffer.cursor_position = 3
|
||||
|
||||
self.buffer.cursor_down()
|
||||
self.assertEqual(self.buffer.document.cursor_position, len('long line1\na'))
|
||||
|
||||
def test_join_next_line(self):
|
||||
self.buffer.insert_text('line1\nline2\nline3')
|
||||
self.buffer.cursor_up()
|
||||
self.buffer.join_next_line()
|
||||
|
||||
self.assertEqual(self.buffer.text, 'line1\nline2line3')
|
||||
|
||||
# Test when there is no '\n' in the text
|
||||
self.buffer.reset()
|
||||
self.buffer.insert_text('line1')
|
||||
self.buffer.cursor_position = 0
|
||||
self.buffer.join_next_line()
|
||||
|
||||
self.assertEqual(self.buffer.text, 'line1')
|
||||
|
||||
def test_newline(self):
|
||||
self.buffer.insert_text('hello world')
|
||||
self.buffer.newline()
|
||||
|
||||
self.assertEqual(self.buffer.text, 'hello world\n')
|
||||
|
||||
def test_swap_characters_before_cursor(self):
|
||||
self.buffer.insert_text('hello world')
|
||||
self.buffer.cursor_left()
|
||||
self.buffer.cursor_left()
|
||||
self.buffer.swap_characters_before_cursor()
|
||||
|
||||
self.assertEqual(self.buffer.text, 'hello wrold')
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from prompt_toolkit.line import Document
|
||||
from prompt_toolkit.document import Document
|
||||
|
||||
import unittest
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from prompt_toolkit.key_binding import InputProcessor, Registry, KeyPress
|
||||
from prompt_toolkit.key_binding.input_processor import InputProcessor, KeyPress
|
||||
from prompt_toolkit.key_binding.registry import Registry
|
||||
from prompt_toolkit.keys import Keys
|
||||
|
||||
import unittest
|
||||
|
@ -25,7 +26,7 @@ class KeyBindingTest(unittest.TestCase):
|
|||
self.registry.add_binding(Keys.ControlD)(self.handlers.control_d)
|
||||
self.registry.add_binding(Keys.ControlSquareClose, Keys.Any)(self.handlers.control_square_close_any)
|
||||
|
||||
self.processor = InputProcessor(self.registry)
|
||||
self.processor = InputProcessor(self.registry, lambda: None)
|
||||
|
||||
def test_feed_simple(self):
|
||||
self.processor.feed_key(KeyPress(Keys.ControlX, '\x18'))
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from prompt_toolkit.line import Line
|
||||
|
||||
import unittest
|
||||
|
||||
|
||||
class LineTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.cli = Line()
|
||||
|
||||
def test_initial(self):
|
||||
self.assertEqual(self.cli.text, '')
|
||||
self.assertEqual(self.cli.cursor_position, 0)
|
||||
|
||||
def test_insert_text(self):
|
||||
self.cli.insert_text('some_text')
|
||||
self.assertEqual(self.cli.text, 'some_text')
|
||||
self.assertEqual(self.cli.cursor_position, len('some_text'))
|
||||
|
||||
def test_cursor_movement(self):
|
||||
self.cli.insert_text('some_text')
|
||||
self.cli.cursor_left()
|
||||
self.cli.cursor_left()
|
||||
self.cli.cursor_left()
|
||||
self.cli.cursor_right()
|
||||
self.cli.insert_text('A')
|
||||
|
||||
self.assertEqual(self.cli.text, 'some_teAxt')
|
||||
self.assertEqual(self.cli.cursor_position, len('some_teA'))
|
||||
|
||||
def test_backspace(self):
|
||||
self.cli.insert_text('some_text')
|
||||
self.cli.cursor_left()
|
||||
self.cli.cursor_left()
|
||||
self.cli.delete_before_cursor()
|
||||
|
||||
self.assertEqual(self.cli.text, 'some_txt')
|
||||
self.assertEqual(self.cli.cursor_position, len('some_t'))
|
||||
|
||||
def test_cursor_up(self):
|
||||
# Cursor up to a line thats longer.
|
||||
self.cli.insert_text('long line1\nline2')
|
||||
self.cli.cursor_up()
|
||||
|
||||
self.assertEqual(self.cli.document.cursor_position, 5)
|
||||
|
||||
# Going up when already at the top.
|
||||
self.cli.cursor_up()
|
||||
self.assertEqual(self.cli.document.cursor_position, 5)
|
||||
|
||||
# Going up to a line that's shorter.
|
||||
self.cli.reset()
|
||||
self.cli.insert_text('line1\nlong line2')
|
||||
|
||||
self.cli.cursor_up()
|
||||
self.assertEqual(self.cli.document.cursor_position, 5)
|
||||
|
||||
def test_cursor_down(self):
|
||||
self.cli.insert_text('line1\nline2')
|
||||
self.cli.cursor_position = 3
|
||||
|
||||
# Normally going down
|
||||
self.cli.cursor_down()
|
||||
self.assertEqual(self.cli.document.cursor_position, len('line1\nlin'))
|
||||
|
||||
# Going down to a line that's storter.
|
||||
self.cli.reset()
|
||||
self.cli.insert_text('long line1\na\nb')
|
||||
self.cli.cursor_position = 3
|
||||
|
||||
self.cli.cursor_down()
|
||||
self.assertEqual(self.cli.document.cursor_position, len('long line1\na'))
|
||||
|
||||
# def test_auto_up_and_down(self):
|
||||
# self.cli.insert_text('long line3\nlong line4')
|
||||
#
|
||||
# # Test current
|
||||
# self.assertEqual(self.cli.text, 'long line3\nlong line4')
|
||||
# self.assertEqual(self.cli.cursor_position, len('long line3\nlong line4'))
|
||||
#
|
||||
# # Go up.
|
||||
# self.cli.auto_up()
|
||||
# self.assertEqual(self.cli.text, 'long line3\nlong line4')
|
||||
# self.assertEqual(self.cli.cursor_position, len('long line3'))
|
||||
#
|
||||
# # Go up again (goes to first item.)
|
||||
# self.cli.auto_up()
|
||||
# self.assertEqual(self.cli.text, 'line1\nline2')
|
||||
# self.assertEqual(self.cli.cursor_position, len('line1\nline2'))
|
||||
#
|
||||
# # Go up again (goes to first line of first item.)
|
||||
# self.cli.auto_up()
|
||||
# self.assertEqual(self.cli.text, 'line1\nline2')
|
||||
# self.assertEqual(self.cli.cursor_position, len('line1'))
|
||||
#
|
||||
# # Go up again (while we're at the first item in history.)
|
||||
# # (Nothing changes.)
|
||||
# self.cli.auto_up()
|
||||
# self.assertEqual(self.cli.text, 'line1\nline2')
|
||||
# self.assertEqual(self.cli.cursor_position, len('line1'))
|
||||
#
|
||||
# # Go down (to second line of first item.)
|
||||
# self.cli.auto_down()
|
||||
# self.assertEqual(self.cli.text, 'line1\nline2')
|
||||
# self.assertEqual(self.cli.cursor_position, len('line1\nline2'))
|
||||
#
|
||||
# # Go down again (to first line of second item.)
|
||||
# # (Going down goes to the first character of a line.)
|
||||
# self.cli.auto_down()
|
||||
# self.assertEqual(self.cli.text, 'long line3\nlong line4')
|
||||
# self.assertEqual(self.cli.cursor_position, len(''))
|
||||
#
|
||||
# # Go down again (to second line of second item.)
|
||||
# self.cli.auto_down()
|
||||
# self.assertEqual(self.cli.text, 'long line3\nlong line4')
|
||||
# self.assertEqual(self.cli.cursor_position, len('long line3\n'))
|
||||
#
|
||||
# # Go down again after the last line. (nothing should happen.)
|
||||
# self.cli.auto_down()
|
||||
# self.assertEqual(self.cli.text, 'long line3\nlong line4')
|
||||
# self.assertEqual(self.cli.cursor_position, len('long line3\n'))
|
||||
|
||||
def test_join_next_line(self):
|
||||
self.cli.insert_text('line1\nline2\nline3')
|
||||
self.cli.cursor_up()
|
||||
self.cli.join_next_line()
|
||||
|
||||
self.assertEqual(self.cli.text, 'line1\nline2line3')
|
||||
|
||||
# Test when there is no '\n' in the text
|
||||
self.cli.reset()
|
||||
self.cli.insert_text('line1')
|
||||
self.cli.cursor_position = 0
|
||||
self.cli.join_next_line()
|
||||
|
||||
self.assertEqual(self.cli.text, 'line1')
|
||||
|
||||
def test_newline(self):
|
||||
self.cli.insert_text('hello world')
|
||||
self.cli.newline()
|
||||
|
||||
self.assertEqual(self.cli.text, 'hello world\n')
|
||||
|
||||
def test_swap_characters_before_cursor(self):
|
||||
self.cli.insert_text('hello world')
|
||||
self.cli.cursor_left()
|
||||
self.cli.cursor_left()
|
||||
self.cli.swap_characters_before_cursor()
|
||||
|
||||
self.assertEqual(self.cli.text, 'hello wrold')
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from line_tests import *
|
||||
from buffer_tests import *
|
||||
from document_tests import *
|
||||
from inputstream_tests import *
|
||||
from key_binding_tests import *
|
||||
|
@ -12,4 +12,4 @@ from layout_tests import *
|
|||
import unittest
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in New Issue