prompt_toolkit-1.0.15/0000775000175000017500000000000013136335632016321 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/tests/0000775000175000017500000000000013136335632017463 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/tests/test_style.py0000664000175000017500000000275613136130420022232 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.styles import Attrs, style_from_dict from prompt_toolkit.token import Token def test_style_from_dict(): style = style_from_dict({ Token.A: '#ff0000 bold underline italic', Token.B: 'bg:#00ff00 blink reverse', }) expected = Attrs(color='ff0000', bgcolor=None, bold=True, underline=True, italic=True, blink=False, reverse=False) assert style.get_attrs_for_token(Token.A) == expected expected = Attrs(color=None, bgcolor='00ff00', bold=False, underline=False, italic=False, blink=True, reverse=True) assert style.get_attrs_for_token(Token.B) == expected def test_style_inheritance(): style = style_from_dict({ Token: '#ff0000', Token.A.B.C: 'bold', Token.A.B.C.D: '#ansired', Token.A.B.C.D.E: 'noinherit blink' }) expected = Attrs(color='ff0000', bgcolor=None, bold=True, underline=False, italic=False, blink=False, reverse=False) assert style.get_attrs_for_token(Token.A.B.C) == expected expected = Attrs(color='ansired', bgcolor=None, bold=True, underline=False, italic=False, blink=False, reverse=False) assert style.get_attrs_for_token(Token.A.B.C.D) == expected expected = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=True, reverse=False) assert style.get_attrs_for_token(Token.A.B.C.D.E) == expected prompt_toolkit-1.0.15/tests/test_key_binding.py0000664000175000017500000001016713136130420023347 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals 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 pytest class Handlers(object): def __init__(self): self.called = [] def __getattr__(self, name): def func(event): self.called.append(name) return func @pytest.fixture def handlers(): return Handlers() @pytest.fixture def registry(handlers): registry = Registry() registry.add_binding( Keys.ControlX, Keys.ControlC)(handlers.controlx_controlc) registry.add_binding(Keys.ControlX)(handlers.control_x) registry.add_binding(Keys.ControlD)(handlers.control_d) registry.add_binding( Keys.ControlSquareClose, Keys.Any)(handlers.control_square_close_any) return registry @pytest.fixture def processor(registry): return InputProcessor(registry, lambda: None) def test_feed_simple(processor, handlers): processor.feed(KeyPress(Keys.ControlX, '\x18')) processor.feed(KeyPress(Keys.ControlC, '\x03')) processor.process_keys() assert handlers.called == ['controlx_controlc'] def test_feed_several(processor, handlers): # First an unknown key first. processor.feed(KeyPress(Keys.ControlQ, '')) processor.process_keys() assert handlers.called == [] # Followed by a know key sequence. processor.feed(KeyPress(Keys.ControlX, '')) processor.feed(KeyPress(Keys.ControlC, '')) processor.process_keys() assert handlers.called == ['controlx_controlc'] # Followed by another unknown sequence. processor.feed(KeyPress(Keys.ControlR, '')) processor.feed(KeyPress(Keys.ControlS, '')) # Followed again by a know key sequence. processor.feed(KeyPress(Keys.ControlD, '')) processor.process_keys() assert handlers.called == ['controlx_controlc', 'control_d'] def test_control_square_closed_any(processor, handlers): processor.feed(KeyPress(Keys.ControlSquareClose, '')) processor.feed(KeyPress('C', 'C')) processor.process_keys() assert handlers.called == ['control_square_close_any'] def test_common_prefix(processor, handlers): # Sending Control_X should not yet do anything, because there is # another sequence starting with that as well. processor.feed(KeyPress(Keys.ControlX, '')) processor.process_keys() assert handlers.called == [] # When another key is pressed, we know that we did not meant the longer # "ControlX ControlC" sequence and the callbacks are called. processor.feed(KeyPress(Keys.ControlD, '')) processor.process_keys() assert handlers.called == ['control_x', 'control_d'] def test_previous_key_sequence(processor, handlers): """ test whether we receive the correct previous_key_sequence. """ events = [] def handler(event): events.append(event) # Build registry. registry = Registry() registry.add_binding('a', 'a')(handler) registry.add_binding('b', 'b')(handler) processor = InputProcessor(registry, lambda: None) # Create processor and feed keys. processor.feed(KeyPress('a', 'a')) processor.feed(KeyPress('a', 'a')) processor.feed(KeyPress('b', 'b')) processor.feed(KeyPress('b', 'b')) processor.process_keys() # Test. assert len(events) == 2 assert len(events[0].key_sequence) == 2 assert events[0].key_sequence[0].key == 'a' assert events[0].key_sequence[0].data == 'a' assert events[0].key_sequence[1].key == 'a' assert events[0].key_sequence[1].data == 'a' assert events[0].previous_key_sequence == [] assert len(events[1].key_sequence) == 2 assert events[1].key_sequence[0].key == 'b' assert events[1].key_sequence[0].data == 'b' assert events[1].key_sequence[1].key == 'b' assert events[1].key_sequence[1].data == 'b' assert len(events[1].previous_key_sequence) == 2 assert events[1].previous_key_sequence[0].key == 'a' assert events[1].previous_key_sequence[0].data == 'a' assert events[1].previous_key_sequence[1].key == 'a' assert events[1].previous_key_sequence[1].data == 'a' prompt_toolkit-1.0.15/tests/test_print_tokens.py0000664000175000017500000000211113136130420023572 0ustar jonathanjonathan00000000000000""" Test `shortcuts.print_tokens`. """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import print_tokens from prompt_toolkit.token import Token from prompt_toolkit.styles import style_from_dict class _Capture: " Emulate an stdout object. " encoding = 'utf-8' def __init__(self): self._data = [] def write(self, data): self._data.append(data) @property def data(self): return b''.join(self._data) def flush(self): pass def isatty(self): return True def test_print_tokens(): f = _Capture() print_tokens([(Token, 'hello'), (Token, 'world')], file=f) assert b'hello' in f.data assert b'world' in f.data def test_with_style(): f = _Capture() style = style_from_dict({ Token.Hello: '#ff0066', Token.World: '#44ff44 italic', }) tokens = [ (Token.Hello, 'Hello '), (Token.World, 'world'), ] print_tokens(tokens, style=style, file=f) assert b'\x1b[0;38;5;197mHello' in f.data assert b'\x1b[0;38;5;83;3mworld' in f.data prompt_toolkit-1.0.15/tests/test_inputstream.py0000664000175000017500000000776513136130420023452 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.terminal.vt100_input import InputStream from prompt_toolkit.keys import Keys import pytest class _ProcessorMock(object): def __init__(self): self.keys = [] def feed_key(self, key_press): self.keys.append(key_press) @pytest.fixture def processor(): return _ProcessorMock() @pytest.fixture def stream(processor): return InputStream(processor.feed_key) def test_control_keys(processor, stream): stream.feed('\x01\x02\x10') assert len(processor.keys) == 3 assert processor.keys[0].key == Keys.ControlA assert processor.keys[1].key == Keys.ControlB assert processor.keys[2].key == Keys.ControlP assert processor.keys[0].data == '\x01' assert processor.keys[1].data == '\x02' assert processor.keys[2].data == '\x10' def test_arrows(processor, stream): stream.feed('\x1b[A\x1b[B\x1b[C\x1b[D') assert len(processor.keys) == 4 assert processor.keys[0].key == Keys.Up assert processor.keys[1].key == Keys.Down assert processor.keys[2].key == Keys.Right assert processor.keys[3].key == Keys.Left assert processor.keys[0].data == '\x1b[A' assert processor.keys[1].data == '\x1b[B' assert processor.keys[2].data == '\x1b[C' assert processor.keys[3].data == '\x1b[D' def test_escape(processor, stream): stream.feed('\x1bhello') assert len(processor.keys) == 1 + len('hello') assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == 'h' assert processor.keys[0].data == '\x1b' assert processor.keys[1].data == 'h' def test_special_double_keys(processor, stream): stream.feed('\x1b[1;3D') # Should both send escape and left. assert len(processor.keys) == 2 assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == Keys.Left assert processor.keys[0].data == '\x1b[1;3D' assert processor.keys[1].data == '\x1b[1;3D' def test_flush_1(processor, stream): # Send left key in two parts without flush. stream.feed('\x1b') stream.feed('[D') assert len(processor.keys) == 1 assert processor.keys[0].key == Keys.Left assert processor.keys[0].data == '\x1b[D' def test_flush_2(processor, stream): # Send left key with a 'Flush' in between. # The flush should make sure that we process evenything before as-is, # with makes the first part just an escape character instead. stream.feed('\x1b') stream.flush() stream.feed('[D') assert len(processor.keys) == 3 assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == '[' assert processor.keys[2].key == 'D' assert processor.keys[0].data == '\x1b' assert processor.keys[1].data == '[' assert processor.keys[2].data == 'D' def test_meta_arrows(processor, stream): stream.feed('\x1b\x1b[D') assert len(processor.keys) == 2 assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == Keys.Left def test_control_square_close(processor, stream): stream.feed('\x1dC') assert len(processor.keys) == 2 assert processor.keys[0].key == Keys.ControlSquareClose assert processor.keys[1].key == 'C' def test_invalid(processor, stream): # Invalid sequence that has at two characters in common with other # sequences. stream.feed('\x1b[*') assert len(processor.keys) == 3 assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == '[' assert processor.keys[2].key == '*' def test_cpr_response(processor, stream): stream.feed('a\x1b[40;10Rb') assert len(processor.keys) == 3 assert processor.keys[0].key == 'a' assert processor.keys[1].key == Keys.CPRResponse assert processor.keys[2].key == 'b' def test_cpr_response_2(processor, stream): # Make sure that the newline is not included in the CPR response. stream.feed('\x1b[40;1R\n') assert len(processor.keys) == 2 assert processor.keys[0].key == Keys.CPRResponse assert processor.keys[1].key == Keys.ControlJ prompt_toolkit-1.0.15/tests/test_regular_languages.py0000664000175000017500000000626513136130420024560 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.completion import CompleteEvent, Completer, Completion from prompt_toolkit.contrib.regular_languages import compile from prompt_toolkit.contrib.regular_languages.compiler import Match, Variables from prompt_toolkit.contrib.regular_languages.completion import \ GrammarCompleter from prompt_toolkit.document import Document def test_simple_match(): g = compile('hello|world') m = g.match('hello') assert isinstance(m, Match) m = g.match('world') assert isinstance(m, Match) m = g.match('somethingelse') assert m is None def test_variable_varname(): """ Test `Variable` with varname. """ g = compile('((?Phello|world)|test)') m = g.match('hello') variables = m.variables() assert isinstance(variables, Variables) assert variables.get('varname') == 'hello' assert variables['varname'] == 'hello' m = g.match('world') variables = m.variables() assert isinstance(variables, Variables) assert variables.get('varname') == 'world' assert variables['varname'] == 'world' m = g.match('test') variables = m.variables() assert isinstance(variables, Variables) assert variables.get('varname') is None assert variables['varname'] is None def test_prefix(): """ Test `match_prefix`. """ g = compile(r'(hello\ world|something\ else)') m = g.match_prefix('hello world') assert isinstance(m, Match) m = g.match_prefix('he') assert isinstance(m, Match) m = g.match_prefix('') assert isinstance(m, Match) m = g.match_prefix('som') assert isinstance(m, Match) m = g.match_prefix('hello wor') assert isinstance(m, Match) m = g.match_prefix('no-match') assert m.trailing_input().start == 0 assert m.trailing_input().stop == len('no-match') m = g.match_prefix('hellotest') assert m.trailing_input().start == len('hello') assert m.trailing_input().stop == len('hellotest') def test_completer(): class completer1(Completer): def get_completions(self, document, complete_event): yield Completion( 'before-%s-after' % document.text, -len(document.text)) yield Completion( 'before-%s-after-B' % document.text, -len(document.text)) class completer2(Completer): def get_completions(self, document, complete_event): yield Completion( 'before2-%s-after2' % document.text, -len(document.text)) yield Completion( 'before2-%s-after2-B' % document.text, -len(document.text)) # Create grammar. "var1" + "whitespace" + "var2" g = compile(r'(?P[a-z]*) \s+ (?P[a-z]*)') # Test 'get_completions()' completer = GrammarCompleter( g, {'var1': completer1(), 'var2': completer2()}) completions = list(completer.get_completions( Document('abc def', len('abc def')), CompleteEvent())) assert len(completions) == 2 assert completions[0].text == 'before2-def-after2' assert completions[0].start_position == -3 assert completions[1].text == 'before2-def-after2-B' assert completions[1].start_position == -3 prompt_toolkit-1.0.15/tests/test_shortcuts.py0000664000175000017500000000304213136130420023115 0ustar jonathanjonathan00000000000000from prompt_toolkit.shortcuts import _split_multiline_prompt from prompt_toolkit.token import Token def test_split_multiline_prompt(): # Test 1: no newlines: tokens = [(Token, 'ab')] has_before_tokens, before, first_input_line = _split_multiline_prompt(lambda cli: tokens) assert has_before_tokens(None) is False assert before(None) == [] assert first_input_line(None) == [ (Token, 'a'), (Token, 'b'), ] # Test 1: multiple lines. tokens = [(Token, 'ab\ncd\nef')] has_before_tokens, before, first_input_line = _split_multiline_prompt(lambda cli: tokens) assert has_before_tokens(None) is True assert before(None) == [ (Token, 'a'), (Token, 'b'), (Token, '\n'), (Token, 'c'), (Token, 'd'), ] assert first_input_line(None) == [ (Token, 'e'), (Token, 'f'), ] # Edge case 1: starting with a newline. tokens = [(Token, '\nab')] has_before_tokens, before, first_input_line = _split_multiline_prompt(lambda cli: tokens) assert has_before_tokens(None) is True assert before(None) == [] assert first_input_line(None) == [ (Token, 'a'), (Token, 'b') ] # Edge case 2: starting with two newlines. tokens = [(Token, '\n\nab')] has_before_tokens, before, first_input_line = _split_multiline_prompt(lambda cli: tokens) assert has_before_tokens(None) is True assert before(None) == [(Token, '\n')] assert first_input_line(None) == [ (Token, 'a'), (Token, 'b') ] prompt_toolkit-1.0.15/tests/test_buffer.py0000664000175000017500000000513013136130420022330 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.buffer import Buffer import pytest @pytest.fixture def _buffer(): return Buffer() def test_initial(_buffer): assert _buffer.text == '' assert _buffer.cursor_position == 0 def test_insert_text(_buffer): _buffer.insert_text('some_text') assert _buffer.text == 'some_text' assert _buffer.cursor_position == len('some_text') def test_cursor_movement(_buffer): _buffer.insert_text('some_text') _buffer.cursor_left() _buffer.cursor_left() _buffer.cursor_left() _buffer.cursor_right() _buffer.insert_text('A') assert _buffer.text == 'some_teAxt' assert _buffer.cursor_position == len('some_teA') def test_backspace(_buffer): _buffer.insert_text('some_text') _buffer.cursor_left() _buffer.cursor_left() _buffer.delete_before_cursor() assert _buffer.text == 'some_txt' assert _buffer.cursor_position == len('some_t') def test_cursor_up(_buffer): # Cursor up to a line thats longer. _buffer.insert_text('long line1\nline2') _buffer.cursor_up() assert _buffer.document.cursor_position == 5 # Going up when already at the top. _buffer.cursor_up() assert _buffer.document.cursor_position == 5 # Going up to a line that's shorter. _buffer.reset() _buffer.insert_text('line1\nlong line2') _buffer.cursor_up() assert _buffer.document.cursor_position == 5 def test_cursor_down(_buffer): _buffer.insert_text('line1\nline2') _buffer.cursor_position = 3 # Normally going down _buffer.cursor_down() assert _buffer.document.cursor_position == len('line1\nlin') # Going down to a line that's storter. _buffer.reset() _buffer.insert_text('long line1\na\nb') _buffer.cursor_position = 3 _buffer.cursor_down() assert _buffer.document.cursor_position == len('long line1\na') def test_join_next_line(_buffer): _buffer.insert_text('line1\nline2\nline3') _buffer.cursor_up() _buffer.join_next_line() assert _buffer.text == 'line1\nline2 line3' # Test when there is no '\n' in the text _buffer.reset() _buffer.insert_text('line1') _buffer.cursor_position = 0 _buffer.join_next_line() assert _buffer.text == 'line1' def test_newline(_buffer): _buffer.insert_text('hello world') _buffer.newline() assert _buffer.text == 'hello world\n' def test_swap_characters_before_cursor(_buffer): _buffer.insert_text('hello world') _buffer.cursor_left() _buffer.cursor_left() _buffer.swap_characters_before_cursor() assert _buffer.text == 'hello wrold' prompt_toolkit-1.0.15/tests/test_utils.py0000664000175000017500000000230513136130420022220 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.utils import take_using_weights import itertools def test_using_weights(): def take(generator, count): return list(itertools.islice(generator, 0, count)) # Check distribution. data = take(take_using_weights(['A', 'B', 'C'], [5, 10, 20]), 35) assert data.count('A') == 5 assert data.count('B') == 10 assert data.count('C') == 20 assert data == [ 'A', 'B', 'C', 'C', 'B', 'C', 'C', 'A', 'B', 'C', 'C', 'B', 'C', 'C', 'A', 'B', 'C', 'C', 'B', 'C', 'C', 'A', 'B', 'C', 'C', 'B', 'C', 'C', 'A', 'B', 'C', 'C', 'B', 'C', 'C'] # Another order. data = take(take_using_weights(['A', 'B', 'C'], [20, 10, 5]), 35) assert data.count('A') == 20 assert data.count('B') == 10 assert data.count('C') == 5 # Bigger numbers. data = take(take_using_weights(['A', 'B', 'C'], [20, 10, 5]), 70) assert data.count('A') == 40 assert data.count('B') == 20 assert data.count('C') == 10 # Negative numbers. data = take(take_using_weights(['A', 'B', 'C'], [-20, 10, 0]), 70) assert data.count('A') == 0 assert data.count('B') == 70 assert data.count('C') == 0 prompt_toolkit-1.0.15/tests/test_layout.py0000664000175000017500000000221213136130420022372 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.layout.utils import split_lines from prompt_toolkit.token import Token def test_split_lines(): lines = list(split_lines([(Token.A, 'line1\nline2\nline3')])) assert lines == [ [(Token.A, 'line1')], [(Token.A, 'line2')], [(Token.A, 'line3')], ] def test_split_lines_2(): lines = list(split_lines([ (Token.A, 'line1'), (Token.B, 'line2\nline3\nline4') ])) assert lines == [ [(Token.A, 'line1'), (Token.B, 'line2')], [(Token.B, 'line3')], [(Token.B, 'line4')], ] def test_split_lines_3(): " Edge cases: inputs ending with newlines. " # -1- lines = list(split_lines([ (Token.A, 'line1\nline2\n') ])) assert lines == [ [(Token.A, 'line1')], [(Token.A, 'line2')], [(Token.A, '')], ] # -2- lines = list(split_lines([ (Token.A, '\n'), ])) assert lines == [ [], [(Token.A, '')], ] # -3- lines = list(split_lines([ (Token.A, ''), ])) assert lines == [ [(Token.A, '')], ] prompt_toolkit-1.0.15/tests/test_document.py0000664000175000017500000000303213136130420022674 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import pytest from prompt_toolkit.document import Document @pytest.fixture def document(): return Document( 'line 1\n' + 'line 2\n' + 'line 3\n' + 'line 4\n', len('line 1\n' + 'lin') ) def test_current_char(document): assert document.current_char == 'e' def test_text_before_cursor(document): assert document.text_before_cursor == 'line 1\nlin' def test_text_after_cursor(document): assert document.text_after_cursor == 'e 2\n' + \ 'line 3\n' + \ 'line 4\n' def test_lines(document): assert document.lines == [ 'line 1', 'line 2', 'line 3', 'line 4', ''] def test_line_count(document): assert document.line_count == 5 def test_current_line_before_cursor(document): assert document.current_line_before_cursor == 'lin' def test_current_line_after_cursor(document): assert document.current_line_after_cursor == 'e 2' def test_current_line(document): assert document.current_line == 'line 2' def test_cursor_position(document): assert document.cursor_position_row == 1 assert document.cursor_position_col == 3 d = Document('', 0) assert d.cursor_position_row == 0 assert d.cursor_position_col == 0 def test_translate_index_to_position(document): pos = document.translate_index_to_position( len('line 1\nline 2\nlin')) assert pos[0] == 2 assert pos[1] == 3 pos = document.translate_index_to_position(0) assert pos == (0, 0) prompt_toolkit-1.0.15/tests/test_cli.py0000664000175000017500000004723713136130420021644 0ustar jonathanjonathan00000000000000# encoding: utf-8 """ These are almost end-to-end tests. They create a CommandLineInterface instance, feed it with some input and check the result. """ from __future__ import unicode_literals from prompt_toolkit.application import Application from prompt_toolkit.buffer import Buffer, AcceptAction from prompt_toolkit.clipboard import InMemoryClipboard, ClipboardData from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode from prompt_toolkit.eventloop.posix import PosixEventLoop from prompt_toolkit.history import InMemoryHistory from prompt_toolkit.input import PipeInput from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.key_binding.manager import KeyBindingManager from prompt_toolkit.output import DummyOutput from prompt_toolkit.terminal.vt100_input import ANSI_SEQUENCES from functools import partial import pytest def _history(): h = InMemoryHistory() h.append('line1 first input') h.append('line2 second input') h.append('line3 third input') return h def _feed_cli_with_input(text, editing_mode=EditingMode.EMACS, clipboard=None, history=None, multiline=False, check_line_ending=True, pre_run_callback=None): """ Create a CommandLineInterface, feed it with the given user input and return the CLI object. This returns a (result, CLI) tuple. """ # If the given text doesn't end with a newline, the interface won't finish. if check_line_ending: assert text.endswith('\n') loop = PosixEventLoop() try: inp = PipeInput() inp.send_text(text) cli = CommandLineInterface( application=Application( buffer=Buffer(accept_action=AcceptAction.RETURN_DOCUMENT, history=history, is_multiline=multiline), editing_mode=editing_mode, clipboard=clipboard or InMemoryClipboard(), key_bindings_registry=KeyBindingManager.for_prompt().registry, ), eventloop=loop, input=inp, output=DummyOutput()) if pre_run_callback: pre_run_callback(cli) result = cli.run() return result, cli finally: loop.close() inp.close() def test_simple_text_input(): # Simple text input, followed by enter. result, cli = _feed_cli_with_input('hello\n') assert result.text == 'hello' assert cli.buffers[DEFAULT_BUFFER].text == 'hello' def test_emacs_cursor_movements(): """ Test cursor movements with Emacs key bindings. """ # ControlA (beginning-of-line) result, cli = _feed_cli_with_input('hello\x01X\n') assert result.text == 'Xhello' # ControlE (end-of-line) result, cli = _feed_cli_with_input('hello\x01X\x05Y\n') assert result.text == 'XhelloY' # ControlH or \b result, cli = _feed_cli_with_input('hello\x08X\n') assert result.text == 'hellX' # Delete. (Left, left, delete) result, cli = _feed_cli_with_input('hello\x1b[D\x1b[D\x1b[3~\n') assert result.text == 'helo' # Left. result, cli = _feed_cli_with_input('hello\x1b[DX\n') assert result.text == 'hellXo' # ControlA, right result, cli = _feed_cli_with_input('hello\x01\x1b[CX\n') assert result.text == 'hXello' # ControlA, right result, cli = _feed_cli_with_input('hello\x01\x1b[CX\n') assert result.text == 'hXello' # ControlB (backward-char) result, cli = _feed_cli_with_input('hello\x02X\n') assert result.text == 'hellXo' # ControlF (forward-char) result, cli = _feed_cli_with_input('hello\x01\x06X\n') assert result.text == 'hXello' # ControlC: raise KeyboardInterrupt. with pytest.raises(KeyboardInterrupt): result, cli = _feed_cli_with_input('hello\x03\n') assert result.text == 'hello' # ControlD without any input: raises EOFError. with pytest.raises(EOFError): result, cli = _feed_cli_with_input('\x04\n') assert result.text == 'hello' # ControlD: delete after cursor. result, cli = _feed_cli_with_input('hello\x01\x04\n') assert result.text == 'ello' # ControlD at the end of the input ssshould not do anything. result, cli = _feed_cli_with_input('hello\x04\n') assert result.text == 'hello' # Left, Left, ControlK (kill-line) result, cli = _feed_cli_with_input('hello\x1b[D\x1b[D\x0b\n') assert result.text == 'hel' # Left, Left Esc- ControlK (kill-line, but negative) result, cli = _feed_cli_with_input('hello\x1b[D\x1b[D\x1b-\x0b\n') assert result.text == 'lo' # ControlL: should not influence the result. result, cli = _feed_cli_with_input('hello\x0c\n') assert result.text == 'hello' # ControlRight (forward-word) result, cli = _feed_cli_with_input('hello world\x01X\x1b[1;5CY\n') assert result.text == 'XhelloY world' # ContrlolLeft (backward-word) result, cli = _feed_cli_with_input('hello world\x1b[1;5DY\n') assert result.text == 'hello Yworld' # -f with argument. (forward-word) result, cli = _feed_cli_with_input('hello world abc def\x01\x1b3\x1bfX\n') assert result.text == 'hello world abcX def' # -f with negative argument. (forward-word) result, cli = _feed_cli_with_input('hello world abc def\x1b-\x1b3\x1bfX\n') assert result.text == 'hello Xworld abc def' # -b with argument. (backward-word) result, cli = _feed_cli_with_input('hello world abc def\x1b3\x1bbX\n') assert result.text == 'hello Xworld abc def' # -b with negative argument. (backward-word) result, cli = _feed_cli_with_input('hello world abc def\x01\x1b-\x1b3\x1bbX\n') assert result.text == 'hello world abc Xdef' # ControlW (kill-word / unix-word-rubout) result, cli = _feed_cli_with_input('hello world\x17\n') assert result.text == 'hello ' assert cli.clipboard.get_data().text == 'world' result, cli = _feed_cli_with_input('test hello world\x1b2\x17\n') assert result.text == 'test ' # Escape Backspace (unix-word-rubout) result, cli = _feed_cli_with_input('hello world\x1b\x7f\n') assert result.text == 'hello ' assert cli.clipboard.get_data().text == 'world' result, cli = _feed_cli_with_input('hello world\x1b\x08\n') assert result.text == 'hello ' assert cli.clipboard.get_data().text == 'world' # Backspace (backward-delete-char) result, cli = _feed_cli_with_input('hello world\x7f\n') assert result.text == 'hello worl' assert result.cursor_position == len('hello worl') result, cli = _feed_cli_with_input('hello world\x08\n') assert result.text == 'hello worl' assert result.cursor_position == len('hello worl') # Delete (delete-char) result, cli = _feed_cli_with_input('hello world\x01\x1b[3~\n') assert result.text == 'ello world' assert result.cursor_position == 0 # Escape-\\ (delete-horizontal-space) result, cli = _feed_cli_with_input('hello world\x1b8\x02\x1b\\\n') assert result.text == 'helloworld' assert result.cursor_position == len('hello') def test_emacs_yank(): # ControlY (yank) c = InMemoryClipboard(ClipboardData('XYZ')) result, cli = _feed_cli_with_input('hello\x02\x19\n', clipboard=c) assert result.text == 'hellXYZo' assert result.cursor_position == len('hellXYZ') def test_quoted_insert(): # ControlQ - ControlB (quoted-insert) result, cli = _feed_cli_with_input('hello\x11\x02\n') assert result.text == 'hello\x02' def test_transformations(): # Meta-c (capitalize-word) result, cli = _feed_cli_with_input('hello world\01\x1bc\n') assert result.text == 'Hello world' assert result.cursor_position == len('Hello') # Meta-u (uppercase-word) result, cli = _feed_cli_with_input('hello world\01\x1bu\n') assert result.text == 'HELLO world' assert result.cursor_position == len('Hello') # Meta-u (downcase-word) result, cli = _feed_cli_with_input('HELLO WORLD\01\x1bl\n') assert result.text == 'hello WORLD' assert result.cursor_position == len('Hello') # ControlT (transpose-chars) result, cli = _feed_cli_with_input('hello\x14\n') assert result.text == 'helol' assert result.cursor_position == len('hello') # Left, Left, Control-T (transpose-chars) result, cli = _feed_cli_with_input('abcde\x1b[D\x1b[D\x14\n') assert result.text == 'abdce' assert result.cursor_position == len('abcd') def test_emacs_other_bindings(): # Transpose characters. result, cli = _feed_cli_with_input('abcde\x14X\n') # Ctrl-T assert result.text == 'abcedX' # Left, Left, Transpose. (This is slightly different.) result, cli = _feed_cli_with_input('abcde\x1b[D\x1b[D\x14X\n') assert result.text == 'abdcXe' # Clear before cursor. result, cli = _feed_cli_with_input('hello\x1b[D\x1b[D\x15X\n') assert result.text == 'Xlo' # unix-word-rubout: delete word before the cursor. # (ControlW). result, cli = _feed_cli_with_input('hello world test\x17X\n') assert result.text == 'hello world X' result, cli = _feed_cli_with_input('hello world /some/very/long/path\x17X\n') assert result.text == 'hello world X' # (with argument.) result, cli = _feed_cli_with_input('hello world test\x1b2\x17X\n') assert result.text == 'hello X' result, cli = _feed_cli_with_input('hello world /some/very/long/path\x1b2\x17X\n') assert result.text == 'hello X' # backward-kill-word: delete word before the cursor. # (Esc-ControlH). result, cli = _feed_cli_with_input('hello world /some/very/long/path\x1b\x08X\n') assert result.text == 'hello world /some/very/long/X' # (with arguments.) result, cli = _feed_cli_with_input('hello world /some/very/long/path\x1b3\x1b\x08X\n') assert result.text == 'hello world /some/very/X' def test_controlx_controlx(): # At the end: go to the start of the line. result, cli = _feed_cli_with_input('hello world\x18\x18X\n') assert result.text == 'Xhello world' assert result.cursor_position == 1 # At the start: go to the end of the line. result, cli = _feed_cli_with_input('hello world\x01\x18\x18X\n') assert result.text == 'hello worldX' # Left, Left Control-X Control-X: go to the end of the line. result, cli = _feed_cli_with_input('hello world\x1b[D\x1b[D\x18\x18X\n') assert result.text == 'hello worldX' def test_emacs_history_bindings(): # Adding a new item to the history. history = _history() result, cli = _feed_cli_with_input('new input\n', history=history) assert result.text == 'new input' history.strings[-1] == 'new input' # Go up in history, and accept the last item. result, cli = _feed_cli_with_input('hello\x1b[A\n', history=history) assert result.text == 'new input' # Esc< (beginning-of-history) result, cli = _feed_cli_with_input('hello\x1b<\n', history=history) assert result.text == 'line1 first input' # Esc> (end-of-history) result, cli = _feed_cli_with_input('another item\x1b[A\x1b[a\x1b>\n', history=history) assert result.text == 'another item' # ControlUp (previous-history) result, cli = _feed_cli_with_input('\x1b[1;5A\n', history=history) assert result.text == 'another item' # Esc< ControlDown (beginning-of-history, next-history) result, cli = _feed_cli_with_input('\x1b<\x1b[1;5B\n', history=history) assert result.text == 'line2 second input' def test_emacs_reverse_search(): history = _history() # ControlR (reverse-search-history) result, cli = _feed_cli_with_input('\x12input\n\n', history=history) assert result.text == 'line3 third input' # Hitting ControlR twice. result, cli = _feed_cli_with_input('\x12input\x12\n\n', history=history) assert result.text == 'line2 second input' def test_emacs_arguments(): """ Test various combinations of arguments in Emacs mode. """ # esc 4 result, cli = _feed_cli_with_input('\x1b4x\n') assert result.text == 'xxxx' # esc 4 4 result, cli = _feed_cli_with_input('\x1b44x\n') assert result.text == 'x' * 44 # esc 4 esc 4 result, cli = _feed_cli_with_input('\x1b4\x1b4x\n') assert result.text == 'x' * 44 # esc - right (-1 position to the right, equals 1 to the left.) result, cli = _feed_cli_with_input('aaaa\x1b-\x1b[Cbbbb\n') assert result.text == 'aaabbbba' # esc - 3 right result, cli = _feed_cli_with_input('aaaa\x1b-3\x1b[Cbbbb\n') assert result.text == 'abbbbaaa' # esc - - - 3 right result, cli = _feed_cli_with_input('aaaa\x1b---3\x1b[Cbbbb\n') assert result.text == 'abbbbaaa' def test_emacs_arguments_for_all_commands(): """ Test all Emacs commands with Meta-[0-9] arguments (both positive and negative). No one should crash. """ for key in ANSI_SEQUENCES: # Ignore BracketedPaste. This would hang forever, because it waits for # the end sequence. if key != '\x1b[200~': try: # Note: we add an 'X' after the key, because Ctrl-Q (quoted-insert) # expects something to follow. We add an additional \n, because # Ctrl-R and Ctrl-S (reverse-search) expect that. result, cli = _feed_cli_with_input( 'hello\x1b4' + key + 'X\n\n') result, cli = _feed_cli_with_input( 'hello\x1b-' + key + 'X\n\n') except KeyboardInterrupt: # This exception should only be raised for Ctrl-C assert key == '\x03' def test_emacs_kill_ring(): operations = ( # abc ControlA ControlK 'abc\x01\x0b' # def ControlA ControlK 'def\x01\x0b' # ghi ControlA ControlK 'ghi\x01\x0b' # ControlY (yank) '\x19' ) result, cli = _feed_cli_with_input(operations + '\n') assert result.text == 'ghi' result, cli = _feed_cli_with_input(operations + '\x1by\n') assert result.text == 'def' result, cli = _feed_cli_with_input(operations + '\x1by\x1by\n') assert result.text == 'abc' result, cli = _feed_cli_with_input(operations + '\x1by\x1by\x1by\n') assert result.text == 'ghi' def test_emacs_insert_comment(): # Test insert-comment (M-#) binding. result, cli = _feed_cli_with_input('hello\x1b#', check_line_ending=False) assert result.text == '#hello' result, cli = _feed_cli_with_input( 'hello\nworld\x1b#', check_line_ending=False, multiline=True) assert result.text == '#hello\n#world' def test_emacs_record_macro(): operations = ( ' ' '\x18(' # Start recording macro. C-X( 'hello' '\x18)' # Stop recording macro. ' ' '\x18e' # Execute macro. '\x18e' # Execute macro. '\n' ) result, cli = _feed_cli_with_input(operations) assert result.text == ' hello hellohello' def test_prefix_meta(): # Test the prefix-meta command. def setup_keybindings(cli): from prompt_toolkit.key_binding.bindings.named_commands import prefix_meta from prompt_toolkit.filters import ViInsertMode cli.application.key_bindings_registry.add_binding('j', 'j', filter=ViInsertMode())(prefix_meta) result, cli = _feed_cli_with_input( 'hellojjIX\n', pre_run_callback=setup_keybindings, editing_mode=EditingMode.VI) assert result.text == 'Xhello' def test_bracketed_paste(): result, cli = _feed_cli_with_input('\x1b[200~hello world\x1b[201~\n') assert result.text == 'hello world' result, cli = _feed_cli_with_input('\x1b[200~hello\nworld\x1b[201~\x1b\n') assert result.text == 'hello\nworld' # With \r\n endings. result, cli = _feed_cli_with_input('\x1b[200~hello\r\nworld\x1b[201~\x1b\n') assert result.text == 'hello\nworld' # With \r endings. result, cli = _feed_cli_with_input('\x1b[200~hello\rworld\x1b[201~\x1b\n') assert result.text == 'hello\nworld' def test_vi_cursor_movements(): """ Test cursor movements with Vi key bindings. """ feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) result, cli = feed('\x1b\n') assert result.text == '' assert cli.editing_mode == EditingMode.VI # Esc h a X result, cli = feed('hello\x1bhaX\n') assert result.text == 'hellXo' # Esc I X result, cli = feed('hello\x1bIX\n') assert result.text == 'Xhello' # Esc I X result, cli = feed('hello\x1bIX\n') assert result.text == 'Xhello' # Esc 2hiX result, cli = feed('hello\x1b2hiX\n') assert result.text == 'heXllo' # Esc 2h2liX result, cli = feed('hello\x1b2h2liX\n') assert result.text == 'hellXo' # Esc \b\b result, cli = feed('hello\b\b\n') assert result.text == 'hel' # Esc \b\b result, cli = feed('hello\b\b\n') assert result.text == 'hel' # Esc 2h D result, cli = feed('hello\x1b2hD\n') assert result.text == 'he' # Esc 2h rX \n result, cli = feed('hello\x1b2hrX\n') assert result.text == 'heXlo' def test_vi_operators(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) # Esc g~0 result, cli = feed('hello\x1bg~0\n') assert result.text == 'HELLo' # Esc gU0 result, cli = feed('hello\x1bgU0\n') assert result.text == 'HELLo' # Esc d0 result, cli = feed('hello\x1bd0\n') assert result.text == 'o' def test_vi_text_objects(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) # Esc gUgg result, cli = feed('hello\x1bgUgg\n') assert result.text == 'HELLO' # Esc gUU result, cli = feed('hello\x1bgUU\n') assert result.text == 'HELLO' # Esc di( result, cli = feed('before(inside)after\x1b8hdi(\n') assert result.text == 'before()after' # Esc di[ result, cli = feed('before[inside]after\x1b8hdi[\n') assert result.text == 'before[]after' # Esc da( result, cli = feed('before(inside)after\x1b8hda(\n') assert result.text == 'beforeafter' def test_vi_digraphs(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) # C-K o/ result, cli = feed('hello\x0bo/\n') assert result.text == 'helloø' # C-K /o (reversed input.) result, cli = feed('hello\x0b/o\n') assert result.text == 'helloø' # C-K e: result, cli = feed('hello\x0be:\n') assert result.text == 'helloë' # C-K xxy (Unknown digraph.) result, cli = feed('hello\x0bxxy\n') assert result.text == 'helloy' def test_vi_block_editing(): " Test Vi Control-V style block insertion. " feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) operations = ( # Three lines of text. '-line1\n-line2\n-line3\n-line4\n-line5\n-line6' # Go to the second character of the second line. '\x1bkkkkkkkj0l' # Enter Visual block mode. '\x16' # Go down two more lines. 'jj' # Go 3 characters to the right. 'lll' # Go to insert mode. 'insert' # (Will be replaced.) # Insert stars. '***' # Escape again. '\x1b\n') # Control-I result, cli = feed(operations.replace('insert', 'I')) assert (result.text == '-line1\n-***line2\n-***line3\n-***line4\n-line5\n-line6') # Control-A result, cli = feed(operations.replace('insert', 'A')) assert (result.text == '-line1\n-line***2\n-line***3\n-line***4\n-line5\n-line6') def test_vi_character_paste(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) # Test 'p' character paste. result, cli = feed('abcde\x1bhhxp\n') assert result.text == 'abdce' assert result.cursor_position == 3 # Test 'P' character paste. result, cli = feed('abcde\x1bhhxP\n') assert result.text == 'abcde' assert result.cursor_position == 2 prompt_toolkit-1.0.15/tests/test_filter.py0000664000175000017500000001023213136130420022343 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.filters import Condition, Never, Always, Filter from prompt_toolkit.filters.types import CLIFilter, SimpleFilter from prompt_toolkit.filters.utils import to_cli_filter, to_simple_filter from prompt_toolkit.filters.cli import HasArg, HasFocus, HasSelection import pytest def test_condition_filter_args(): c = Condition(lambda a, b, c: True) assert c.test_args('a', 'b', 'c') assert not c.test_args() assert not c.test_args('a') assert not c.test_args('a', 'b') assert not c.test_args('a', 'b', 'c', 'd') c2 = Condition(lambda a, b=1: True) assert c2.test_args('a') assert c2.test_args('a', 'b') assert not c2.test_args('a', 'b', 'c') assert not c2.test_args() c3 = Condition(lambda *a: True) assert c3.test_args() assert c3.test_args('a') assert c3.test_args('a', 'b') def test_and_arg(): c1 = Condition(lambda a: True) c2 = Condition(lambda a: True) c3 = c1 & c2 assert c3.test_args('a') assert not c3.test_args() assert not c3.test_args('a', 'b') def test_or_arg(): c1 = Condition(lambda a: True) c2 = Condition(lambda a: True) c3 = c1 | c2 assert c3.test_args('a') assert not c3.test_args() assert not c3.test_args('a', 'b') def test_condition(): c = Condition(lambda a: a % 2 == 0) assert c(4) assert c(6) assert not c(5) assert not c(3) def test_never(): assert not Never()() def test_always(): assert Always()() def test_invert(): assert not (~Always())() assert (~Never()()) c = ~Condition(lambda: False) assert c() def test_or(): for a in (True, False): for b in (True, False): c1 = Condition(lambda: a) c2 = Condition(lambda: b) c3 = c1 | c2 assert isinstance(c3, Filter) assert c3() == a or b def test_and(): for a in (True, False): for b in (True, False): c1 = Condition(lambda: a) c2 = Condition(lambda: b) c3 = c1 & c2 assert isinstance(c3, Filter) assert c3() == (a and b) def test_cli_filter(): c1 = Condition(lambda cli: True) assert isinstance(c1, CLIFilter) assert not isinstance(c1, SimpleFilter) c2 = Condition(lambda: True) assert not isinstance(c2, CLIFilter) assert isinstance(c2, SimpleFilter) c3 = c1 | c2 assert not isinstance(c3, CLIFilter) assert not isinstance(c3, SimpleFilter) c4 = Condition(lambda cli: True) c5 = Condition(lambda cli: True) c6 = c4 & c5 c7 = c4 | c5 assert isinstance(c6, CLIFilter) assert isinstance(c7, CLIFilter) assert not isinstance(c6, SimpleFilter) assert not isinstance(c7, SimpleFilter) c8 = Condition(lambda *args: True) assert isinstance(c8, CLIFilter) assert isinstance(c8, SimpleFilter) def test_to_cli_filter(): f1 = to_cli_filter(True) f2 = to_cli_filter(False) f3 = to_cli_filter(Condition(lambda cli: True)) f4 = to_cli_filter(Condition(lambda cli: False)) assert isinstance(f1, CLIFilter) assert isinstance(f2, CLIFilter) assert isinstance(f3, CLIFilter) assert isinstance(f4, CLIFilter) assert f1(None) assert not f2(None) assert f3(None) assert not f4(None) with pytest.raises(TypeError): to_cli_filter(4) with pytest.raises(TypeError): to_cli_filter(Condition(lambda: True)) def test_to_simple_filter(): f1 = to_simple_filter(True) f2 = to_simple_filter(False) f3 = to_simple_filter(Condition(lambda: True)) f4 = to_simple_filter(Condition(lambda: False)) assert isinstance(f1, SimpleFilter) assert isinstance(f2, SimpleFilter) assert isinstance(f3, SimpleFilter) assert isinstance(f4, SimpleFilter) assert f1() assert not f2() assert f3() assert not f4() with pytest.raises(TypeError): to_simple_filter(4) with pytest.raises(TypeError): to_simple_filter(Condition(lambda cli: True)) def test_cli_filters(): assert isinstance(HasArg(), CLIFilter) assert isinstance(HasFocus('BUFFER_NAME'), CLIFilter) assert isinstance(HasSelection(), CLIFilter) prompt_toolkit-1.0.15/tests/test_contrib.py0000664000175000017500000001707213136130420022527 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals, absolute_import, print_function import os import shutil import tempfile from contextlib import contextmanager from six import text_type from prompt_toolkit.completion import CompleteEvent from prompt_toolkit.document import Document from prompt_toolkit.contrib.completers.filesystem import PathCompleter @contextmanager def chdir(directory): """Context manager for current working directory temporary change.""" orig_dir = os.getcwd() os.chdir(directory) try: yield finally: os.chdir(orig_dir) def write_test_files(test_dir, names=None): """Write test files in test_dir using the names list.""" names = names or range(10) for i in names: with open(os.path.join(test_dir, str(i)), 'wb') as out: out.write(''.encode('UTF-8')) def test_pathcompleter_completes_in_current_directory(): completer = PathCompleter() doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert len(completions) > 0 def test_pathcompleter_completes_files_in_current_directory(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) expected = sorted([str(i) for i in range(10)]) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep with chdir(test_dir): completer = PathCompleter() # this should complete on the cwd doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted(c.text for c in completions) assert expected == result # cleanup shutil.rmtree(test_dir) def test_pathcompleter_completes_files_in_absolute_directory(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) expected = sorted([str(i) for i in range(10)]) test_dir = os.path.abspath(test_dir) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep completer = PathCompleter() # force unicode doc_text = text_type(test_dir) doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted([c.text for c in completions]) assert expected == result # cleanup shutil.rmtree(test_dir) def test_pathcompleter_completes_directories_with_only_directories(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # create a sub directory there os.mkdir(os.path.join(test_dir, 'subdir')) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep with chdir(test_dir): completer = PathCompleter(only_directories=True) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert ['subdir'] == result # check that there is no completion when passing a file with chdir(test_dir): completer = PathCompleter(only_directories=True) doc_text = '1' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # cleanup shutil.rmtree(test_dir) def test_pathcompleter_respects_completions_under_min_input_len(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # min len:1 and no text with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # min len:1 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = '1' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert [''] == result # min len:0 and text of len 2 with chdir(test_dir): completer = PathCompleter(min_input_len=0) doc_text = '1' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert [''] == result # create 10 files with a 2 char long name for i in range(10): with open(os.path.join(test_dir, str(i) * 2), 'wb') as out: out.write(b'') # min len:1 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = '2' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted(c.text for c in completions) assert ['', '2'] == result # min len:2 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=2) doc_text = '2' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # cleanup shutil.rmtree(test_dir) def test_pathcompleter_does_not_expanduser_by_default(): completer = PathCompleter() doc_text = '~' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions def test_pathcompleter_can_expanduser(): completer = PathCompleter(expanduser=True) doc_text = '~' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert len(completions) > 0 def test_pathcompleter_can_apply_file_filter(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # add a .csv file with open(os.path.join(test_dir, 'my.csv'), 'wb') as out: out.write(b'') file_filter = lambda f: f and f.endswith('.csv') with chdir(test_dir): completer = PathCompleter(file_filter=file_filter) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert ['my.csv'] == result # cleanup shutil.rmtree(test_dir) def test_pathcompleter_get_paths_constrains_path(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # add a subdir with 10 other files with different names subdir = os.path.join(test_dir, 'subdir') os.mkdir(subdir) write_test_files(subdir, 'abcdefghij') get_paths = lambda: ['subdir'] with chdir(test_dir): completer = PathCompleter(get_paths=get_paths) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] expected = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] assert expected == result # cleanup shutil.rmtree(test_dir) prompt_toolkit-1.0.15/tests/test_yank_nth_arg.py0000664000175000017500000000402013136130420023520 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.buffer import Buffer from prompt_toolkit.history import InMemoryHistory import pytest @pytest.fixture def _history(): " Prefilled history. " history = InMemoryHistory() history.append('alpha beta gamma delta') history.append('one two three four') return history # Test yank_last_arg. def test_empty_history(): buf = Buffer() buf.yank_last_arg() assert buf.document.current_line == '' def test_simple_search(_history): buff = Buffer(history=_history) buff.yank_last_arg() assert buff.document.current_line == 'four' def test_simple_search_with_quotes(_history): _history.append("""one two "three 'x' four"\n""") buff = Buffer(history=_history) buff.yank_last_arg() assert buff.document.current_line == '''"three 'x' four"''' def test_simple_search_with_arg(_history): buff = Buffer(history=_history) buff.yank_last_arg(n=2) assert buff.document.current_line == 'three' def test_simple_search_with_arg_out_of_bounds(_history): buff = Buffer(history=_history) buff.yank_last_arg(n=8) assert buff.document.current_line == '' def test_repeated_search(_history): buff = Buffer(history=_history) buff.yank_last_arg() buff.yank_last_arg() assert buff.document.current_line == 'delta' def test_repeated_search_with_wraparound(_history): buff = Buffer(history=_history) buff.yank_last_arg() buff.yank_last_arg() buff.yank_last_arg() assert buff.document.current_line == 'four' # Test yank_last_arg. def test_yank_nth_arg(_history): buff = Buffer(history=_history) buff.yank_nth_arg() assert buff.document.current_line == 'two' def test_repeated_yank_nth_arg(_history): buff = Buffer(history=_history) buff.yank_nth_arg() buff.yank_nth_arg() assert buff.document.current_line == 'beta' def test_yank_nth_arg_with_arg(_history): buff = Buffer(history=_history) buff.yank_nth_arg(n=2) assert buff.document.current_line == 'three' prompt_toolkit-1.0.15/AUTHORS.rst0000664000175000017500000000022413136130420020162 0ustar jonathanjonathan00000000000000Authors ======= Creator ------- Jonathan Slenders Contributors ------------ - Amjith Ramanujam prompt_toolkit-1.0.15/examples/0000775000175000017500000000000013136335632020137 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/examples/bottom-toolbar.py0000775000175000017500000000120013136130420023435 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example showing a bottom toolbar. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.styles import style_from_dict from prompt_toolkit.token import Token test_style = style_from_dict({ Token.Toolbar: '#ffffff bg:#333333', }) def main(): def get_bottom_toolbar_tokens(cli): return [(Token.Toolbar, ' This is a toolbar. ')] text = prompt('Say something: ', get_bottom_toolbar_tokens=get_bottom_toolbar_tokens, style=test_style) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/gevent-get-input.py0000775000175000017500000000117613136130420023707 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ For testing: test to make sure that everything still works when gevent monkey patches are applied. """ from __future__ import unicode_literals from gevent.monkey import patch_all from prompt_toolkit.shortcuts import prompt, create_eventloop if __name__ == '__main__': # Apply patches. patch_all() # There were some issues in the past when the event loop had an input hook. def dummy_inputhook(*a): pass eventloop = create_eventloop(inputhook=dummy_inputhook) # Ask for input. answer = prompt('Give me some input: ', eventloop=eventloop) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/colored-prompt.py0000775000175000017500000000205013136130420023443 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a colored prompt. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.styles import style_from_dict from prompt_toolkit.token import Token example_style = style_from_dict({ # User input. Token: '#ff0066', # Prompt. Token.Username: '#884444 italic', Token.At: '#00aa00', Token.Colon: '#00aa00', Token.Pound: '#00aa00', Token.Host: '#000088 bg:#aaaaff', Token.Path: '#884444 underline', # Make a selection reverse/underlined. # (Use Control-Space to select.) Token.SelectedText: 'reverse underline', }) def get_prompt_tokens(cli): return [ (Token.Username, 'john'), (Token.At, '@'), (Token.Host, 'localhost'), (Token.Colon, ':'), (Token.Path, '/user/john'), (Token.Pound, '# '), ] if __name__ == '__main__': answer = prompt(get_prompt_tokens=get_prompt_tokens, style=example_style) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/rprompt.py0000775000175000017500000000141513136130420022204 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a right prompt. This is an additional prompt that is displayed on the right side of the terminal. It will be hidden automatically when the input is long enough to cover the right side of the terminal. This is similar to RPROMPT is Zsh. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.styles import style_from_dict from prompt_toolkit.token import Token example_style = style_from_dict({ Token.RPrompt: 'bg:#ff0066 #ffffff', }) def get_rprompt_tokens(cli): return [ (Token, ' '), (Token.RPrompt, ''), ] if __name__ == '__main__': answer = prompt('> ', get_rprompt_tokens=get_rprompt_tokens, style=example_style) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/switch-between-vi-emacs.py0000775000175000017500000000253213136130420025134 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example that displays how to switch between Emacs and Vi input mode. """ from prompt_toolkit import prompt from prompt_toolkit.enums import EditingMode from prompt_toolkit.key_binding.defaults import load_key_bindings_for_prompt from prompt_toolkit.keys import Keys from prompt_toolkit.styles import style_from_dict from prompt_toolkit.token import Token def run(): # Create a `Registry` that contains the default key bindings. registry = load_key_bindings_for_prompt() # Add an additional key binding for toggling this flag. @registry.add_binding(Keys.F4) def _(event): " Toggle between Emacs and Vi mode. " if event.cli.editing_mode == EditingMode.VI: event.cli.editing_mode = EditingMode.EMACS else: event.cli.editing_mode = EditingMode.VI # Add a bottom toolbar to display the status. style = style_from_dict({ Token.Toolbar: 'reverse', }) def get_bottom_toolbar_tokens(cli): " Display the current input mode. " text = 'Vi' if cli.editing_mode == EditingMode.VI else 'Emacs' return [ (Token.Toolbar, ' [F4] %s ' % text) ] prompt('> ', key_bindings_registry=registry, get_bottom_toolbar_tokens=get_bottom_toolbar_tokens, style=style) if __name__ == '__main__': run() prompt_toolkit-1.0.15/examples/input-validation.py0000775000175000017500000000123513136130420023770 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of input validation. """ from __future__ import unicode_literals from prompt_toolkit.validation import Validator, ValidationError from prompt_toolkit import prompt class EmailValidator(Validator): def validate(self, document): if '@' not in document.text: raise ValidationError(message='Not a valid e-mail address (Does not contain an @).', cursor_position=len(document.text)) # Move cursor to end of input. def main(): text = prompt('Enter e-mail address: ', validator=EmailValidator()) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/autocompletion.py0000775000175000017500000000212613136130420023543 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Autocompletion example. Press [Tab] to complete the current word. - The first Tab press fills in the common part of all completions and shows all the completions. (In the menu) - Any following tab press cycles through all the possible completions. """ from __future__ import unicode_literals from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit import prompt animal_completer = WordCompleter([ 'alligator', 'ant', 'ape', 'bat', 'bear', 'beaver', 'bee', 'bison', 'butterfly', 'cat', 'chicken', 'crocodile', 'dinosaur', 'dog', 'dolphine', 'dove', 'duck', 'eagle', 'elephant', 'fish', 'goat', 'gorilla', 'kangoroo', 'leopard', 'lion', 'mouse', 'rabbit', 'rat', 'snake', 'spider', 'turkey', 'turtle', ], ignore_case=True) def main(): text = prompt('Give some animals: ', completer=animal_completer, complete_while_typing=False) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/system-prompt.py0000775000175000017500000000051313136130420023342 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': print('If you press meta-! or esc-! at the following prompt, you can enter system commands.') answer = prompt('Give me some input: ', enable_system_bindings=True) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/custom-key-binding.py0000775000175000017500000000372713136130420024221 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of adding a custom key binding to a prompt. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.key_binding.defaults import load_key_bindings_for_prompt from prompt_toolkit.keys import Keys def main(): # We start with a `Registry` of default key bindings. registry = load_key_bindings_for_prompt() # Add our own key binding to the registry of the 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') @registry.add_binding('x', 'y') def _(event): """ (Useless, but for demoing.) Typing 'xy' will insert 'z'. Note that when you type for instance 'xa', the insertion of 'x' is postponed until the 'a' is typed. because we don't know earlier whether or not a 'y' will follow. However, prompt-toolkit should already give some visual feedback of the typed character. """ event.cli.current_buffer.insert_text('z') @registry.add_binding('a', 'b', 'c') def _(event): " Typing 'abc' should insert 'd'. " event.cli.current_buffer.insert_text('d') @registry.add_binding(Keys.ControlT) def _(event): """ Print 'hello world' in the terminal when ControlT is pressed. We use ``run_in_terminal``, because that ensures that the prompt is hidden right before ``print_hello`` gets executed and it's drawn again after it. (Otherwise this would destroy the output.) """ def print_hello(): print('hello world') event.cli.run_in_terminal(print_hello) # Read input. print('Press F4 to insert "hello world", type "xy" to insert "z":') text = prompt('> ', key_bindings_registry=registry) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/inputhook.py0000775000175000017500000000450613136130420022525 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ An example that demonstrates how inputhooks can be used in prompt-toolkit. An inputhook is a callback that an eventloop calls when it's idle. For instance, readline calls `PyOS_InputHook`. This allows us to do other work in the same thread, while waiting for input. Important however is that we give the control back to prompt-toolkit when some input is ready to be processed. There are two ways to know when input is ready. One way is to poll `InputHookContext.input_is_ready()`. Another way is to check for `InputHookContext.fileno()` to be ready. In this example we do the latter. """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import prompt, create_eventloop from pygments.lexers import PythonLexer import gtk, gobject def hello_world_window(): """ Create a GTK window with one 'Hello world' button. """ # Create a new window. window = gtk.Window(gtk.WINDOW_TOPLEVEL) window.set_border_width(50) # Create a new button with the label "Hello World". button = gtk.Button("Hello World") window.add(button) # Clicking the button prints some text. def clicked(data): print('Button clicked!') button.connect("clicked", clicked) # Display the window. button.show() window.show() def inputhook(context): """ When the eventloop of prompt-toolkit is idle, call this inputhook. This will run the GTK main loop until the file descriptor `context.fileno()` becomes ready. :param context: An `InputHookContext` instance. """ def _main_quit(*a, **kw): gtk.main_quit() return False gobject.io_add_watch(context.fileno(), gobject.IO_IN, _main_quit) gtk.main() def main(): # Create user interface. hello_world_window() # Enable threading in GTK. (Otherwise, GTK will keep the GIL.) gtk.gdk.threads_init() # Read input from the command line, using an event loop with this hook. # We say `patch_stdout=True`, because clicking the button will print # something; and that should print nicely 'above' the input line. result = prompt('Python >>> ', eventloop=create_eventloop(inputhook=inputhook), lexer=PythonLexer, patch_stdout=True) print('You said: %s' % result) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/multi-column-autocompletion.py0000775000175000017500000000165313136130420026172 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Similar to the autocompletion example. But display all the completions in multiple columns. """ from __future__ import unicode_literals from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit import prompt animal_completer = WordCompleter([ 'alligator', 'ant', 'ape', 'bat', 'bear', 'beaver', 'bee', 'bison', 'butterfly', 'cat', 'chicken', 'crocodile', 'dinosaur', 'dog', 'dolphine', 'dove', 'duck', 'eagle', 'elephant', 'fish', 'goat', 'gorilla', 'kangoroo', 'leopard', 'lion', 'mouse', 'rabbit', 'rat', 'snake', 'spider', 'turkey', 'turtle', ], ignore_case=True) def main(): text = prompt('Give some animals: ', completer=animal_completer, display_completions_in_columns=True) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/get-multiline-input.py0000775000175000017500000000130713136130420024415 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.token import Token def continuation_tokens(cli, width): " The continuation: display dots before all the following lines. " # (make sure that the width of the continuation does not exceed the given # width. -- It is the prompt that decides the width of the left margin.) return [(Token, '.' * (width - 1) + ' ')] if __name__ == '__main__': print('Press [Meta+Enter] or [Esc] followed by [Enter] to accept input.') answer = prompt('Multiline input: ', multiline=True, get_continuation_tokens=continuation_tokens) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/get-password-with-toggle-display-shortcut.py0000775000175000017500000000166513136130420030673 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ get_password function that displays asterisks instead of the actual characters. With the addition of a ControlT shortcut to hide/show the input. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.key_binding.defaults import load_key_bindings_for_prompt from prompt_toolkit.keys import Keys from prompt_toolkit.filters import Condition def main(): hidden = [True] # Nonlocal registry = load_key_bindings_for_prompt() @registry.add_binding(Keys.ControlT) def _(event): ' When ControlT has been pressed, toggle visibility. ' hidden[0] = not hidden[0] print('Type Control-T to toggle password visible.') password = prompt('Password: ', is_password=Condition(lambda cli: hidden[0]), key_bindings_registry=registry) print('You said: %s' % password) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/persistent-history.py0000775000175000017500000000102413136130420024374 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of a CLI that keeps a persistent history of all the entered strings in a file. When you run this script for a second time, pressing arrow-up will go back in history. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.history import FileHistory def main(): our_history = FileHistory('.example-history-file') text = prompt('Say something: ', history=our_history) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/autocompletion-like-readline.py0000775000175000017500000000266313136130420026254 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Autocompletion example that displays the autocompletions like readline does by binding a custom handler to the Tab key. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline from prompt_toolkit.key_binding.defaults import load_key_bindings from prompt_toolkit.keys import Keys animal_completer = WordCompleter([ 'alligator', 'ant', 'ape', 'bat', 'bear', 'beaver', 'bee', 'bison', 'butterfly', 'cat', 'chicken', 'crocodile', 'dinosaur', 'dog', 'dolphine', 'dove', 'duck', 'eagle', 'elephant', 'fish', 'goat', 'gorilla', 'kangoroo', 'leopard', 'lion', 'mouse', 'rabbit', 'rat', 'snake', 'spider', 'turkey', 'turtle', ], ignore_case=True) # Create key bindings registry with a custom binding for the Tab key that # displays completions like GNU readline. registry = load_key_bindings() registry.add_binding(Keys.ControlI)(display_completions_like_readline) def main(): text = prompt('Give some animals: ', completer=animal_completer, key_bindings_registry=registry, # Important: for this to work: `complete_while_typing` needs # to be False. complete_while_typing=False) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/terminal-title.py0000775000175000017500000000043213136130420023431 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': def get_title(): return 'This is the title' answer = prompt('Give me some input: ', get_title=get_title) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/confirmation-prompt.py0000775000175000017500000000040713136130420024510 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a confirmation prompt. """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import confirm if __name__ == '__main__': answer = confirm('Should we do that? (y/n) ') print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/asyncio-prompt.py0000775000175000017500000000453713136130420023475 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ (Python >= 3.5) This is an example of how to embed a CommandLineInterface inside an application that uses the asyncio eventloop. The ``prompt_toolkit`` library will make sure that when other coroutines are writing to stdout, they write above the prompt, not destroying the input line. This example does several things: 1. It starts a simple coroutine, printing a counter to stdout every second. 2. It starts a simple input/echo cli loop which reads from stdin. Very important is the following patch. If you are passing stdin by reference to other parts of the code, make sure that this patch is applied as early as possible. :: sys.stdout = cli.stdout_proxy() """ from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.shortcuts import create_prompt_application, create_asyncio_eventloop, prompt_async import asyncio import sys loop = asyncio.get_event_loop() async def print_counter(): """ Coroutine that prints counters. """ i = 0 while True: print('Counter: %i' % i) i += 1 await asyncio.sleep(3) async def interactive_shell(): """ Like `interactive_shell`, but doing things manual. """ # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop() # Create interface. cli = CommandLineInterface( application=create_prompt_application('Say something inside the event loop: '), eventloop=eventloop) # Patch stdout in something that will always print *above* the prompt when # something is written to stdout. sys.stdout = cli.stdout_proxy() # Run echo loop. Read text from stdin, and reply it back. while True: try: result = await cli.run_async() print('You said: "{0}"'.format(result.text)) except (EOFError, KeyboardInterrupt): return def main(): shell_task = loop.create_task(interactive_shell()) # Gather all the async calls, so they can be cancelled at once background_task = asyncio.gather(print_counter(), return_exceptions=True) loop.run_until_complete(shell_task) background_task.cancel() loop.run_until_complete(background_task) print('Qutting event loop. Bye.') loop.close() if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/get-input-vi-mode.py0000775000175000017500000000047413136130420023757 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': print("You have Vi keybindings here. Press [Esc] to go to navigation mode.") answer = prompt('Give me some input: ', multiline=False, vi_mode=True) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/full-screen-layout.py0000775000175000017500000002026513136130420024237 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of a full screen application with a vertical split. This will show a window on the left for user input. When the user types, the reversed input is shown on the right. Pressing Ctrl-Q will quit the application. """ from __future__ import unicode_literals from prompt_toolkit.application import Application from prompt_toolkit.buffer import Buffer from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.key_binding.defaults import load_key_bindings from prompt_toolkit.keys import Keys from prompt_toolkit.layout.containers import VSplit, HSplit, Window from prompt_toolkit.layout.controls import BufferControl, FillControl, TokenListControl from prompt_toolkit.layout.dimension import LayoutDimension as D from prompt_toolkit.shortcuts import create_eventloop from prompt_toolkit.token import Token # 1. First we create the layout # -------------------------- # There are two types of classes that have to be combined to construct a layout. # We have containers and user controls. Simply said, containers are used for # arranging the layout, we have for instance `HSplit` and `VSplit`. And on the # other hand user controls paint the actual content. We have for instance # `BufferControl` and `TokenListControl`. An important internal difference is that # containers use absolute coordinates, while user controls paint on their own # `Screen` with a relative coordinates. # The Window class itself is a container that can contain a user control, so # that's the adaptor between the two. The Window class also takes care of # scrolling the content if the user control is painting on a screen that is # larger than what was available to the window. # So, for this example, we create a layout that shows the content of the # default buffer on the left, shows a line in the middle and another buffer # (called 'RESULT') on the right. layout = VSplit([ # One window that holds the BufferControl with the default buffer on the # left. Window(content=BufferControl(buffer_name=DEFAULT_BUFFER)), # A vertical line in the middle. We explicitely specify the width, to make # sure that the layout engine will not try to divide the whole width by # three for all these windows. The `FillControl` will simply fill the whole # window by repeating this character. Window(width=D.exact(1), content=FillControl('|', token=Token.Line)), # Display the Result buffer on the right. Window(content=BufferControl(buffer_name='RESULT')), ]) # As a demonstration. Let's add a title bar to the top, displaying "Hello world". # somewhere, because usually the default key bindings include searching. (Press # Ctrl-R.) It would be really annoying if the search key bindings are handled, # but the user doesn't see any feedback. We will add the search toolbar to the # bottom by using an HSplit. def get_titlebar_tokens(cli): return [ (Token.Title, ' Hello world '), (Token.Title, ' (Press [Ctrl-Q] to quit.)'), ] layout = HSplit([ # The titlebar. Window(height=D.exact(1), content=TokenListControl(get_titlebar_tokens, align_center=True)), # Horizontal separator. Window(height=D.exact(1), content=FillControl('-', token=Token.Line)), # The 'body', like defined above. layout, ]) # 2. Adding key bindings # -------------------- # As a demonstration, we will add just a ControlQ key binding to exit the # application. Key bindings are registered in a # `prompt_toolkit.key_bindings.registry.Registry` instance. We use the # `load_default_key_bindings` utility function to create a registry that # already contains the default key bindings. registry = load_key_bindings() # Now add the Ctrl-Q binding. We have to pass `eager=True` here. The reason is # that there is another key *sequence* that starts with Ctrl-Q as well. Yes, a # key binding is linked to a sequence of keys, not necessarily one key. So, # what happens if there is a key binding for the letter 'a' and a key binding # for 'ab'. When 'a' has been pressed, nothing will happen yet. Because the # next key could be a 'b', but it could as well be anything else. If it's a 'c' # for instance, we'll handle the key binding for 'a' and then look for a key # binding for 'c'. So, when there's a common prefix in a key binding sequence, # prompt-toolkit will wait calling a handler, until we have enough information. # Now, There is an Emacs key binding for the [Ctrl-Q Any] sequence by default. # Pressing Ctrl-Q followed by any other key will do a quoted insert. So to be # sure that we won't wait for that key binding to match, but instead execute # Ctrl-Q immediately, we can pass eager=True. (Don't make a habbit of adding # `eager=True` to all key bindings, but do it when it conflicts with another # existing key binding, and you definitely want to override that behaviour. @registry.add_binding(Keys.ControlC, eager=True) @registry.add_binding(Keys.ControlQ, eager=True) def _(event): """ Pressing Ctrl-Q or Ctrl-C will exit the user interface. Setting a return value means: quit the event loop that drives the user interface and return this value from the `CommandLineInterface.run()` call. Note that Ctrl-Q does not work on all terminals. Sometimes it requires executing `stty -ixon`. """ event.cli.set_return_value(None) # 3. Create the buffers # ------------------ # Buffers are the objects that keep track of the user input. In our example, we # have two buffer instances, both are multiline. buffers={ DEFAULT_BUFFER: Buffer(is_multiline=True), 'RESULT': Buffer(is_multiline=True), } # Now we add an event handler that captures change events to the buffer on the # left. If the text changes over there, we'll update the buffer on the right. def default_buffer_changed(default_buffer): """ When the buffer on the left (DEFAULT_BUFFER) changes, update the buffer on the right. We just reverse the text. """ buffers['RESULT'].text = buffers[DEFAULT_BUFFER].text[::-1] buffers[DEFAULT_BUFFER].on_text_changed += default_buffer_changed # 3. Creating an `Application` instance # ---------------------------------- # This glues everything together. application = Application( layout=layout, buffers=buffers, key_bindings_registry=registry, # Let's add mouse support! mouse_support=True, # Using an alternate screen buffer means as much as: "run full screen". # It switches the terminal to an alternate screen. use_alternate_screen=True) # 4. Run the application # ------------------- def run(): # We need to create an eventloop for this application. An eventloop is # basically a while-true loop that waits for user input, and when it # receives something (like a key press), it will send that to the # application. Usually, you want to use this `create_eventloop` shortcut, # which -- according to the environment (Windows/posix) -- returns # something that will work there. If you want to run your application # inside an "asyncio" environment, you'd have to pass another eventloop. eventloop = create_eventloop() try: # Create a `CommandLineInterface` instance. This is a wrapper around # `Application`, but includes all I/O: eventloops, terminal input and output. cli = CommandLineInterface(application=application, eventloop=eventloop) # Run the interface. (This runs the event loop until Ctrl-Q is pressed.) cli.run() finally: # Clean up. An eventloop creates a posix pipe. This is used internally # for scheduling callables, created in other threads into the main # eventloop. Calling `close` will close this pipe. eventloop.close() if __name__ == '__main__': run() # Some possible improvements. # a) Probably you want to add syntax highlighting to one of these buffers. This # is possible by passing a lexer to the BufferControl. E.g.: # from pygments.lexers import HtmlLexer # from prompt_toolkit.layout.lexers import PygmentsLexer # BufferControl(lexer=PygmentsLexer(HtmlLexer)) # b) Add search functionality. # c) Add additional key bindings to move the focus between the buffers. # d) Add autocompletion. prompt_toolkit-1.0.15/examples/autocorrection.py0000775000175000017500000000226513136130420023545 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of implementing auto correction while typing. The word "impotr" will be corrected when the user types a space afterwards. """ from __future__ import unicode_literals from prompt_toolkit.key_binding.defaults import load_key_bindings_for_prompt from prompt_toolkit import prompt # Database of words to be replaced by typing. corrections = { 'impotr': 'import', 'wolrd': 'world', } def main(): # We start with a `Registry` that contains the default key bindings. registry = load_key_bindings_for_prompt() # We add a custom key binding to space. @registry.add_binding(' ') def _(event): """ When space is pressed, we check the word before the cursor, and autocorrect that. """ b = event.cli.current_buffer w = b.document.get_word_before_cursor() if w is not None: if w in corrections: b.delete_before_cursor(count=len(w)) b.insert_text(corrections[w]) b.insert_text(' ') # Read input. text = prompt('Say something: ', key_bindings_registry=registry) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/get-input.py0000775000175000017500000000031413136130420022412 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ') print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/system-clipboard-integration.py0000775000175000017500000000121313136130420026277 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of a custom clipboard class. This requires the 'pyperclip' library to be installed. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard if __name__ == '__main__': print('Emacs shortcuts:') print(' Press Control-Y to paste from the system clipboard.') print(' Press Control-Space or Control-@ to enter selection mode.') print(' Press Control-W to cut to clipboard.') print('') answer = prompt('Give me some input: ', clipboard=PyperclipClipboard()) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/html-input.py0000775000175000017500000000070013136130420022576 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of a syntax-highlighted HTML input line. (This requires Pygments to be installed.) """ from __future__ import unicode_literals from pygments.lexers import HtmlLexer from prompt_toolkit import prompt from prompt_toolkit.layout.lexers import PygmentsLexer def main(): text = prompt('Enter HTML: ', lexer=PygmentsLexer(HtmlLexer)) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/multiline-prompt.py0000775000175000017500000000043013136130420024016 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of how the input can be indented. """ from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input:\n > ', multiline=True) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/auto-suggestion.py0000775000175000017500000000255213136130420023641 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of a CLI that demonstrates fish-style auto suggestion. When you type some input, it will match the input against the history. If One entry of the history starts with the given input, then it will show the remaining part as a suggestion. Pressing the right arrow will insert this suggestion. """ from __future__ import unicode_literals, print_function from prompt_toolkit import prompt from prompt_toolkit.history import InMemoryHistory from prompt_toolkit.interface import AbortAction from prompt_toolkit.auto_suggest import AutoSuggestFromHistory def main(): # Create some history first. (Easy for testing.) history = InMemoryHistory() history.append('import os') history.append('print("hello")') history.append('print("world")') history.append('import path') # Print help. print('This CLI has fish-style auto-suggestion enable.') print('Type for instance "pri", then you\'ll see a suggestion.') print('Press the right arrow to insert the suggestion.') print('Press Control-C to retry. Control-D to exit.') print() text = prompt('Say something: ', history=history, auto_suggest=AutoSuggestFromHistory(), enable_history_search=True, on_abort=AbortAction.RETRY) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/clock-input.py0000775000175000017500000000122413136130420022727 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a 'dynamic' prompt. On that shows the current time in the prompt. """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import prompt from prompt_toolkit.token import Token import datetime def get_prompt_tokens(cli): " Tokens to be shown before the prompt. " now = datetime.datetime.now() return [ (Token.Prompt, '%s:%s:%s' % (now.hour, now.minute, now.second)), (Token.Prompt, ' Enter something: ') ] def main(): result = prompt(get_prompt_tokens=get_prompt_tokens, refresh_interval=.5) print('You said: %s' % result) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/regular-language.py0000775000175000017500000000606213136130420023726 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ This is an example of "prompt_toolkit.contrib.regular_languages" which implements a litle calculator. Type for instance:: > add 4 4 > sub 4 4 > sin 3.14 This example shows how you can define the grammar of a regular language and how to use variables in this grammar with completers and tokens attached. """ from __future__ import unicode_literals from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit import prompt 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.layout.lexers import SimpleLexer from prompt_toolkit.styles import style_from_dict from prompt_toolkit.token import Token import math operators1 = ['add', 'sub', 'div', 'mul'] operators2 = ['sqrt', 'log', 'sin', 'ln'] def create_grammar(): return compile(""" (\s* (?P[a-z]+) \s+ (?P[0-9.]+) \s+ (?P[0-9.]+) \s*) | (\s* (?P[a-z]+) \s+ (?P[0-9.]+) \s*) """) example_style = style_from_dict({ Token.Operator: '#33aa33 bold', Token.Number: '#aa3333 bold', Token.TrailingInput: 'bg:#662222 #ffffff', }) if __name__ == '__main__': g = create_grammar() lexer = GrammarLexer(g, lexers={ 'operator1': SimpleLexer(Token.Operator), 'operator2': SimpleLexer(Token.Operator), 'var1': SimpleLexer(Token.Number), 'var2': SimpleLexer(Token.Number), }) completer = GrammarCompleter(g, { 'operator1': WordCompleter(operators1), 'operator2': WordCompleter(operators2), }) try: # REPL loop. while True: # Read input and parse the result. text = prompt('Calculate: ', lexer=lexer, completer=completer, style=example_style) m = g.match(text) if m: vars = m.variables() else: print('Invalid command\n') continue print(vars) if vars.get('operator1') or vars.get('operator2'): try: var1 = float(vars.get('var1', 0)) var2 = float(vars.get('var2', 0)) except ValueError: print('Invalid command (2)\n') continue # Turn the operator string into a function. operator = { 'add': (lambda a, b: a + b), 'sub': (lambda a, b: a - b), 'mul': (lambda a, b: a * b), 'div': (lambda a, b: a / b), 'sin': (lambda a, b: math.sin(a)), }[vars.get('operator1') or vars.get('operator2')] # Execute and print the result. print('Result: %s\n' % (operator(var1, var2))) elif vars.get('operator2'): print('Operator 2') except EOFError: pass prompt_toolkit-1.0.15/examples/custom-vi-operator-and-text-object.py0000775000175000017500000000427613136130420027256 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of adding a custom Vi operator and text object. (Note that this API is not guaranteed to remain stable.) """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.key_binding.defaults import load_key_bindings_for_prompt from prompt_toolkit.key_binding.bindings.vi import create_operator_decorator, create_text_object_decorator, TextObject from prompt_toolkit.enums import EditingMode def main(): # We start with a `Registry` of default key bindings. registry = load_key_bindings_for_prompt() # Create the decorators to be used for registering text objects and # operators in this registry. operator = create_operator_decorator(registry) text_object = create_text_object_decorator(registry) # Create a custom operator. @operator('R') def _(event, text_object): " Custom operator that reverses text. " buff = event.current_buffer # Get relative start/end coordinates. start, end = text_object.operator_range(buff.document) start += buff.cursor_position end += buff.cursor_position text = buff.text[start:end] text = ''.join(reversed(text)) event.cli.current_buffer.text = buff.text[:start] + text + buff.text[end:] # Create a text object. @text_object('A') def _(event): " A custom text object that involves everything. " # Note that a `TextObject` has coordinatens, relative to the cursor position. buff = event.current_buffer return TextObject( -buff.document.cursor_position, # The start. len(buff.text) - buff.document.cursor_position) # The end. # Read input. print('There is a custom text object "A" that applies to everything') print('and a custom operator "r" that reverses the text object.\n') print('Things that are possible:') print('- Riw - reverse inner word.') print('- yA - yank everything.') print('- RA - reverse everything.') text = prompt('> ', default='hello world', key_bindings_registry=registry, editing_mode=EditingMode.VI) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/up-arrow-partial-string-matching.py0000775000175000017500000000221313136130420027000 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of a CLI that demonstrates up-arrow partial string matching. When you type some input, it's possible to use the up arrow to filter the history on the items starting with the given input text. """ from __future__ import unicode_literals, print_function from prompt_toolkit import prompt from prompt_toolkit.history import InMemoryHistory from prompt_toolkit.interface import AbortAction def main(): # Create some history first. (Easy for testing.) history = InMemoryHistory() history.append('import os') history.append('print("hello")') history.append('print("world")') history.append('import path') # Print help. print('This CLI has up-arrow partial string matching enabled.') print('Type for instance "pri" followed by up-arrow and you') print('get the last items starting with "pri".') print('Press Control-C to retry. Control-D to exit.') print() text = prompt('Say something: ', history=history, enable_history_search=True, on_abort=AbortAction.RETRY) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/mouse-support.py0000775000175000017500000000062713136130420023347 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': print('This is multiline input. press [Meta+Enter] or [Esc] followed by [Enter] to accept input.') print('You can click with the mouse in order to select text.') answer = prompt('Multiline input: ', multiline=True, mouse_support=True) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/get-input-with-default.py0000775000175000017500000000057113136130420025012 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a call to `prompt` with a default value. The input is pre-filled, but the user can still edit the default. """ from __future__ import unicode_literals from prompt_toolkit import prompt import getpass if __name__ == '__main__': answer = prompt('What is your name: ', default='%s' % getpass.getuser()) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/abortaction.retry.py0000775000175000017500000000061313136130420024151 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demontration of the RETRY option. Pressing Control-C will not throw a `KeyboardInterrupt` like usual, but instead the prompt is drawn again. """ from __future__ import unicode_literals from prompt_toolkit import prompt, AbortAction if __name__ == '__main__': answer = prompt('Give me some input: ', on_abort=AbortAction.RETRY) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/finalterm-shell-integration.py0000775000175000017500000000210413136130420026104 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Mark the start and end of the prompt with Final term (iterm2) escape sequences. See: https://iterm2.com/finalterm.html """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.token import Token import sys BEFORE_PROMPT = '\033]133;A\a' AFTER_PROMPT = '\033]133;B\a' BEFORE_OUTPUT = '\033]133;C\a' AFTER_OUTPUT = '\033]133;D;{command_status}\a' # command_status is the command status, 0-255 def get_prompt_tokens(cli): # Generate the tokens for the prompt. # Important: use the `ZeroWidthEscape` token only if you are sure that # writing this as raw text to the output will not introduce any # cursor movements. return [ (Token.ZeroWidthEscape, BEFORE_PROMPT), (Token, 'Say something: # '), (Token.ZeroWidthEscape, AFTER_PROMPT), ] if __name__ == '__main__': answer = prompt(get_prompt_tokens=get_prompt_tokens) sys.stdout.write(BEFORE_OUTPUT) print('You said: %s' % answer) sys.stdout.write(AFTER_OUTPUT.format(command_status=0)) prompt_toolkit-1.0.15/examples/no-wrapping.py0000775000175000017500000000035613136130420022745 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ', wrap_lines=False, multiline=True) print('You said: %s' % answer) prompt_toolkit-1.0.15/examples/telnet.py0000775000175000017500000000327413136130420022001 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit.contrib.telnet.application import TelnetApplication from prompt_toolkit.contrib.telnet.server import TelnetServer from prompt_toolkit.shortcuts import create_prompt_application from prompt_toolkit.application import AbortAction from pygments.lexers import HtmlLexer import logging # Set up logging logging.basicConfig() logging.getLogger().setLevel(logging.INFO) class ExampleApplication(TelnetApplication): def client_connected(self, telnet_connection): # When a client is connected, erase the screen from the client and say # Hello. telnet_connection.erase_screen() telnet_connection.send('Welcome!\n') # Set CommandLineInterface. animal_completer = WordCompleter(['alligator', 'ant']) telnet_connection.set_application( create_prompt_application(message='Say something: ', lexer=HtmlLexer, completer=animal_completer, on_abort=AbortAction.RETRY), self.handle_command) def handle_command(self, telnet_connection, document): # When the client enters a command, just reply. if document.text == 'exit': telnet_connection.close() else: telnet_connection.send('You said: %s\n\n' % document.text) def client_leaving(self, telnet_connection): # Say 'bye' when the client quits. telnet_connection.send('Bye.\n') if __name__ == '__main__': TelnetServer(application=ExampleApplication(), port=2323).run() prompt_toolkit-1.0.15/examples/operate-and-get-next.py0000775000175000017500000000074513136130420024436 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demo of "operate-and-get-next". (Actually, this creates one prompt application, and keeps running the same app over and over again. -- For now, this is the only way to get this working.) """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import create_prompt_application, run_application def main(): app = create_prompt_application('prompt> ') while True: run_application(app) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/ansi-colors.py0000775000175000017500000000706413136130420022740 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of all the ANSI colors. """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import print_tokens from prompt_toolkit.styles import style_from_dict from prompt_toolkit.token import Token def main(): style = style_from_dict({ Token.Title: 'underline', Token.Black: '#ansiblack', Token.White: '#ansiwhite', Token.Red: '#ansired', Token.Green: '#ansigreen', Token.Yellow: '#ansiyellow', Token.Blue: '#ansiblue', Token.Fuchsia: '#ansifuchsia', Token.Turquoise: '#ansiturquoise', Token.LightGray: '#ansilightgray', Token.DarkGray: '#ansidarkgray', Token.DarkRed: '#ansidarkred', Token.DarkGreen: '#ansidarkgreen', Token.Brown: '#ansibrown', Token.DarkBlue: '#ansidarkblue', Token.Purple: '#ansipurple', Token.Teal: '#ansiteal', Token.BgBlack: 'bg:#ansiblack', Token.BgWhite: 'bg:#ansiwhite', Token.BgRed: 'bg:#ansired', Token.BgGreen: 'bg:#ansigreen', Token.BgYellow: 'bg:#ansiyellow', Token.BgBlue: 'bg:#ansiblue', Token.BgFuchsia: 'bg:#ansifuchsia', Token.BgTurquoise: 'bg:#ansiturquoise', Token.BgLightGray: 'bg:#ansilightgray', Token.BgDarkGray: 'bg:#ansidarkgray', Token.BgDarkRed: 'bg:#ansidarkred', Token.BgDarkGreen: 'bg:#ansidarkgreen', Token.BgBrown: 'bg:#ansibrown', Token.BgDarkBlue: 'bg:#ansidarkblue', Token.BgPurple: 'bg:#ansipurple', Token.BgTeal: 'bg:#ansiteal', }) tokens = [ (Token.Title, 'Foreground colors'), (Token, '\n'), (Token.Black, '#ansiblack'), (Token, '\n'), (Token.White, '#ansiwhite'), (Token, '\n'), (Token.Red, '#ansired'), (Token, '\n'), (Token.Green, '#ansigreen'), (Token, '\n'), (Token.Yellow, '#ansiyellow'), (Token, '\n'), (Token.Blue, '#ansiblue'), (Token, '\n'), (Token.Fuchsia, '#ansifuchsia'), (Token, '\n'), (Token.Turquoise, '#ansiturquoise'), (Token, '\n'), (Token.LightGray, '#ansilightgray'), (Token, '\n'), (Token.DarkGray, '#ansidarkgray'), (Token, '\n'), (Token.DarkRed, '#ansidarkred'), (Token, '\n'), (Token.DarkGreen, '#ansidarkgreen'), (Token, '\n'), (Token.Brown, '#ansibrown'), (Token, '\n'), (Token.DarkBlue, '#ansidarkblue'), (Token, '\n'), (Token.Purple, '#ansipurple'), (Token, '\n'), (Token.Teal, '#ansiteal'), (Token, '\n'), (Token.Title, 'Background colors'), (Token, '\n'), (Token.BgBlack, '#ansiblack'), (Token, '\n'), (Token.BgWhite, '#ansiwhite'), (Token, '\n'), (Token.BgRed, '#ansired'), (Token, '\n'), (Token.BgGreen, '#ansigreen'), (Token, '\n'), (Token.BgYellow, '#ansiyellow'), (Token, '\n'), (Token.BgBlue, '#ansiblue'), (Token, '\n'), (Token.BgFuchsia, '#ansifuchsia'), (Token, '\n'), (Token.BgTurquoise, '#ansiturquoise'), (Token, '\n'), (Token.BgLightGray, '#ansilightgray'), (Token, '\n'), (Token.BgDarkGray, '#ansidarkgray'), (Token, '\n'), (Token.BgDarkRed, '#ansidarkred'), (Token, '\n'), (Token.BgDarkGreen, '#ansidarkgreen'), (Token, '\n'), (Token.BgBrown, '#ansibrown'), (Token, '\n'), (Token.BgDarkBlue, '#ansidarkblue'), (Token, '\n'), (Token.BgPurple, '#ansipurple'), (Token, '\n'), (Token.BgTeal, '#ansiteal'), (Token, '\n'), (Token, '\n'), ] print_tokens(tokens, style=style) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/multi-column-autocompletion-with-meta.py0000775000175000017500000000201713136130420030062 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Autocompletion example that shows meta-information alongside the completions. """ from __future__ import unicode_literals from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit import prompt animal_completer = WordCompleter([ 'alligator', 'ant', 'ape', 'bat', 'bear', 'beaver', 'bee', 'bison', 'butterfly', 'cat', 'chicken', 'crocodile', 'dinosaur', 'dog', 'dolphine', 'dove', 'duck', 'eagle', 'elephant', ], meta_dict={ 'alligator': 'An alligator is a crocodilian in the genus Alligator of the family Alligatoridae.', 'ant': 'Ants are eusocial insects of the family Formicidae', 'ape': 'Apes (Hominoidea) are a branch of Old World tailless anthropoid catarrhine primates ', 'bat': 'Bats are mammals of the order Chiroptera', }, ignore_case=True) def main(): text = prompt('Give some animals: ', completer=animal_completer, display_completions_in_columns=True) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/get-password.py0000775000175000017500000000033013136130420023113 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': password = prompt('Password: ', is_password=True) print('You said: %s' % password) prompt_toolkit-1.0.15/examples/print-tokens.py0000775000175000017500000000106613136130420023140 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of printing colored text to the output. """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import print_tokens from prompt_toolkit.styles import style_from_dict from prompt_toolkit.token import Token def main(): style = style_from_dict({ Token.Hello: '#ff0066', Token.World: '#44ff44 italic', }) tokens = [ (Token.Hello, 'Hello '), (Token.World, 'World'), (Token, '\n'), ] print_tokens(tokens, style=style) if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/patch-stdout.py0000775000175000017500000000160213136130420023116 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ An example that demonstrates how `patch_stdout` works. This makes sure that output from other threads doesn't disturb the rendering of the prompt, but instead is printed nicely above the prompt. """ from __future__ import unicode_literals from prompt_toolkit import prompt import threading import time def main(): # Print a counter every second in another thread. running = True def thread(): i = 0 while running: i += 1 print('i=%i' % i) time.sleep(1) t = threading.Thread(target=thread) t.daemon = True t.start() # Now read the input. The print statements of the other thread # should not disturb anything. result = prompt('Say something: ', patch_stdout=True) print('You said: %s' % result) # Stop thread. running = False if __name__ == '__main__': main() prompt_toolkit-1.0.15/examples/tutorial/0000775000175000017500000000000013136335632022002 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/examples/tutorial/sqlite-cli.py0000775000175000017500000000323013136130420024407 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals import sys import sqlite3 from prompt_toolkit import AbortAction, prompt from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit.history import InMemoryHistory from pygments.lexers import SqlLexer from pygments.style import Style from pygments.styles.default import DefaultStyle from pygments.token import Token sql_completer = WordCompleter(['create', 'select', 'insert', 'drop', 'delete', 'from', 'where', 'table'], ignore_case=True) class DocumentStyle(Style): styles = { Token.Menu.Completions.Completion.Current: 'bg:#00aaaa #000000', Token.Menu.Completions.Completion: 'bg:#008888 #ffffff', Token.Menu.Completions.ProgressButton: 'bg:#003333', Token.Menu.Completions.ProgressBar: 'bg:#00aaaa', } styles.update(DefaultStyle.styles) def main(database): history = InMemoryHistory() connection = sqlite3.connect(database) while True: try: text = prompt('> ', lexer=SqlLexer, completer=sql_completer, style=DocumentStyle, history=history, on_abort=AbortAction.RETRY) except EOFError: break # Control-D pressed. with connection: try: messages = connection.execute(text) except Exception as e: print(repr(e)) else: for message in messages: print(message) print('GoodBye!') if __name__ == '__main__': if len(sys.argv) < 2: db = ':memory:' else: db = sys.argv[1] main(db) prompt_toolkit-1.0.15/PKG-INFO0000664000175000017500000002204713136335632017423 0ustar jonathanjonathan00000000000000Metadata-Version: 1.1 Name: prompt_toolkit Version: 1.0.15 Summary: Library for building powerful interactive command lines in Python Home-page: https://github.com/jonathanslenders/python-prompt-toolkit Author: Jonathan Slenders Author-email: UNKNOWN License: UNKNOWN Description: Python Prompt Toolkit ===================== |Build Status| |PyPI| ``prompt_toolkit`` is a library for building powerful interactive command lines and terminal applications in Python. Read the `documentation on readthedocs `_. Ptpython ******** `ptpython `_ is an interactive Python Shell, build on top of prompt_toolkit. .. image :: https://github.com/jonathanslenders/python-prompt-toolkit/raw/master/docs/images/ptpython.png prompt_toolkit features *********************** ``prompt_toolkit`` could be a replacement for `GNU readline `_, but it can be much more than that. Some features: - Pure Python. - Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.) - Multi-line input editing. - Advanced code completion. - Both Emacs and Vi key bindings. (Similar to readline.) - Even some advanced Vi functionality, like named registers and digraphs. - Reverse and forward incremental search. - Runs on all Python versions from 2.6 up to 3.5. - Works well with Unicode double width characters. (Chinese input.) - Selecting text for copy/paste. (Both Emacs and Vi style.) - Support for `bracketed paste `_. - Mouse support for cursor positioning and scrolling. - Auto suggestions. (Like `fish shell `_.) - Multiple input buffers. - No global state. - Lightweight, the only dependencies are Pygments, six and wcwidth. - Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems. - And much more... Feel free to create tickets for bugs and feature requests, and create pull requests if you have nice patches that you would like to share with others. About Windows support ********************* ``prompt_toolkit`` is cross platform, and everything that you build on top should run fine on both Unix and Windows systems. On Windows, it uses a different event loop (``WaitForMultipleObjects`` instead of ``select``), and another input and output system. (Win32 APIs instead of pseudo-terminals and VT100.) It's worth noting that the implementation is a "best effort of what is possible". Both Unix and Windows terminals have their limitations. But in general, the Unix experience will still be a little better. For Windows, it's recommended to use either `cmder `_ or `conemu `_. Installation ************ :: pip install prompt_toolkit For Conda, do: :: conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit Getting started *************** The most simple example of the library would look like this: .. code:: python from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ') print('You said: %s' % answer) For more complex examples, have a look in the ``examples`` directory. All examples are chosen to demonstrate only one thing. Also, don't be afraid to look at the source code. The implementation of the ``prompt`` function could be a good start. Note for Python 2: all strings are expected to be unicode strings. So, either put a small ``u`` in front of every string or put ``from __future__ import unicode_literals`` at the start of the above example. Projects using prompt_toolkit ***************************** Shells: - `ptpython `_: Python REPL - `ptpdb `_: Python debugger (pdb replacement) - `pgcli `_: Postgres client. - `mycli `_: MySql client. - `wharfee `_: A Docker command line. - `xonsh `_: A Python-ish, BASHwards-compatible shell. - `saws `_: A Supercharged AWS Command Line Interface. - `cycli `_: A Command Line Interface for Cypher. - `crash `_: Crate command line client. - `vcli `_: Vertica client. - `aws-shell `_: An integrated shell for working with the AWS CLI. - `softlayer-python `_: A command-line interface to manage various SoftLayer products and services. - `ipython `_: The IPython REPL - `click-repl `_: Subcommand REPL for click apps. - `haxor-news `_: A Hacker News CLI. - `gitsome `_: A Git/Shell Autocompleter with GitHub Integration. - `http-prompt `_: An interactive command-line HTTP client. - `coconut `_: Functional programming in Python. - `Ergonomica `_: A Bash alternative written in Python. - `Kube-shell `_: Kubernetes shell: An integrated shell for working with the Kubernetes CLI Full screen applications: - `pymux `_: A terminal multiplexer (like tmux) in pure Python. - `pyvim `_: A Vim clone in pure Python. (Want your own project to be listed here? Please create a GitHub issue.) Philosophy ********** The source code of ``prompt_toolkit`` should be readable, concise and efficient. We prefer short functions focussing each on one task and for which the input and output types are clearly specified. We mostly prefer composition over inheritance, because inheritance can result in too much functionality in the same object. We prefer immutable objects where possible (objects don't change after initialisation). Reusability is important. We absolutely refrain from having a changing global state, it should be possible to have multiple independent instances of the same code in the same process. The architecture should be layered: the lower levels operate on primitive operations and data structures giving -- when correctly combined -- all the possible flexibility; while at the higher level, there should be a simpler API, ready-to-use and sufficient for most use cases. Thinking about algorithms and efficiency is important, but avoid premature optimization. Special thanks to ***************** - `Pygments `_: Syntax highlighter. - `wcwidth `_: Determine columns needed for a wide characters. .. |Build Status| image:: https://api.travis-ci.org/jonathanslenders/python-prompt-toolkit.svg?branch=master :target: https://travis-ci.org/jonathanslenders/python-prompt-toolkit# .. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg :target: https://pypi.python.org/pypi/prompt-toolkit/ :alt: Latest Version Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python Classifier: Topic :: Software Development prompt_toolkit-1.0.15/CHANGELOG0000664000175000017500000012775513136335134017551 0ustar jonathanjonathan00000000000000CHANGELOG ========= 1.0.15: 2017-07-27 ------------------ Fixes: - Don't shuffle tasks in the event loop. This fixes an issue where lines printed from background threads were printed in a different order if `patch_stdout=True`. - Only consider the text before the cursor when activating history search. - Pressing escape should accept the search, this is closer to how readline works. - Enable autowrap again when required. New features: - Add run_in_terminal option to disable cooked mode. 1.0.14: 2017-03-26 ------------------ Fixes: - Handle arguments in the $EDITOR and $VISUAL env variable. - Added missing explicit loops for Futures in asyncio_posix eventloop. - Fallback to default terminal size if reported as 0. - Don't save undo state when receiving CPRResponse. - Set VMIN terminal flag to 1 when entering raw mode. (Fixes a bug on Solaris.) - Fix `previous_key_sequences`. 1.0.13: 2017-02-02 ------------------ Fixes: - The 1.0.11 and 1.0.12 builds went wrong. (1.0.11 contained an additional empty directory `prompt_toolkit/input/` which did hide the `prompt_toolkit/input.py` file, and 1.0.12 contained an additional broken `widgets.py` test file.) 1.0.11: 2017-02-02 ------------------ Fixes: - Only handle 'edit-and-execute-command' in Vi navigation mode. (This affects every tool that uses `enable_open_in_editor=True`.) 1.0.10: 2017-01-30 ------------------ Fixes: - Fixed the `NoConsoleScreenBuffer` error that appeared on some 64bit Python versions. - Fix mixup in the mapping from ANSI color names for vt100 output. New features: - Added a `reverse_vi_search_direction` option. - Handle Ctrl-Left/Right in rxvt. - Implemented difference between `backward-kill-word` and `unix-word-rubout`. - Implementation of the Emacs kill-ring (yank-pop command). - Take a 'file' argument in 'print_tokens'. - Implemented the `operate-and-get-next` command, bound to C-O in Emacs mode. - Added multiple named commands: * Added `insert-comment` command, bound to M-#. * Added `vi-editing-mode` and `emacs-editing-mode` commands. * Added `prefix-meta` command. * Added `edit-and-execute` command. * Added `complete`/`menu_complete`/`menu-complete-backward` commands. * Added `quoted-insert` command. - Take $VISUAL into account. - Display a quoted inserted using the `^` character, just like Vi does. - Implemented keyboard macros. (Like Readline.) - Extracted the Vi `create_operator_decorator` and `create_text_object_decorator` functions. (This makes it easier to implement custom Vi bindings.) - Pass `raw=True` to the `stdout_context` in `prompt_toolkit.shortcuts`. - Added `Buffer.validation_state`. (Don't call the validator again if the input didn't change.) Changes: - Refactoring of the key bindings. * All the load functions now create a new `Registry` object. * Added `MergedRegistry` and `ConditionalRegistry`. * Added `prompt_toolkit.key_binding.defaults` for loading the default key bindings. 1.0.9: 2016-11-07 ----------------- Fixes: - Fixed a bug in the `cooked_mode` context manager. This caused a bug in ptpython where executing `input()` would display ^M instead of accepting the input. - Handle race condition in eventloop/posix.py - Updated ANSI color names for vt100. (High and low intensity colors were swapped.) New features: - Added yank-nth-arg and yank-last-arg readline commands + Emacs bindings. - Allow searching in Vi selection mode. - Made text objects of the Vi 'n' and 'N' search bindings. This adds for instance the following bindings: cn, cN, dn, dN, yn, yN 1.0.8: 2016-10-16 ----------------- Fixes: - In 'shortcuts': complete_while_typing was a SimpleFilter, not a CLIFilter. - Always reset color attributes after rendering. - Handle bug in Windows when '$TERM' is not defined. - Ignore errors when calling tcgetattr/tcsetattr. (This handles the "Inappropriate ioctl for device" crash in some scenarios.) - Fix for Windows. Correctly recognize all Chinese and Lithuanian characters. New features: - Added shift+left/up/down/right keys. - Small performance optimization in the renderer. - Small optimization in the posix event loop. Don't call time.time() if we don't have an inputhook. (Less syscalls.) - Turned the _max_postpone_until argument of call_from_executor into a float. (As returned by `time.time`.) This will do less system calls. It's backwards-incompatible, but this is still a private API, used only by pymux.) - Added Shift-I/A commands in Vi block selection mode for inserting text at the beginning of each line of the block. - Refactoring of the 'selectors' module for the posix event loop. (Reuse the same selector object in one loop, don't recreate it for each select.) 1.0.7: 2016-08-21 ----------------- Fixes: - Bugfix in completion. When calculating the common completion to be inserted, the new completions were calculated wrong. - On Windows, avoid extra vertical scrolling if the cursor is already on screen. New features: - Support negative arguments for next/previous word ending/beginning. 1.0.6: 2016-08-15 ----------------- Fixes: - Go to the start of the line in Vi nagivation mode, when 'j' or 'k' have been pressed to navigate to a new history entry. - Don't crash when pasting text that contains \r\n characters. (This could happen in iTerm2.) - Python 2.6 compatibility fix. - Allow pressing before each -ve argument. - Better support for conversion from #ffffff values to ANSI colors in Vt100_Output. * Prefer colors with some saturation, instead of gray colors, if the given color was not gray. * Prefer a different foreground and background color if they were originally not the same. (This avoids concealing text.) New features: - Improved ANSI color support. * If the $PROMPT_TOOLKIT_ANSI_COLORS_ONLY environment variable has been set, use the 16 ANSI colors only. * Take an `ansi_colors_only` parameter in `Vt100_Output` and `shortcuts.create_output`. 1.0.5: 2016-08-04 ----------------- Fixes: - Critical fix for running on Windows. The gevent work-around in the inputhook caused 'An operation was attempted on something that is not a socket'. 1.0.4: 2016-08-03 ----------------- Fixes: - Key binding fixes: * Improved handling of repeat arguments in Emacs mode. Pressing sequences like 'esc---123' do now work (like GNU Readline): - repetition of the minus sign is ignored. - No esc prefix is required for each digit. * Fix in ControlX-ControlX binding. * Fix in bracketed paste. * Pressing Control-U at the start of the line now deletes the newline. * Pressing Control-K at the end of the line, deletes the newline after the cursor. * Support negative argument for Control-K * Fixed cash when left/right were pressed with a negative argument. (In Emacs mode.) * Fix in ControlUp/ControlDown key bindings. * Distinguish backspace from Control-H. They are not the same. * Delete in front of the cursor when a negative argument has been given to backspace. * Handle arrow keys correctly in emacs-term. - Performance optimizations: * Performance optimization in Registry. * Several performance optimization in filters. * Import asyncio inline (only if required). - Use the best possible selector in the event loop. This fixes bugs in situations where we have too many open file descriptors. - Fix UI freeze when gevent monkey patch has been applied. - Fix segmentation fault in Alpine Linux. (Regarding the use of ioctl.) - Use the correct colors on Windows. (When the foreground/background colors have been modified.) - Display a better error message when running in Idle. - Additional flags for vt100 inputs: disable flow control. - Also patch stderr in CommandLineInterface.patch_stdout_context. New features: - Allow users to enter Vi digraphs in reverse order. - Improved autocompletion behaviour. See IPython issue #9658. - Added a 'clear' function in the shortcuts module. For future compatibility: - `Keys.Enter` has been added. This is the key that should be bound for handling the enter key. Right now, prompt_toolkit translates \r into \n during the handling of the input; this is not correct and makes it impossible to distinguish between ControlJ and ControlM. Some applications bind ControlJ for custom handling of the enter key, because this equals \n. However, in a future version we will stop replacing \r by \n and at that point, the enter key will be ControlM. So better is to use `Keys.Enter`, which becomes an alias for whatever the enter key translates into. 1.0.3: 2016-06-20 ----------------- Fixes: - Bugfix for Python2 in readline-like completion. - Bugfix in readline-like completion visualisation. New features: - Added `erase_when_done` parameter to the `Aplication` class. (This was required for the bug fixes.) - Added (experimental) `CommandLineInterface.run_application_generator` method. (Also required for the bug fix.) 1.0.2: 2016-06-16 ----------------- Fixes: - Don't select the first completion when `complete_while_typing` is False. (Restore the old behaviour.) 1.0.1: 2016-06-15 ----------------- Fixes: - Bugfix in GrammarValidator and SentenceValidator. - Don't leave the alternate screen on resize events. - Use errors=surrogateescape, in order to handle mouse events in some terminals. - Ignore key presses in _InterfaceEventLoopCallbacks.feed_key when the CLI is in the done state. - Bugfix in get_common_complete_suffix. Don't return any suffix when there are completions that change whatever is before the cursor. - Bugfix for Win32/Python2: use unicode literals: This crashed arrow navigation on Windows. - Bugfix in InputProcessor: handling of more complex key bindings. - Fix: don't apply completions, if there is only one completion which doesn't have any effect. - Fix: correctly handle prompts starting with a newline in prompt_toolkit.shortcuts. - Fix: thread safety in autocomplete code. - Improve styling for matching brackets. (Allow individual styling for the bracket under the cursor and the other.) - Fix in ShowLeadingWhiteSpaceProcessor/ShowTrailingWhiteSpaceProcessor: take output encoding into account. (The signature had to change a little for this.) - Bug fix in key bindings: only activate Emacs system/open-in-editor bindings if editing_mode is emacs. - Added write_binary parameter to Vt100_Output. This fixes a bug in some cases where we expect it to write non-encoded strings. - Fix key bindings for Vi mode registers. New features (**): - Added shortcuts.confirm/create_confirm_application function. - Emulate bracketed paste on Windows. (When the input stream contains multiple key presses among which a newline and at least one other character, consider this a paste event, and handle as bracketed paste on Unix. - Added key handler for displaying completions, just like readline does. - Implemented Vi guu,gUU,g~~ key bindings. - Implemented Vi 'gJ' key binding. - Implemented Vi ab,ib,aB,iB text objects. - Support for ZeroWidthEscape tokens in prompt and token lists. Used to support final shell integration. - Fix: Make document.text/cursor_position/selection read-only. (Changing these would break the caching causing bigger issues.) - Using pytest for unit tests. - Allow key bindings to have Keys.Any at any possible position. (Not just the end.) This made it significantly easier to write the named register Vi bindings, resulting in an approved start-up time.) - Better feedback when entering multi-key key bindings in insert mode. (E.g. when 'jj' would be mapped to escape.) - Small improvement in key processor: allow key bindings to generate new key presses. - Handle ControlUp and ControlDown by default: move to the previous/next record in the history. - Accept 'char'/'get_char' parameters in FillControl. - Added refresh_interval method to prompt() function. Performance improvements: - Improve the performance of test_callable_args: this should significantly increase the start-up time. - Start-up time for creating the Vi bindings has been improved significantly. (**) Some small backwards-compatible features were allowed for this minor release. After evaluating the impact/risk/work involved we concluded that we could ship these in a minor release. 1.0.0: 2016-05-05 ----------------- Fixes: - Adjust minimum completion menu width to match UIControl and Window class. - Bugfix regarding weakref in InputProcessor. - Fix for pypy3: bug in WeakValueDictionary. - Correctly handle '0' key binding in Vi mode. - Also load Vi bindings by default in Application if no registry has been given. - Only go into selection mode if the current buffer is not empty. - Close PipeInput after usage. - Only use 16 colors in (Emacs) eterm-color. - Bugfix in "xP Vi key binding. - Bugfix in Vi { and } key binding. - Fix: use correct token for Scrollbar in MultiColumnCompletionMenuControl. - Handle negative values in translate_row_col_to_index. - Handle decomposed unicode characters. - Fixed Window.always_hide_cursor. (Parameter was ignored.) - Fix in zz Vi key binding. (When render info is not available.) - Fix in Document.get_cursor_up_position. (When an argument is given.) New features: - Separated `load_mouse_bindings`. - Refactoring/simplification of the key bindings: better use of filters and CLI.editing_mode. - Added DummyOutput class and a few unit tests that test the whole CLI. - Use the bisect module in Document._line_start_indexes instead of a custom binary search. This should improve the performance. - Stay in the same column when doing multiple up/down movements. - Visual improvements: * Implemented cursorcolumn, cursorline and colorcolumn. * Only reserve menu space when `complete_while_typing=True` or when there are completions to be displayed. * Support for chaining tokens for combined styles. SelectedText will now reverse the colors from the highlighting by default. Style `Token.SelectedText` to set a fixed foreground/background. Also for SearchMatch, we now use combined tokens. * Support for dark gray on Windows. * Default token for SystemToolbar and SearchToolbar. * Display selection also on empty lines. - Emacs key bindings improved: * Recognize + handle ControlDelete key. * Implemented meta-* and control-backslash key bindings. - Vi key bindings improved: * Handle inclusive and linewise motions properly. * Fix g_ motion off by one character, and don't work when cursor is in the trailing whitespace part of line. * Make a(/a)/i(/i)/... motions. Find enclosing brackets instead of the next bracket. * Update N% motion according to vim behaviors. * Fix | motion off by one character. * ge/gE motions go to end of previous word, not start. * Added Vi 'gm' key binding. * Implemented 'gq' key binding in Vi mode. (Reshape text.) * Vi operator/text object separation for key bindings. * Added 'ap' (auto-paragraph) text object. * Implemented Vi digraphs. ControlK will now insert a digraph. * Implemented vi tilde_operator. * Support named registers. * Vi < and > key bindings became operators. * Text objects and motions are now separate bindings. * Improved copy/paste in Vi mode. Backwards-incompatible changes: - Don't reset the current buffer anymore by default in CommandLineInterface.run(). Passing `reset_current_buffer=True` is now required. - Renamed MouseEventTypes to MouseEventType for consistency. The old name is still valid, but deprecated. - Refactoring of Callbacks. All events should now receive one argument, which is the sender. (Furter, Callback was renamed to Event.) This is mostly used internally. - Moved on_invalidate callback from CommandLineInterface to Application - Renamed `PipeInput.send` to `PipeInput.send_text`. (Old deprecated name is still kept as a valid alias.) - Renamed SimpleLexer.default_token to SimpleLexer.token. (+ backwards-compatibility.) - Refactoring of the filters: `ViStateFilter` has been deprecated. (Should not be used anymore.) Use the filters, as defined in prompt_toolkit.filters. - `editing_mode` is now a property of `CommandLineInterface`. This is replacing the `vi_mode` parameter in `KeyBindingManager`. - The default accept_action for the default Buffer in Application now becomes IGNORE. This is a much more sensible default. Pass RETURN_DOCUMENT to get the previous behaviour, - Always expect an EventLoop instance in CommandLineInterface. Creating it in __init__ caused a memory leak. 0.60: 2016-03-14 ---------------- Fixes: - Fix in Document.paste. (The screen was not updated after an undo of a paste.) - Don't use deprecated inspect.getargspec on Python 3. - Fixed reading input on Windows when input was piped in stdin. - Use correct file descriptors for input/output in run_system_command. - Always correctly split prompt in shortcuts.prompt. (Even when multiline=False) - Correctly align right prompt to the top when the left prompt consists of multiple lines. - Correctly use Token.Transparent as default token for a TokenListControl. - Fix in syntax synchronisation. (Better handle the case when no synchronisation point was found.) - Send SIGTSTP to the whole process group. - Correctly raise on_buffer_changed on all text changes. - Fix in regular_languages.GrammarLexer. (Fixes bug in ptipython syntax highlighting.) New features: - Add support for additional readers to the Win32 event loop. - Added on_render event. - Carry the weight in layout dimensions to allow stretching. 0.59: 2016-02-27 ---------------- Fixes: - Set correct default color on Windows. (Gray instead of high intensity gray.) - Reverse colors on Windows when foreground/background color have not been specified. - Correct handling of mouse events for FillControl. - Take margin into account when calculating Window height. (Fixes bug in multiline prompt.) - Handle division by zero in UIContent.get_height_for_text. 0.58: 2016-02-23 ---------------- Fixes: - Correctly return result for mouse handler in TokenListControl. - Bugfix in meta-backspace key binding. (Delete all whitespace before the cursor, when there is only whitespace.) - Bugfix in Vi gu, gU, g? and g~ key bindings (in selection mode). - Correctly restore default console attributes on Windows. - Disable bracketed paste support in ConEmu. (This was broken.) - When an unknown exception is raised in `CommandLineInterface.run()`, don't forget to redraw the CLI. New features: - Many performance improvements and better caching. (Especially in the `Document` class.) - Support for continuation tokens in `shortcuts.prompt` and `shortcuts.create_prompt_layout`. - Added `shortcuts.print_tokens` function for printing colored output. - Sound bell when nothing was deleted. - Added escape sequences for F1-F5 keys on the Linux console. - Improved support for the Linux console. (Switch back to 16 colors.) - Added F13-F24 input codes for xterm. - Created prompt_toolkit.token. A custom Token implementation, that is compatible with Pygments.token. (This way, Pygments becomes an optional dependency. For many use cases, nothing except the Token class from Pygments was used, so it was a bit overkill to install Pygments for only that.) - Refactoring of prompt_toolkit.styles. - `Float` objects got a `hide_when_covering_content` option. - Implementation of RPROMPT, like ZSH: Added `get_rprompt_tokens` to `create_prompt_layout`. - Some improvements to the default style. - Also handle Ctrl-R and Ctrl-S in Vi mode when searching. - Added TabsProcessor: a tool to visualise tabs instead of displaying ^I. - Give a better error message when trying to run in git-bash. - Support for ANSI color names in style dictionaries. - Big refactoring of the `Window` and `UIControl` classes. This should result in huge performance improvements on big inputs. (While first, a document could have 1,000 lines; now it can have about 100,000 lines on the same system.) The Window and UIControl have been rewritten very much. Rather than each time rendering the whole user control, we now only have to render the visible part. Because of this, many pieces had to be rewritten: - UIControls work differently. They return a `UIContent` instance that consist of a collection of lines. - All processors have been rewritten. (Their API changed as well, because they process one line at a time.) - Lexers work differently. `Lexer.lex_document` should now return a function that returns the tokens for one line. PygmentsLexer has been optimized that it becomes 'lazy', and it has optional syntax synchronisation. That means, that the lexer doesn't have to start the lexing at the beginning of the document. (Which would be slow for big documents.) Backwards-incompatible changes: - As mentioned above, the refactoring of `Window` and `UIControl` caused many "internal" APIs to change. All custom `UIControl`, `Processor` and `Lexer` classes have to be rewritten. However, for most applications this should not be an issue. Especially, the `shortcuts.prompt` function is backwards-compatible. - `wrap_lines` became a property of `Window` instead of `BufferControl`. 0.57: 2016-01-04 ---------------- Fixes: - Made `max_render_postpone_time` configurable. The current default was bad. (We should probably always draw the UI once every cycle of the event loop.) 0.56: 2016-01-03 ---------------- Fixes: - Fix in bracketed paste. It was not correctly enabled for each prompt. 0.55: 2016-01-03 ---------------- New features: - Implemented bracketed paste mode. (This allows much faster pasting, as well as pasting without going into paste mode. This makes sure that indentation in ptpython for instance is kept correctly.) - Added support for italic output and blink. (For terminals that support it.) - Added get_horizontal_scroll, get_vertical_scroll and always_hide_cursor parameters to Window. - Refactoring of the posix event loop. Better scheduling of all tasks/FDs to avoid starvation. (Everything should feel more responsive in high CPU situations.) - Added get_default_char function to TokenListControl. - AppendAutoSuggestion now accepts a token parameter. - Support for ansi color names in styles. - Accept get_width/get_height parameters in Float. - Added Output.write_raw and accept 'raw' parameter in CommandLineInterface.stdout_proxy. - Better caching of tokens in TokenListControl. - Add mouse support to TokenListControl. - Display "Window too small" when the window becomes too small. - Added 'bell' function to Output. - Accept weights in HSplit/VSplit. - Added Registry.remove_binding method to dynamically remove key bindings. - Added focus_on_click parameter to BufferControl. - Introduced BufferMapping class as a wrapper around the buffers dictionary. This one also contains the focus stack. - Improved 'v' and 'V' key bindings. Allow switching between line and character selection modes. - Added layout.highlighters. A new, much faster way to do selection and search highlighting. - Make search_state dynamic for key bindings. - Added 'sentence' option to WordCompleter. - Cache Document.lines for better performance. - Implementation of BLOCK selections. (Cut, copy, paste.) - Accept a 'reserve_space_for_menu' parameter in the shortcuts. (This is an integer.) - Support for 24bit true color on vt100 terminals. - Added CommandLineInterface.on_invalidate event. - Added __version__ to __init__.py. Fixes: - Always show cursor in the 'done' state. - Allow HSplit to have zero children. - Bugfix for handling of backslash on Windows with some non-us keyboards. (Ptpython issue #28.) - Never render characters outside the visible screen region. - Fix in WordCompleter. When case insensitive and input contained uppercase. - Highlight search match when the cursor is at any position on the match. (not just the beginning.) Backwards-incompatible changes: (Most changes will probably not have an impact on external applications.) - Change in the `Style` API. This allows caching of Attrs in renderer and faster rendering. (Style now has a get_attrs_for_token instead of a get_token_to_attributes_dict method.) - Removed DefaultStyle. Created PygmentsStyle.from_defaults class method instead. - Removed AbortAction.IGNORE. This was ambiguous. - Accept 'cli' parameter in 'walk' and 'find_window_for_buffer_name'. - The focus stack is now stored in BufferMapping. - ViStateFilter and KeyBindingManager now accept a get_vi_state callable instead of vi_state itself. (This way a key bindings registry becomes stateless.) - HighlightSearchProcessor and HighlightSelectionProcessor became deprecated. (Use highlighters instead.) 0.54: 2015-10-29 ---------------- New features: - Allow CommandLineInterface to run in any thread. - Hide cursor while rendering. - Added add_reader/remove_reader methods to EventLoop. - Support for 'reverse' style. - Redraw more lazy, by using invalidate. - Added show_cursor property to Screen. - Center or right align text in TokenListControl also when it spans multiple lines. Fixes: - Bugfix in PathCompleter. (Expanduser issue.) - Fix in signal handler. - Use utf-8 encoding in Vt100_Output by default. - Use correct default token in BufferControl. - Fix in ControlL key binding. Use @handle to allow desactivation. Backwards-incompatible changes: - Renamed create_default_layout to create_prompt_layout - Renamed create_default_application to create_prompt_application - Renamed Layout to Container. - Renamed CommandLineInterfaces.request_redraw to invalidate. - Changed the actual value of SEARCH_BUFFER, DEFAULT_BUFFER, SYSTEM_BUFFER and DUMMY_BUFFER. - Changed order of keyword arguments of the BufferControl class. "buffer_name" now comes first. - Removed old pt(i)python code. 0.53: 2015-10-06 ---------------- New features: - Handling of the insert key in Vi mode. - Added 'zt' and 'zb' Vi key bindings. - Added delete key binding for deleting selected text. - Select word below cursor on double mouse click. - Added `wrap_lines` option to TokenListControl. - Added `KeyBindingManager.for_prompt`. Fixes: - Fix in rendering output. - Reset renderer correctly in run_in_terminal. - Only reset buffer when using `AbortAction.RETRY`. - Fix in handling of exit (Ctrl-D) key presses. - Fix in `CompleteEvent`. Correctly set `completion_requested`. Backwards-incompatible changes: - Renamed `ValidationError.index` to `ValidationError.cursor_position`. - Renamed `shortcuts.get_input` to `shortcuts.prompt`. - Return empty string instead of None in `Document.current_char`/`char_before_cursor`. 0.52: 2015-09-24 ---------------- Fixes: - Fix in auto suggestion: hide suggestion when accepting input. 0.51: 2015-09-24 ---------------- New features: - Mouse support. (Scrolling and clicking for vt100 terminals. For Windows only clicking.) Both the autocompletion menus and buffer controls respond to scolling and clicking. - Added auto suggestions. (Like the fish shell.) - Stdout proxy become thread safe. - Linewrapping can now be disabled, instead we get horizontal scrolling. - Line numbering can now be relative. Like the vi 'relativenumber' option. Fixes: - Fixed excessive scrolling in Windows. - Bugfix in search highlighting. - Copy all words during repetition of Ctrl-W presses. - The 'libs' folder has been removed. - Fix in MultiColumnCompletionsMenu: don't create very big columns. Backwards-incompatible changes: - Disable search by default in KeyBindingManager. - Separated abort/exit key bindings. Disabled by default in KeyBindingManager. - 'Ignore' became the default on_abort action in `Application`. - 'Ignore' became the default accept_action in `Buffer`. - The layout processors have been refactored. The API is changed. - `SwitchableValidator` has been renamed to `ConditionalValidator`. - `WindowRenderInfo` has several incompatible changes. - Margins have been refactored completely. Now it's the window that has the margin instead of `BufferControl`. Is is both much more performant and flexible. 0.50: 2015-09-06 ---------------- Fix: - Leaving of alternate screen on Windows. 0.49: 2015-09-06 ---------------- New features: - Added MANIFEST.in - Better support for multiline prompts in shortcuts. - Added Document.set_document method. - Added 'default' argument to `shortcuts.create_default_application`. - Added `align_center` option for `TokenListControl`. - Added optional key bindings for full page navigation. (Moved key bindings from pyvim into prompt-toolkit.) - Accepts default_char in BufferControl for filling the background. - Added InFocusStack filter. Fixes: - Small fix in TokenListControl: use the right Char for aligning. Backwards-incompatible changes: - Removed deprecated 'tokens' attribute from GrammarLexer. 0.48: 2015-09-02 ---------------- New features: - run_in_terminal now returns the result of the called function. - Made history attribute of Buffer class public. - Added support for sub CommandLineInterfaces. - Accept optional vi_state parameter in KeyBindingManager. Fixes: - Pop-up menu positioning. The menu was shown too often above instead of below the cursor. - Fix in Control-W key binding. When there is only whitespace before the cursor, delete the whitespace. - Rendering bug fix in open_in_editor: run editor using cli.run_in_terminal. - Fix in renderer. Correctly reserve the vertical space as required by the layout. - Small fix in Margin ABC. - Added __iter__ to History ABC. - Small bugfix in CommandLineInterface: create correct eventloop when no eventloop was given. - Never schedule a second repaint operation when a previous was not yet executed. 0.47: 2015-08-19 ---------------- New features: - Added `prompt_toolkit.layout.utils.iter_token_lines`. - Allow `None` values on the focus stack. - Buffers can be readonly. Added `IsReadOnly` filter. - `eager` behaviour for key bindings. When a key binding is eager it will be executed as soon as it's matched, even when there is another binding that starts with this key sequence. - Custom margins for BufferControl. Fixes: - Don't trigger autocompletion on paste. - Added `pre_run` parameter to CommandLineInterface. - Correct invalidation of BeforeInput and AfterInput. - Correctly handle transparancy. (For floats.) - Small change in the algorithm to determine Window dimensions: keep in the bounds of the Window dimensions. Backwards-incompatible changes: - There a now a `Lexer` abstract base class. Every lexer should be an instance of that class, and Pygments lexers should be wrapped in a `PygmentsLexer` class. `prompt_toolkit.shortcuts` still accepts Pygments lexers directly for backwards-compatibilty. - BufferControl no longer has a `show_line_numbers` argument. Pass a `NumberredMargin` instance instead. - The `History` class became an abstract base class and only defines an interface. The default history class is now called `InMemoryHistory`. 0.46: 2015-08-08 ---------------- New features: - By default, in shortcuts, only show search highlighting when the search is the current input buffer. - Accept 'count' for all search operations. (For repetition.) - `shortcuts.create_default_layout` accepts a `multiline` parameter. - Show meta display text for completions also in multi-column mode. Fixes: - Correct invalidation of DefaultPrompt when search direction changes. - Correctly include/exclude current cursor position in search. - More consistency in styles. - Fix in ConditionalProcessor.has_focus. - Python 2.6 compatibility fix. - Show cursor at the correct position during reverse-i-search. - Fixed stdout encoding bug for vt100 output. Backwards-incompatible changes: - Use of `ConditionalContainer` everywhere. The `Window` class no longer accepts a `filter` argument to decide about the visibility. Instead wrapping inside a `ConditionalContainer` class is required. 0.45: 2015-07-30 ---------------- Fixes: - Bug fix on OS X: correctly detect platform as not Windows. 0.44: 2015-07-30 ---------------- Fixes: - Fixed bug in eventloops: handle timeout correctly, even when there is an eventhook. - Bug fix in open-in-editor: set correct cursor position. New features: - CompletionsMenu got a scroll_offset. - Use 256 colors and ANSI sequences when ConEmu ANSI support has been detected. - Added PyperclipClipboard for synchronisation with the system clipboard. and clipboard parameter in shortcut functions. - Filter for enabling/disabling handling of Vi 'v' binding. 0.43: 2015-07-15 ---------------- Fixes: - Windows bug fix. STD_INPUT_HANDLE should be c_ulong instead of HANDLE. (This caused crashes on some systems.) New features: - Added eventloop and patch_stdout parameters to get_input. - Inputhook support added. - Added ShowLeadingWhiteSpaceProcessor and ShowTrailingWhiteSpaceProcessor processors. - Accept Filter as multiline parameter in 'shortcuts'. - MultiColumnCompletionsMenu + display_completions_in_columns parameter in shortcuts. Backwards incompatible changes: - Layout.width was renamed to preferred_width and now receives a max_available_width parameter. 0.42: 2015-06-25 ---------------- Fixes: - Support for Windows cmder and conemu consoles. - Correct handling of unicode input and output on Windows. New features: - Support terminal titles. - Handle Control-Enter as Meta-Enter on Windows. - Control-Z key binding for Windows. - Implemented alternate screen buffer on Windows. - Clipboard became an ABC and InMemoryClipboard default implementation. 0.41: 2015-06-20 ---------------- Fixes: - Emacs Control-T key binding. - Color fix for Windows consoles. New features: - Allow both booleans and Filters in many places. - `password` can be a Filter now. 0.40: 2015-06-15 ---------------- Fixes: - Fix in output_screen_diff: reset correctly. - Ignore flush errors in vt100_output. - Implemented gg Vi key binding. - Bug fix in the renderer when the style changes. New features: - TokenListControl can now display the cursor somewhere. - Added SwitchableValidator class. - print_tokens function added. - get_style argument for Application added. - KeyBindingManager got an enable_all argument. Backwards incompatible changes: - history_search is now a SimpleFilter instance. 0.39: 2015-06-04 ---------------- Fixes: - Fixed layout.py example. - Fixed eventloop for Python 64bit on Windows. - Fix in history. - Fix in key bindings. 0.38: 2015-05-31 ---------------- New features: - Improved performance significantly for processing key bindings. (Pasting text will be a lot faster.) - Added 'M' Vi key binding. - Added 'z-' and 'z+' and 'z-[Enter]' Vi keybindings. - Correctly handle input and output encodings on Windows. Bug fixes: - Fix bug when completion cursor position is outside range of current text. - Don't crash Control-D is pressed while waiting for ENTER press (in run_system_command.) - On Ctrl-Z, don't suspend on Windows, where we don't have SIGTSTP. - Ignore result when open_in_editor received a nonzero return code. - Bug fix in displaying of menu meta information. Don't show 'None'. Backwards incompatible changes: - Refactoring of the I/O layer. Separation of the CommandLineInterface and Application class. - Renamed enable_system_prompt to enable_system_bindings. 0.37: 2015-05-11 ---------------- New features: - Handling of trailing input in contrib.regular_languages. Bug fixes: - Default message in shortcuts.get_input. - Windows compatibility for contrib.telnet. - OS X bugfix in contrib.telnet. 0.36: 2015-05-09 ---------------- New features: - Added get_prompt_tokens paramter to create_default_layout. - Show prompt in bold by default. Bug fixes: - Correct cache invalidation of DefaultPrompt. - Using text_type assertions in contrib.telnet. - Removed contrib.shortcuts completely. (The .pyc files still appeared incorrectly in the wheel.) 0.35: 2015-05-07 ---------------- New features: - WORD parameter for WordCompleter. - DefaultPrompt.from_message constructor. - Added reactive.py for simple integer data binding. - Implemented scroll_offset and scroll_beyond_bottom for Window. - Some performance improvements. Bug fixes: - Handling of relative path in PathCompleter. - unicode_literals for all examples. - Visibility of bottom toolbar in create_default_layout shortcut. - Correctly handle 'J' vi key binding. - Fix in indent/unindent. - Better Vi bindings in visual mode. Backwards incompatible changes: - Moved prompt_toolkit.contrib.shortcuts to prompt_toolkit.shortcuts. - Refactoring of contrib.telnet. 0.34: 2015-04-26 ---------------- Bug fixes: - Correct display of multi width characters in completion menu. Backwards incompatible changes: - Renamed Buffer.add_to_history to Buffer.append_to_history. 0.33: 2015-04-25 ---------------- Bug fixes: - Crash fixed in SystemCompleter when some directiories didn't exist. - Made text/cursor_position in Document more atomic. - Fixed Char.__ne__, improves performance. - Better performance of the filter module. - Refactoring of the filter module. - Bugfix in BufferControl, caching was not done correctly. - fixed 'zz' Vi key binding. New features: - Do tilde expansion for system commands. - Added ignore_case option for CommandLineInterface. Backwards incompatible changes: - complete_while_typing parameter has been moved from CommandLineInterface to Buffer. 0.32: 2015-04-22 ---------------- New features: - Implemented repeat arg for '{' and '}' vi key binding. - Added autocorrection example. - first experimental telnet interface added. - Added contrib.validators.SentenceValidator. - Added Layout.walk generator to traverse the layout. - Improved 'L' and 'H' Vi key bindings. - Implemented Vi 'zz' key binding. - ValidationToolbar got a show_position parameter. - When only width or hight are given for a float, the control is centered in the parent. - Added beforeKeyPress and afterKeyPress events. - Added HighlightMatchingBracketProcessor. - SearchToolbar got a vi_mode option to show '?' and '/' instead of 'I-search'. - Implemented vi '*' binding. - Added onBufferChanged event to CommandLineInterface. - Many performance improvements: some caching and not rendering after every single key stroke. - Added ConditionalProcessor. - Floating menus are now shown above the cursor, when below is not enough space, but above is enough space. - Improved vi 'G' key binding. - WindowRenderInfo got a full_height_visible, top_visible, and a few other attributes. - PathCompleter got an expanduser option to do tilde expansion. Fixed: - Always insert indentation when pressing enter. - vertical_srcoll should be an int instead of a float. - Some bug fixes in renderer.Output. - Pressing backspace in an empty search in Vi mode now goes back to navigation mode. - Bug fix in TokenListControl (Correctly calculate hight for multiline content.) - Only apply HighlightMatchingBracketProcessor when editing buffer. - Ensure that floating layouts never go out of bounds. - Home/End now go to the start and end of the line. - Fixed vi 'c' key binding. - Redraw the whole output when the style changes. - Don't trigger onTextInsert when working_index doesn't change. - Searching now wraps around the start/end of buffer/history. - Don't go to the start of the line when moving forward in history. Changes: - Don't show directory/file/link in the meta information of PathCompleter anymore. - Complete refactoring of the event loops. - Refactoring of the Renderer and CommandLineInterface class. - CommandLineInterface now accepts an optional Output argument. - CommandLineInterface now accepts a use_alternate_screen parameter. - Moved highlighting code for search/selection from BufferControl to processors. - Completers are now always run asynchronously. - Complete refactoring of the search. (Most responsibility move out of Buffer class. CommandLineInterface now got a search_state attribute.) Backwards incompatible changes: - get_input does now have a history attribute instead of history_filename. - EOFError and KeyboardInterrupt is raised for abort and exit instead of custom exceptions. - CommandLineInterface does no longer have a property 'is_reading_input'. - filters.AlwaysOn/AlwaysOff have been renamed to Always/Never. - AcceptAction has been moved from CommandLineInterface to Buffer. Now every buffer can define its own accept action. - CommandLineInterface now expects an Eventloop instance in __init__. 0.31: 2015-01-30 ---------------- Fixed: - Bug in float positioning - Show completion menu only for the default_buffer in get_input. New features: - PathCompleter got a get_paths parameter. - PathCompleter sorts alphabetically. - Added contrib.completers.SystemCompleter - Completion got a get_display_meta parameter. 0.30: 2015-01-26 ---------------- Fixed: - Backward compatibility with django_extensions. - Usage of alternate screen in the renderer. New features: - Vi '#' key binding. - contrib.shortcuts.get_input got a get_bottom_toolbar_tokens argument. - Separate key bindings for "open in editor." KeyBindingManager got a enable_open_in_editor argument. 0.28: 2015-01-25 ---------------- Fixed: - syntax error in 0.27 0.27: 2015-01-25 ---------------- Backwards-incompatible changes: - Complete refactoring of the layout system. (HSplit, VSplit, FloatContainer) as well as a list of controls (TokenListControl, BufferControl) in order to design much more complex layouts. - ptpython code has been moved to a separate repository. New features: - prompt_toolkit.contrib.shortcuts.get_input has been extended. Fixed: - Behaviour of Control+left/right/up/down. - Backspace in incremental search. - Hide completion menu correctly when the cursor position changes. 0.26: 2015-01-08 ---------------- Backwards-incompatible changes: - Refactoring of the key input system. (The registry which contains the key bindings, the focus stack, key binding manager.) Overal much better API. - Renamed `Line` to `Buffer`. New features: - Added filters as a way of disabling/enabling parts of the runtime according to certain conditions. - Global clipboard, shared between all buffers. - Added (experimental) "merge history" feature to ptpython. - Added 'C-x r k' and 'C-x r y' emacs key bindings for cut and paste. - Added g_, ge and gE vi key bindings. - Added support for handling control + arrows keys. Fixed: - Correctly handle f1-f4 in rxvt-unicode. 0.25: 2014-12-11 ---------------- Fixed: - Package did not install on Python 2.6/2.7. 0.24: 2014-12-10 ---------------- Backwards-incompatible changes: - Completer.get_completions now gets a complete_event argument. New features: - For ptpython: filename completion inside Python strings. - prompt_toolkit.contrib.regular_languages added. - prompt_toolkit.contrib.pdb added. (Experimental PDB front-end.) - Support for multiline toolbars. - asyncio support added. (Integration with asyncio event loop.) - WORD parameter added to Document.word_before_cursor. Fixed: - Small fixes in Win32 terminal output. - Bug fix in parsing of CPR response. 0.23: 2014-11-28 ---------------- New features: - contrib.completers added. Fixed: - Improved j/k key bindings in Vi mode. - Don't leak internal variables into ptipython shell. - Initialize IPython extensions. - Use IPython's prompt. - Workarounds for Jedi crashes. 0.22: 2014-11-09 ---------------- Fixed: - Fixed missing import which caused Ctrl-Z to crash. - Show error message for ptipython when IPython is not installed. 0.21: 2014-10-25 ---------------- New features: - Using entry_points in setup.py - Experimental Win32 support added. Fixed: - Behaviour of 'r' and 'R' key bindings in Vi mode. - Detect multiline correctly for ptpython when there are triple quoted strings. - Some other small improvements. 0.20: 2014-10-04 ---------------- Fixed: - Workarounds for Jedi bugs. - Better handling of window resize events. - Fixed counter in ptipython prompt. - Use IPythonInputSplitter.transform_cell for IPython syntax validation. - Only insert newlines for open brackets if the cursor is at the end of the input string. New features: - More Vi key bindings: 'B', 'W', 'E', 'aW', 'aw' and 'iW' - ControlZ now suspends the process 0.19: 2014-09-30 ---------------- Fixed: - Handle Jedi crashes. - Autocompletion in `ptipython` - Input validation in `ptipython` - Execution of system commands (in `ptpython`) in Python 3 - Add current directory to sys.path for `ptpython`. - Minimal jedi and six version in setup.py New features - Python 2.6 support - C-C> and C-C< indent and unindent emacs key bindings. - `ptpython` can now also run python scripts, so aliasing of `ptpython` as `python` will work better. 0.18: 2014-09-29 ---------------- - First official (beta) release. Jan 25, 2014 ------------ first commit prompt_toolkit-1.0.15/MANIFEST.in0000664000175000017500000000020513136130420020040 0ustar jonathanjonathan00000000000000include *rst LICENSE CHANGELOG MANIFEST.in recursive-include examples *.py recursive-include tests *.py prune examples/sample?/build prompt_toolkit-1.0.15/prompt_toolkit.egg-info/0000775000175000017500000000000013136335632023101 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit.egg-info/requires.txt0000664000175000017500000000002313136335632025474 0ustar jonathanjonathan00000000000000six>=1.9.0 wcwidth prompt_toolkit-1.0.15/prompt_toolkit.egg-info/dependency_links.txt0000664000175000017500000000000113136335632027147 0ustar jonathanjonathan00000000000000 prompt_toolkit-1.0.15/prompt_toolkit.egg-info/PKG-INFO0000664000175000017500000002204713136335632024203 0ustar jonathanjonathan00000000000000Metadata-Version: 1.1 Name: prompt-toolkit Version: 1.0.15 Summary: Library for building powerful interactive command lines in Python Home-page: https://github.com/jonathanslenders/python-prompt-toolkit Author: Jonathan Slenders Author-email: UNKNOWN License: UNKNOWN Description: Python Prompt Toolkit ===================== |Build Status| |PyPI| ``prompt_toolkit`` is a library for building powerful interactive command lines and terminal applications in Python. Read the `documentation on readthedocs `_. Ptpython ******** `ptpython `_ is an interactive Python Shell, build on top of prompt_toolkit. .. image :: https://github.com/jonathanslenders/python-prompt-toolkit/raw/master/docs/images/ptpython.png prompt_toolkit features *********************** ``prompt_toolkit`` could be a replacement for `GNU readline `_, but it can be much more than that. Some features: - Pure Python. - Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.) - Multi-line input editing. - Advanced code completion. - Both Emacs and Vi key bindings. (Similar to readline.) - Even some advanced Vi functionality, like named registers and digraphs. - Reverse and forward incremental search. - Runs on all Python versions from 2.6 up to 3.5. - Works well with Unicode double width characters. (Chinese input.) - Selecting text for copy/paste. (Both Emacs and Vi style.) - Support for `bracketed paste `_. - Mouse support for cursor positioning and scrolling. - Auto suggestions. (Like `fish shell `_.) - Multiple input buffers. - No global state. - Lightweight, the only dependencies are Pygments, six and wcwidth. - Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems. - And much more... Feel free to create tickets for bugs and feature requests, and create pull requests if you have nice patches that you would like to share with others. About Windows support ********************* ``prompt_toolkit`` is cross platform, and everything that you build on top should run fine on both Unix and Windows systems. On Windows, it uses a different event loop (``WaitForMultipleObjects`` instead of ``select``), and another input and output system. (Win32 APIs instead of pseudo-terminals and VT100.) It's worth noting that the implementation is a "best effort of what is possible". Both Unix and Windows terminals have their limitations. But in general, the Unix experience will still be a little better. For Windows, it's recommended to use either `cmder `_ or `conemu `_. Installation ************ :: pip install prompt_toolkit For Conda, do: :: conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit Getting started *************** The most simple example of the library would look like this: .. code:: python from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ') print('You said: %s' % answer) For more complex examples, have a look in the ``examples`` directory. All examples are chosen to demonstrate only one thing. Also, don't be afraid to look at the source code. The implementation of the ``prompt`` function could be a good start. Note for Python 2: all strings are expected to be unicode strings. So, either put a small ``u`` in front of every string or put ``from __future__ import unicode_literals`` at the start of the above example. Projects using prompt_toolkit ***************************** Shells: - `ptpython `_: Python REPL - `ptpdb `_: Python debugger (pdb replacement) - `pgcli `_: Postgres client. - `mycli `_: MySql client. - `wharfee `_: A Docker command line. - `xonsh `_: A Python-ish, BASHwards-compatible shell. - `saws `_: A Supercharged AWS Command Line Interface. - `cycli `_: A Command Line Interface for Cypher. - `crash `_: Crate command line client. - `vcli `_: Vertica client. - `aws-shell `_: An integrated shell for working with the AWS CLI. - `softlayer-python `_: A command-line interface to manage various SoftLayer products and services. - `ipython `_: The IPython REPL - `click-repl `_: Subcommand REPL for click apps. - `haxor-news `_: A Hacker News CLI. - `gitsome `_: A Git/Shell Autocompleter with GitHub Integration. - `http-prompt `_: An interactive command-line HTTP client. - `coconut `_: Functional programming in Python. - `Ergonomica `_: A Bash alternative written in Python. - `Kube-shell `_: Kubernetes shell: An integrated shell for working with the Kubernetes CLI Full screen applications: - `pymux `_: A terminal multiplexer (like tmux) in pure Python. - `pyvim `_: A Vim clone in pure Python. (Want your own project to be listed here? Please create a GitHub issue.) Philosophy ********** The source code of ``prompt_toolkit`` should be readable, concise and efficient. We prefer short functions focussing each on one task and for which the input and output types are clearly specified. We mostly prefer composition over inheritance, because inheritance can result in too much functionality in the same object. We prefer immutable objects where possible (objects don't change after initialisation). Reusability is important. We absolutely refrain from having a changing global state, it should be possible to have multiple independent instances of the same code in the same process. The architecture should be layered: the lower levels operate on primitive operations and data structures giving -- when correctly combined -- all the possible flexibility; while at the higher level, there should be a simpler API, ready-to-use and sufficient for most use cases. Thinking about algorithms and efficiency is important, but avoid premature optimization. Special thanks to ***************** - `Pygments `_: Syntax highlighter. - `wcwidth `_: Determine columns needed for a wide characters. .. |Build Status| image:: https://api.travis-ci.org/jonathanslenders/python-prompt-toolkit.svg?branch=master :target: https://travis-ci.org/jonathanslenders/python-prompt-toolkit# .. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg :target: https://pypi.python.org/pypi/prompt-toolkit/ :alt: Latest Version Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python Classifier: Topic :: Software Development prompt_toolkit-1.0.15/prompt_toolkit.egg-info/SOURCES.txt0000664000175000017500000001277413136335632025000 0ustar jonathanjonathan00000000000000AUTHORS.rst CHANGELOG LICENSE MANIFEST.in README.rst setup.py examples/abortaction.retry.py examples/ansi-colors.py examples/asyncio-prompt.py examples/auto-suggestion.py examples/autocompletion-like-readline.py examples/autocompletion.py examples/autocorrection.py examples/bottom-toolbar.py examples/clock-input.py examples/colored-prompt.py examples/confirmation-prompt.py examples/custom-key-binding.py examples/custom-vi-operator-and-text-object.py examples/finalterm-shell-integration.py examples/full-screen-layout.py examples/get-input-vi-mode.py examples/get-input-with-default.py examples/get-input.py examples/get-multiline-input.py examples/get-password-with-toggle-display-shortcut.py examples/get-password.py examples/gevent-get-input.py examples/html-input.py examples/input-validation.py examples/inputhook.py examples/mouse-support.py examples/multi-column-autocompletion-with-meta.py examples/multi-column-autocompletion.py examples/multiline-prompt.py examples/no-wrapping.py examples/operate-and-get-next.py examples/patch-stdout.py examples/persistent-history.py examples/print-tokens.py examples/regular-language.py examples/rprompt.py examples/switch-between-vi-emacs.py examples/system-clipboard-integration.py examples/system-prompt.py examples/telnet.py examples/terminal-title.py examples/up-arrow-partial-string-matching.py examples/tutorial/sqlite-cli.py prompt_toolkit/__init__.py prompt_toolkit/application.py prompt_toolkit/auto_suggest.py prompt_toolkit/buffer.py prompt_toolkit/buffer_mapping.py prompt_toolkit/cache.py prompt_toolkit/completion.py prompt_toolkit/document.py prompt_toolkit/enums.py prompt_toolkit/history.py prompt_toolkit/input.py prompt_toolkit/interface.py prompt_toolkit/keys.py prompt_toolkit/mouse_events.py prompt_toolkit/output.py prompt_toolkit/reactive.py prompt_toolkit/renderer.py prompt_toolkit/search_state.py prompt_toolkit/selection.py prompt_toolkit/shortcuts.py prompt_toolkit/token.py prompt_toolkit/utils.py prompt_toolkit/validation.py prompt_toolkit/win32_types.py prompt_toolkit.egg-info/PKG-INFO prompt_toolkit.egg-info/SOURCES.txt prompt_toolkit.egg-info/dependency_links.txt prompt_toolkit.egg-info/requires.txt prompt_toolkit.egg-info/top_level.txt prompt_toolkit/clipboard/__init__.py prompt_toolkit/clipboard/base.py prompt_toolkit/clipboard/in_memory.py prompt_toolkit/clipboard/pyperclip.py prompt_toolkit/contrib/__init__.py prompt_toolkit/contrib/completers/__init__.py prompt_toolkit/contrib/completers/base.py prompt_toolkit/contrib/completers/filesystem.py prompt_toolkit/contrib/completers/system.py prompt_toolkit/contrib/regular_languages/__init__.py prompt_toolkit/contrib/regular_languages/compiler.py prompt_toolkit/contrib/regular_languages/completion.py prompt_toolkit/contrib/regular_languages/lexer.py prompt_toolkit/contrib/regular_languages/regex_parser.py prompt_toolkit/contrib/regular_languages/validation.py prompt_toolkit/contrib/telnet/__init__.py prompt_toolkit/contrib/telnet/application.py prompt_toolkit/contrib/telnet/log.py prompt_toolkit/contrib/telnet/protocol.py prompt_toolkit/contrib/telnet/server.py prompt_toolkit/contrib/validators/__init__.py prompt_toolkit/contrib/validators/base.py prompt_toolkit/eventloop/__init__.py prompt_toolkit/eventloop/asyncio_base.py prompt_toolkit/eventloop/asyncio_posix.py prompt_toolkit/eventloop/asyncio_win32.py prompt_toolkit/eventloop/base.py prompt_toolkit/eventloop/callbacks.py prompt_toolkit/eventloop/inputhook.py prompt_toolkit/eventloop/posix.py prompt_toolkit/eventloop/posix_utils.py prompt_toolkit/eventloop/select.py prompt_toolkit/eventloop/utils.py prompt_toolkit/eventloop/win32.py prompt_toolkit/filters/__init__.py prompt_toolkit/filters/base.py prompt_toolkit/filters/cli.py prompt_toolkit/filters/types.py prompt_toolkit/filters/utils.py prompt_toolkit/key_binding/__init__.py prompt_toolkit/key_binding/defaults.py prompt_toolkit/key_binding/digraphs.py prompt_toolkit/key_binding/input_processor.py prompt_toolkit/key_binding/manager.py prompt_toolkit/key_binding/registry.py prompt_toolkit/key_binding/vi_state.py prompt_toolkit/key_binding/bindings/__init__.py prompt_toolkit/key_binding/bindings/basic.py prompt_toolkit/key_binding/bindings/completion.py prompt_toolkit/key_binding/bindings/emacs.py prompt_toolkit/key_binding/bindings/named_commands.py prompt_toolkit/key_binding/bindings/scroll.py prompt_toolkit/key_binding/bindings/vi.py prompt_toolkit/layout/__init__.py prompt_toolkit/layout/containers.py prompt_toolkit/layout/controls.py prompt_toolkit/layout/dimension.py prompt_toolkit/layout/lexers.py prompt_toolkit/layout/margins.py prompt_toolkit/layout/menus.py prompt_toolkit/layout/mouse_handlers.py prompt_toolkit/layout/processors.py prompt_toolkit/layout/prompt.py prompt_toolkit/layout/screen.py prompt_toolkit/layout/toolbars.py prompt_toolkit/layout/utils.py prompt_toolkit/styles/__init__.py prompt_toolkit/styles/base.py prompt_toolkit/styles/defaults.py prompt_toolkit/styles/from_dict.py prompt_toolkit/styles/from_pygments.py prompt_toolkit/styles/utils.py prompt_toolkit/terminal/__init__.py prompt_toolkit/terminal/conemu_output.py prompt_toolkit/terminal/vt100_input.py prompt_toolkit/terminal/vt100_output.py prompt_toolkit/terminal/win32_input.py prompt_toolkit/terminal/win32_output.py tests/test_buffer.py tests/test_cli.py tests/test_contrib.py tests/test_document.py tests/test_filter.py tests/test_inputstream.py tests/test_key_binding.py tests/test_layout.py tests/test_print_tokens.py tests/test_regular_languages.py tests/test_shortcuts.py tests/test_style.py tests/test_utils.py tests/test_yank_nth_arg.pyprompt_toolkit-1.0.15/prompt_toolkit.egg-info/top_level.txt0000664000175000017500000000001713136335632025631 0ustar jonathanjonathan00000000000000prompt_toolkit prompt_toolkit-1.0.15/setup.py0000775000175000017500000000307313136130420020025 0ustar jonathanjonathan00000000000000#!/usr/bin/env python import os import re from setuptools import setup, find_packages long_description = open( os.path.join( os.path.dirname(__file__), 'README.rst' ) ).read() def get_version(package): """ Return package version as listed in `__version__` in `__init__.py`. """ path = os.path.join(os.path.dirname(__file__), package, '__init__.py') with open(path, 'rb') as f: init_py = f.read().decode('utf-8') return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) setup( name='prompt_toolkit', author='Jonathan Slenders', version=get_version('prompt_toolkit'), url='https://github.com/jonathanslenders/python-prompt-toolkit', description='Library for building powerful interactive command lines in Python', long_description=long_description, packages=find_packages('.'), install_requires=[ 'six>=1.9.0', 'wcwidth', ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python', 'Topic :: Software Development', ], ) prompt_toolkit-1.0.15/prompt_toolkit/0000775000175000017500000000000013136335632021407 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/eventloop/0000775000175000017500000000000013136335632023422 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/eventloop/callbacks.py0000664000175000017500000000133713136130420025703 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'EventLoopCallbacks', ) class EventLoopCallbacks(with_metaclass(ABCMeta, object)): """ This is the glue between the :class:`~prompt_toolkit.eventloop.base.EventLoop` and :class:`~prompt_toolkit.interface.CommandLineInterface`. :meth:`~prompt_toolkit.eventloop.base.EventLoop.run` takes an :class:`.EventLoopCallbacks` instance and operates on that one, driving the interface. """ @abstractmethod def terminal_size_changed(self): pass @abstractmethod def input_timeout(self): pass @abstractmethod def feed_key(self, key): pass prompt_toolkit-1.0.15/prompt_toolkit/eventloop/inputhook.py0000664000175000017500000000675513136130420026015 0ustar jonathanjonathan00000000000000""" Similar to `PyOS_InputHook` of the Python API. Some eventloops can have an inputhook to allow easy integration with other event loops. When the eventloop of prompt-toolkit is idle, it can call such a hook. This hook can call another eventloop that runs for a short while, for instance to keep a graphical user interface responsive. It's the responsibility of this hook to exit when there is input ready. There are two ways to detect when input is ready: - Call the `input_is_ready` method periodically. Quit when this returns `True`. - Add the `fileno` as a watch to the external eventloop. Quit when file descriptor becomes readable. (But don't read from it.) Note that this is not the same as checking for `sys.stdin.fileno()`. The eventloop of prompt-toolkit allows thread-based executors, for example for asynchronous autocompletion. When the completion for instance is ready, we also want prompt-toolkit to gain control again in order to display that. An alternative to using input hooks, is to create a custom `EventLoop` class that controls everything. """ from __future__ import unicode_literals import os import threading from prompt_toolkit.utils import is_windows from .select import select_fds __all__ = ( 'InputHookContext', ) class InputHookContext(object): """ Given as a parameter to the inputhook. """ def __init__(self, inputhook): assert callable(inputhook) self.inputhook = inputhook self._input_is_ready = None self._r, self._w = os.pipe() def input_is_ready(self): """ Return True when the input is ready. """ return self._input_is_ready(wait=False) def fileno(self): """ File descriptor that will become ready when the event loop needs to go on. """ return self._r def call_inputhook(self, input_is_ready_func): """ Call the inputhook. (Called by a prompt-toolkit eventloop.) """ self._input_is_ready = input_is_ready_func # Start thread that activates this pipe when there is input to process. def thread(): input_is_ready_func(wait=True) os.write(self._w, b'x') threading.Thread(target=thread).start() # Call inputhook. self.inputhook(self) # Flush the read end of the pipe. try: # Before calling 'os.read', call select.select. This is required # when the gevent monkey patch has been applied. 'os.read' is never # monkey patched and won't be cooperative, so that would block all # other select() calls otherwise. # See: http://www.gevent.org/gevent.os.html # Note: On Windows, this is apparently not an issue. # However, if we would ever want to add a select call, it # should use `windll.kernel32.WaitForMultipleObjects`, # because `select.select` can't wait for a pipe on Windows. if not is_windows(): select_fds([self._r], timeout=None) os.read(self._r, 1024) except OSError: # This happens when the window resizes and a SIGWINCH was received. # We get 'Error: [Errno 4] Interrupted system call' # Just ignore. pass self._input_is_ready = None def close(self): """ Clean up resources. """ if self._r: os.close(self._r) os.close(self._w) self._r = self._w = None prompt_toolkit-1.0.15/prompt_toolkit/eventloop/asyncio_win32.py0000664000175000017500000000457313136130420026460 0ustar jonathanjonathan00000000000000""" Win32 asyncio event loop. Windows notes: - Somehow it doesn't seem to work with the 'ProactorEventLoop'. """ from __future__ import unicode_literals from .base import EventLoop, INPUT_TIMEOUT from ..terminal.win32_input import ConsoleInputReader from .callbacks import EventLoopCallbacks from .asyncio_base import AsyncioTimeout import asyncio __all__ = ( 'Win32AsyncioEventLoop', ) class Win32AsyncioEventLoop(EventLoop): def __init__(self, loop=None): self._console_input_reader = ConsoleInputReader() self.running = False self.closed = False self.loop = loop or asyncio.get_event_loop() @asyncio.coroutine def run_as_coroutine(self, stdin, callbacks): """ The input 'event loop'. """ # Note: We cannot use "yield from", because this package also # installs on Python 2. assert isinstance(callbacks, EventLoopCallbacks) if self.closed: raise Exception('Event loop already closed.') timeout = AsyncioTimeout(INPUT_TIMEOUT, callbacks.input_timeout, self.loop) self.running = True try: while self.running: timeout.reset() # Get keys try: g = iter(self.loop.run_in_executor(None, self._console_input_reader.read)) while True: yield next(g) except StopIteration as e: keys = e.args[0] # Feed keys to input processor. for k in keys: callbacks.feed_key(k) finally: timeout.stop() def stop(self): self.running = False def close(self): # Note: we should not close the asyncio loop itself, because that one # was not created here. self.closed = True self._console_input_reader.close() def run_in_executor(self, callback): self.loop.run_in_executor(None, callback) def call_from_executor(self, callback, _max_postpone_until=None): self.loop.call_soon_threadsafe(callback) def add_reader(self, fd, callback): " Start watching the file descriptor for read availability. " self.loop.add_reader(fd, callback) def remove_reader(self, fd): " Stop watching the file descriptor for read availability. " self.loop.remove_reader(fd) prompt_toolkit-1.0.15/prompt_toolkit/eventloop/select.py0000664000175000017500000001345213136130420025244 0ustar jonathanjonathan00000000000000""" Selectors for the Posix event loop. """ from __future__ import unicode_literals, absolute_import import sys import abc import errno import select import six __all__ = ( 'AutoSelector', 'PollSelector', 'SelectSelector', 'Selector', 'fd_to_int', ) def fd_to_int(fd): assert isinstance(fd, int) or hasattr(fd, 'fileno') if isinstance(fd, int): return fd else: return fd.fileno() class Selector(six.with_metaclass(abc.ABCMeta, object)): @abc.abstractmethod def register(self, fd): assert isinstance(fd, int) @abc.abstractmethod def unregister(self, fd): assert isinstance(fd, int) @abc.abstractmethod def select(self, timeout): pass @abc.abstractmethod def close(self): pass class AutoSelector(Selector): def __init__(self): self._fds = [] self._select_selector = SelectSelector() self._selectors = [self._select_selector] # When 'select.poll' exists, create a PollSelector. if hasattr(select, 'poll'): self._poll_selector = PollSelector() self._selectors.append(self._poll_selector) else: self._poll_selector = None # Use of the 'select' module, that was introduced in Python3.4. We don't # use it before 3.5 however, because this is the point where this module # retries interrupted system calls. if sys.version_info >= (3, 5): self._py3_selector = Python3Selector() self._selectors.append(self._py3_selector) else: self._py3_selector = None def register(self, fd): assert isinstance(fd, int) self._fds.append(fd) for sel in self._selectors: sel.register(fd) def unregister(self, fd): assert isinstance(fd, int) self._fds.remove(fd) for sel in self._selectors: sel.unregister(fd) def select(self, timeout): # Try Python 3 selector first. if self._py3_selector: try: return self._py3_selector.select(timeout) except PermissionError: # We had a situation (in pypager) where epoll raised a # PermissionError when a local file descriptor was registered, # however poll and select worked fine. So, in that case, just # try using select below. pass try: # Prefer 'select.select', if we don't have much file descriptors. # This is more universal. return self._select_selector.select(timeout) except ValueError: # When we have more than 1024 open file descriptors, we'll always # get a "ValueError: filedescriptor out of range in select()" for # 'select'. In this case, try, using 'poll' instead. if self._poll_selector is not None: return self._poll_selector.select(timeout) else: raise def close(self): for sel in self._selectors: sel.close() class Python3Selector(Selector): """ Use of the Python3 'selectors' module. NOTE: Only use on Python 3.5 or newer! """ def __init__(self): assert sys.version_info >= (3, 5) import selectors # Inline import: Python3 only! self._sel = selectors.DefaultSelector() def register(self, fd): assert isinstance(fd, int) import selectors # Inline import: Python3 only! self._sel.register(fd, selectors.EVENT_READ, None) def unregister(self, fd): assert isinstance(fd, int) self._sel.unregister(fd) def select(self, timeout): events = self._sel.select(timeout=timeout) return [key.fileobj for key, mask in events] def close(self): self._sel.close() class PollSelector(Selector): def __init__(self): self._poll = select.poll() def register(self, fd): assert isinstance(fd, int) self._poll.register(fd, select.POLLIN) def unregister(self, fd): assert isinstance(fd, int) def select(self, timeout): tuples = self._poll.poll(timeout) # Returns (fd, event) tuples. return [t[0] for t in tuples] def close(self): pass # XXX class SelectSelector(Selector): """ Wrapper around select.select. When the SIGWINCH signal is handled, other system calls, like select are aborted in Python. This wrapper will retry the system call. """ def __init__(self): self._fds = [] def register(self, fd): self._fds.append(fd) def unregister(self, fd): self._fds.remove(fd) def select(self, timeout): while True: try: return select.select(self._fds, [], [], timeout)[0] except select.error as e: # Retry select call when EINTR if e.args and e.args[0] == errno.EINTR: continue else: raise def close(self): pass def select_fds(read_fds, timeout, selector=AutoSelector): """ Wait for a list of file descriptors (`read_fds`) to become ready for reading. This chooses the most appropriate select-tool for use in prompt-toolkit. """ # Map to ensure that we return the objects that were passed in originally. # Whether they are a fd integer or an object that has a fileno(). # (The 'poll' implementation for instance, returns always integers.) fd_map = dict((fd_to_int(fd), fd) for fd in read_fds) # Wait, using selector. sel = selector() try: for fd in read_fds: sel.register(fd) result = sel.select(timeout) if result is not None: return [fd_map[fd_to_int(fd)] for fd in result] finally: sel.close() prompt_toolkit-1.0.15/prompt_toolkit/eventloop/asyncio_posix.py0000664000175000017500000000644313136130420026656 0ustar jonathanjonathan00000000000000""" Posix asyncio event loop. """ from __future__ import unicode_literals from ..terminal.vt100_input import InputStream from .asyncio_base import AsyncioTimeout from .base import EventLoop, INPUT_TIMEOUT from .callbacks import EventLoopCallbacks from .posix_utils import PosixStdinReader import asyncio import signal __all__ = ( 'PosixAsyncioEventLoop', ) class PosixAsyncioEventLoop(EventLoop): def __init__(self, loop=None): self.loop = loop or asyncio.get_event_loop() self.closed = False self._stopped_f = asyncio.Future(loop=self.loop) @asyncio.coroutine def run_as_coroutine(self, stdin, callbacks): """ The input 'event loop'. """ assert isinstance(callbacks, EventLoopCallbacks) # Create reader class. stdin_reader = PosixStdinReader(stdin.fileno()) if self.closed: raise Exception('Event loop already closed.') inputstream = InputStream(callbacks.feed_key) try: # Create a new Future every time. self._stopped_f = asyncio.Future(loop=self.loop) # Handle input timouts def timeout_handler(): """ When no input has been received for INPUT_TIMEOUT seconds, flush the input stream and fire the timeout event. """ inputstream.flush() callbacks.input_timeout() timeout = AsyncioTimeout(INPUT_TIMEOUT, timeout_handler, self.loop) # Catch sigwinch def received_winch(): self.call_from_executor(callbacks.terminal_size_changed) self.loop.add_signal_handler(signal.SIGWINCH, received_winch) # Read input data. def stdin_ready(): data = stdin_reader.read() inputstream.feed(data) timeout.reset() # Quit when the input stream was closed. if stdin_reader.closed: self.stop() self.loop.add_reader(stdin.fileno(), stdin_ready) # Block this coroutine until stop() has been called. for f in self._stopped_f: yield f finally: # Clean up. self.loop.remove_reader(stdin.fileno()) self.loop.remove_signal_handler(signal.SIGWINCH) # Don't trigger any timeout events anymore. timeout.stop() def stop(self): # Trigger the 'Stop' future. self._stopped_f.set_result(True) def close(self): # Note: we should not close the asyncio loop itself, because that one # was not created here. self.closed = True def run_in_executor(self, callback): self.loop.run_in_executor(None, callback) def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. """ self.loop.call_soon_threadsafe(callback) def add_reader(self, fd, callback): " Start watching the file descriptor for read availability. " self.loop.add_reader(fd, callback) def remove_reader(self, fd): " Stop watching the file descriptor for read availability. " self.loop.remove_reader(fd) prompt_toolkit-1.0.15/prompt_toolkit/eventloop/asyncio_base.py0000664000175000017500000000220013136130420026411 0ustar jonathanjonathan00000000000000""" Eventloop for integration with Python3 asyncio. Note that we can't use "yield from", because the package should be installable under Python 2.6 as well, and it should contain syntactically valid Python 2.6 code. """ from __future__ import unicode_literals __all__ = ( 'AsyncioTimeout', ) class AsyncioTimeout(object): """ Call the `timeout` function when the timeout expires. Every call of the `reset` method, resets the timeout and starts a new timer. """ def __init__(self, timeout, callback, loop): self.timeout = timeout self.callback = callback self.loop = loop self.counter = 0 self.running = True def reset(self): """ Reset the timeout. Starts a new timer. """ self.counter += 1 local_counter = self.counter def timer_timeout(): if self.counter == local_counter and self.running: self.callback() self.loop.call_later(self.timeout, timer_timeout) def stop(self): """ Ignore timeout. Don't call the callback anymore. """ self.running = False prompt_toolkit-1.0.15/prompt_toolkit/eventloop/__init__.py0000664000175000017500000000000013136130420025505 0ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/eventloop/posix.py0000664000175000017500000002724513136130420025134 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import fcntl import os import signal import threading import time from prompt_toolkit.terminal.vt100_input import InputStream from prompt_toolkit.utils import DummyContext, in_main_thread from prompt_toolkit.input import Input from .base import EventLoop, INPUT_TIMEOUT from .callbacks import EventLoopCallbacks from .inputhook import InputHookContext from .posix_utils import PosixStdinReader from .utils import TimeIt from .select import AutoSelector, Selector, fd_to_int __all__ = ( 'PosixEventLoop', ) _now = time.time class PosixEventLoop(EventLoop): """ Event loop for posix systems (Linux, Mac os X). """ def __init__(self, inputhook=None, selector=AutoSelector): assert inputhook is None or callable(inputhook) assert issubclass(selector, Selector) self.running = False self.closed = False self._running = False self._callbacks = None self._calls_from_executor = [] self._read_fds = {} # Maps fd to handler. self.selector = selector() # Create a pipe for inter thread communication. self._schedule_pipe = os.pipe() fcntl.fcntl(self._schedule_pipe[0], fcntl.F_SETFL, os.O_NONBLOCK) # Create inputhook context. self._inputhook_context = InputHookContext(inputhook) if inputhook else None def run(self, stdin, callbacks): """ The input 'event loop'. """ assert isinstance(stdin, Input) assert isinstance(callbacks, EventLoopCallbacks) assert not self._running if self.closed: raise Exception('Event loop already closed.') self._running = True self._callbacks = callbacks inputstream = InputStream(callbacks.feed_key) current_timeout = [INPUT_TIMEOUT] # Nonlocal # Create reader class. stdin_reader = PosixStdinReader(stdin.fileno()) # Only attach SIGWINCH signal handler in main thread. # (It's not possible to attach signal handlers in other threads. In # that case we should rely on a the main thread to call this manually # instead.) if in_main_thread(): ctx = call_on_sigwinch(self.received_winch) else: ctx = DummyContext() def read_from_stdin(): " Read user input. " # Feed input text. data = stdin_reader.read() inputstream.feed(data) # Set timeout again. current_timeout[0] = INPUT_TIMEOUT # Quit when the input stream was closed. if stdin_reader.closed: self.stop() self.add_reader(stdin, read_from_stdin) self.add_reader(self._schedule_pipe[0], None) with ctx: while self._running: # Call inputhook. if self._inputhook_context: with TimeIt() as inputhook_timer: def ready(wait): " True when there is input ready. The inputhook should return control. " return self._ready_for_reading(current_timeout[0] if wait else 0) != [] self._inputhook_context.call_inputhook(ready) inputhook_duration = inputhook_timer.duration else: inputhook_duration = 0 # Calculate remaining timeout. (The inputhook consumed some of the time.) if current_timeout[0] is None: remaining_timeout = None else: remaining_timeout = max(0, current_timeout[0] - inputhook_duration) # Wait until input is ready. fds = self._ready_for_reading(remaining_timeout) # When any of the FDs are ready. Call the appropriate callback. if fds: # Create lists of high/low priority tasks. The main reason # for this is to allow painting the UI to happen as soon as # possible, but when there are many events happening, we # don't want to call the UI renderer 1000x per second. If # the eventloop is completely saturated with many CPU # intensive tasks (like processing input/output), we say # that drawing the UI can be postponed a little, to make # CPU available. This will be a low priority task in that # case. tasks = [] low_priority_tasks = [] now = None # Lazy load time. (Fewer system calls.) for fd in fds: # For the 'call_from_executor' fd, put each pending # item on either the high or low priority queue. if fd == self._schedule_pipe[0]: for c, max_postpone_until in self._calls_from_executor: if max_postpone_until is None: # Execute now. tasks.append(c) else: # Execute soon, if `max_postpone_until` is in the future. now = now or _now() if max_postpone_until < now: tasks.append(c) else: low_priority_tasks.append((c, max_postpone_until)) self._calls_from_executor = [] # Flush all the pipe content. os.read(self._schedule_pipe[0], 1024) else: handler = self._read_fds.get(fd) if handler: tasks.append(handler) # When there are high priority tasks, run all these. # Schedule low priority tasks for the next iteration. if tasks: for t in tasks: t() # Postpone low priority tasks. for t, max_postpone_until in low_priority_tasks: self.call_from_executor(t, _max_postpone_until=max_postpone_until) else: # Currently there are only low priority tasks -> run them right now. for t, _ in low_priority_tasks: t() else: # Flush all pending keys on a timeout. (This is most # important to flush the vt100 'Escape' key early when # nothing else follows.) inputstream.flush() # Fire input timeout event. callbacks.input_timeout() current_timeout[0] = None self.remove_reader(stdin) self.remove_reader(self._schedule_pipe[0]) self._callbacks = None def _ready_for_reading(self, timeout=None): """ Return the file descriptors that are ready for reading. """ fds = self.selector.select(timeout) return fds def received_winch(self): """ Notify the event loop that SIGWINCH has been received """ # Process signal asynchronously, because this handler can write to the # output, and doing this inside the signal handler causes easily # reentrant calls, giving runtime errors.. # Furthur, this has to be thread safe. When the CommandLineInterface # runs not in the main thread, this function still has to be called # from the main thread. (The only place where we can install signal # handlers.) def process_winch(): if self._callbacks: self._callbacks.terminal_size_changed() self.call_from_executor(process_winch) def run_in_executor(self, callback): """ Run a long running function in a background thread. (This is recommended for code that could block the event loop.) Similar to Twisted's ``deferToThread``. """ # Wait until the main thread is idle. # We start the thread by using `call_from_executor`. The event loop # favours processing input over `calls_from_executor`, so the thread # will not start until there is no more input to process and the main # thread becomes idle for an instant. This is good, because Python # threading favours CPU over I/O -- an autocompletion thread in the # background would cause a significantly slow down of the main thread. # It is mostly noticable when pasting large portions of text while # having real time autocompletion while typing on. def start_executor(): threading.Thread(target=callback).start() self.call_from_executor(start_executor) def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. :param _max_postpone_until: `None` or `time.time` value. For interal use. If the eventloop is saturated, consider this task to be low priority and postpone maximum until this timestamp. (For instance, repaint is done using low priority.) """ assert _max_postpone_until is None or isinstance(_max_postpone_until, float) self._calls_from_executor.append((callback, _max_postpone_until)) if self._schedule_pipe: try: os.write(self._schedule_pipe[1], b'x') except (AttributeError, IndexError, OSError): # Handle race condition. We're in a different thread. # - `_schedule_pipe` could have become None in the meantime. # - We catch `OSError` (actually BrokenPipeError), because the # main thread could have closed the pipe already. pass def stop(self): """ Stop the event loop. """ self._running = False def close(self): self.closed = True # Close pipes. schedule_pipe = self._schedule_pipe self._schedule_pipe = None if schedule_pipe: os.close(schedule_pipe[0]) os.close(schedule_pipe[1]) if self._inputhook_context: self._inputhook_context.close() def add_reader(self, fd, callback): " Add read file descriptor to the event loop. " fd = fd_to_int(fd) self._read_fds[fd] = callback self.selector.register(fd) def remove_reader(self, fd): " Remove read file descriptor from the event loop. " fd = fd_to_int(fd) if fd in self._read_fds: del self._read_fds[fd] self.selector.unregister(fd) class call_on_sigwinch(object): """ Context manager which Installs a SIGWINCH callback. (This signal occurs when the terminal size changes.) """ def __init__(self, callback): self.callback = callback self.previous_callback = None def __enter__(self): self.previous_callback = signal.signal(signal.SIGWINCH, lambda *a: self.callback()) def __exit__(self, *a, **kw): if self.previous_callback is None: # Normally, `signal.signal` should never return `None`. # For some reason it happens here: # https://github.com/jonathanslenders/python-prompt-toolkit/pull/174 signal.signal(signal.SIGWINCH, 0) else: signal.signal(signal.SIGWINCH, self.previous_callback) prompt_toolkit-1.0.15/prompt_toolkit/eventloop/posix_utils.py0000664000175000017500000000636013136130420026347 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from codecs import getincrementaldecoder import os import six __all__ = ( 'PosixStdinReader', ) class PosixStdinReader(object): """ Wrapper around stdin which reads (nonblocking) the next available 1024 bytes and decodes it. Note that you can't be sure that the input file is closed if the ``read`` function returns an empty string. When ``errors=ignore`` is passed, ``read`` can return an empty string if all malformed input was replaced by an empty string. (We can't block here and wait for more input.) So, because of that, check the ``closed`` attribute, to be sure that the file has been closed. :param stdin_fd: File descriptor from which we read. :param errors: Can be 'ignore', 'strict' or 'replace'. On Python3, this can be 'surrogateescape', which is the default. 'surrogateescape' is preferred, because this allows us to transfer unrecognised bytes to the key bindings. Some terminals, like lxterminal and Guake, use the 'Mxx' notation to send mouse events, where each 'x' can be any possible byte. """ # By default, we want to 'ignore' errors here. The input stream can be full # of junk. One occurrence of this that I had was when using iTerm2 on OS X, # with "Option as Meta" checked (You should choose "Option as +Esc".) def __init__(self, stdin_fd, errors=('ignore' if six.PY2 else 'surrogateescape')): assert isinstance(stdin_fd, int) self.stdin_fd = stdin_fd self.errors = errors # Create incremental decoder for decoding stdin. # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, because # it could be that we are in the middle of a utf-8 byte sequence. self._stdin_decoder_cls = getincrementaldecoder('utf-8') self._stdin_decoder = self._stdin_decoder_cls(errors=errors) #: True when there is nothing anymore to read. self.closed = False def read(self, count=1024): # By default we choose a rather small chunk size, because reading # big amounts of input at once, causes the event loop to process # all these key bindings also at once without going back to the # loop. This will make the application feel unresponsive. """ Read the input and return it as a string. Return the text. Note that this can return an empty string, even when the input stream was not yet closed. This means that something went wrong during the decoding. """ if self.closed: return b'' # Note: the following works better than wrapping `self.stdin` like # `codecs.getreader('utf-8')(stdin)` and doing `read(1)`. # Somehow that causes some latency when the escape # character is pressed. (Especially on combination with the `select`.) try: data = os.read(self.stdin_fd, count) # Nothing more to read, stream is closed. if data == b'': self.closed = True return '' except OSError: # In case of SIGWINCH data = b'' return self._stdin_decoder.decode(data) prompt_toolkit-1.0.15/prompt_toolkit/eventloop/win32.py0000664000175000017500000001406413136130420024727 0ustar jonathanjonathan00000000000000""" Win32 event loop. Windows notes: - Somehow it doesn't seem to work with the 'ProactorEventLoop'. """ from __future__ import unicode_literals from ..terminal.win32_input import ConsoleInputReader from ..win32_types import SECURITY_ATTRIBUTES from .base import EventLoop, INPUT_TIMEOUT from .inputhook import InputHookContext from .utils import TimeIt from ctypes import windll, pointer from ctypes.wintypes import DWORD, BOOL, HANDLE import msvcrt import threading __all__ = ( 'Win32EventLoop', ) WAIT_TIMEOUT = 0x00000102 INPUT_TIMEOUT_MS = int(1000 * INPUT_TIMEOUT) class Win32EventLoop(EventLoop): """ Event loop for Windows systems. :param recognize_paste: When True, try to discover paste actions and turn the event into a BracketedPaste. """ def __init__(self, inputhook=None, recognize_paste=True): assert inputhook is None or callable(inputhook) self._event = _create_event() self._console_input_reader = ConsoleInputReader(recognize_paste=recognize_paste) self._calls_from_executor = [] self.closed = False self._running = False # Additional readers. self._read_fds = {} # Maps fd to handler. # Create inputhook context. self._inputhook_context = InputHookContext(inputhook) if inputhook else None def run(self, stdin, callbacks): if self.closed: raise Exception('Event loop already closed.') current_timeout = INPUT_TIMEOUT_MS self._running = True while self._running: # Call inputhook. with TimeIt() as inputhook_timer: if self._inputhook_context: def ready(wait): " True when there is input ready. The inputhook should return control. " return bool(self._ready_for_reading(current_timeout if wait else 0)) self._inputhook_context.call_inputhook(ready) # Calculate remaining timeout. (The inputhook consumed some of the time.) if current_timeout == -1: remaining_timeout = -1 else: remaining_timeout = max(0, current_timeout - int(1000 * inputhook_timer.duration)) # Wait for the next event. handle = self._ready_for_reading(remaining_timeout) if handle == self._console_input_reader.handle: # When stdin is ready, read input and reset timeout timer. keys = self._console_input_reader.read() for k in keys: callbacks.feed_key(k) current_timeout = INPUT_TIMEOUT_MS elif handle == self._event: # When the Windows Event has been trigger, process the messages in the queue. windll.kernel32.ResetEvent(self._event) self._process_queued_calls_from_executor() elif handle in self._read_fds: callback = self._read_fds[handle] callback() else: # Fire input timeout event. callbacks.input_timeout() current_timeout = -1 def _ready_for_reading(self, timeout=None): """ Return the handle that is ready for reading or `None` on timeout. """ handles = [self._event, self._console_input_reader.handle] handles.extend(self._read_fds.keys()) return _wait_for_handles(handles, timeout) def stop(self): self._running = False def close(self): self.closed = True # Clean up Event object. windll.kernel32.CloseHandle(self._event) if self._inputhook_context: self._inputhook_context.close() self._console_input_reader.close() def run_in_executor(self, callback): """ Run a long running function in a background thread. (This is recommended for code that could block the event loop.) Similar to Twisted's ``deferToThread``. """ # Wait until the main thread is idle for an instant before starting the # executor. (Like in eventloop/posix.py, we start the executor using # `call_from_executor`.) def start_executor(): threading.Thread(target=callback).start() self.call_from_executor(start_executor) def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. """ # Append to list of pending callbacks. self._calls_from_executor.append(callback) # Set Windows event. windll.kernel32.SetEvent(self._event) def _process_queued_calls_from_executor(self): # Process calls from executor. calls_from_executor, self._calls_from_executor = self._calls_from_executor, [] for c in calls_from_executor: c() def add_reader(self, fd, callback): " Start watching the file descriptor for read availability. " h = msvcrt.get_osfhandle(fd) self._read_fds[h] = callback def remove_reader(self, fd): " Stop watching the file descriptor for read availability. " h = msvcrt.get_osfhandle(fd) if h in self._read_fds: del self._read_fds[h] def _wait_for_handles(handles, timeout=-1): """ Waits for multiple handles. (Similar to 'select') Returns the handle which is ready. Returns `None` on timeout. http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx """ arrtype = HANDLE * len(handles) handle_array = arrtype(*handles) ret = windll.kernel32.WaitForMultipleObjects( len(handle_array), handle_array, BOOL(False), DWORD(timeout)) if ret == WAIT_TIMEOUT: return None else: h = handle_array[ret] return h def _create_event(): """ Creates a Win32 unnamed Event . http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx """ return windll.kernel32.CreateEventA(pointer(SECURITY_ATTRIBUTES()), BOOL(True), BOOL(False), None) prompt_toolkit-1.0.15/prompt_toolkit/eventloop/base.py0000664000175000017500000000530013136130420024670 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'EventLoop', 'INPUT_TIMEOUT', ) #: When to trigger the `onInputTimeout` event. INPUT_TIMEOUT = .5 class EventLoop(with_metaclass(ABCMeta, object)): """ Eventloop interface. """ def run(self, stdin, callbacks): """ Run the eventloop until stop() is called. Report all input/timeout/terminal-resize events to the callbacks. :param stdin: :class:`~prompt_toolkit.input.Input` instance. :param callbacks: :class:`~prompt_toolkit.eventloop.callbacks.EventLoopCallbacks` instance. """ raise NotImplementedError("This eventloop doesn't implement synchronous 'run()'.") def run_as_coroutine(self, stdin, callbacks): """ Similar to `run`, but this is a coroutine. (For asyncio integration.) """ raise NotImplementedError("This eventloop doesn't implement 'run_as_coroutine()'.") @abstractmethod def stop(self): """ Stop the `run` call. (Normally called by :class:`~prompt_toolkit.interface.CommandLineInterface`, when a result is available, or Abort/Quit has been called.) """ @abstractmethod def close(self): """ Clean up of resources. Eventloop cannot be reused a second time after this call. """ @abstractmethod def add_reader(self, fd, callback): """ Start watching the file descriptor for read availability and then call the callback. """ @abstractmethod def remove_reader(self, fd): """ Stop watching the file descriptor for read availability. """ @abstractmethod def run_in_executor(self, callback): """ Run a long running function in a background thread. (This is recommended for code that could block the event loop.) Similar to Twisted's ``deferToThread``. """ @abstractmethod def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. :param _max_postpone_until: `None` or `time.time` value. For interal use. If the eventloop is saturated, consider this task to be low priority and postpone maximum until this timestamp. (For instance, repaint is done using low priority.) Note: In the past, this used to be a datetime.datetime instance, but apparently, executing `time.time` is more efficient: it does fewer system calls. (It doesn't read /etc/localtime.) """ prompt_toolkit-1.0.15/prompt_toolkit/eventloop/utils.py0000664000175000017500000000076113136130420025124 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import time __all__ = ( 'TimeIt', ) class TimeIt(object): """ Context manager that times the duration of the code body. The `duration` attribute will contain the execution time in seconds. """ def __init__(self): self.duration = None def __enter__(self): self.start = time.time() return self def __exit__(self, *args): self.end = time.time() self.duration = self.end - self.start prompt_toolkit-1.0.15/prompt_toolkit/buffer.py0000664000175000017500000014410513136130420023223 0ustar jonathanjonathan00000000000000""" Data structures for the Buffer. It holds the text, cursor position, history, etc... """ from __future__ import unicode_literals from .auto_suggest import AutoSuggest from .clipboard import ClipboardData from .completion import Completer, Completion, CompleteEvent from .document import Document from .enums import IncrementalSearchDirection from .filters import to_simple_filter from .history import History, InMemoryHistory from .search_state import SearchState from .selection import SelectionType, SelectionState, PasteMode from .utils import Event from .cache import FastDictCache from .validation import ValidationError from six.moves import range import os import re import shlex import six import subprocess import tempfile __all__ = ( 'EditReadOnlyBuffer', 'AcceptAction', 'Buffer', 'indent', 'unindent', 'reshape_text', ) class EditReadOnlyBuffer(Exception): " Attempt editing of read-only :class:`.Buffer`. " class AcceptAction(object): """ What to do when the input is accepted by the user. (When Enter was pressed in the command line.) :param handler: (optional) A callable which takes a :class:`~prompt_toolkit.interface.CommandLineInterface` and :class:`~prompt_toolkit.document.Document`. It is called when the user accepts input. """ def __init__(self, handler=None): assert handler is None or callable(handler) self.handler = handler @classmethod def run_in_terminal(cls, handler, render_cli_done=False): """ Create an :class:`.AcceptAction` that runs the given handler in the terminal. :param render_cli_done: When True, render the interface in the 'Done' state first, then execute the function. If False, erase the interface instead. """ def _handler(cli, buffer): cli.run_in_terminal(lambda: handler(cli, buffer), render_cli_done=render_cli_done) return AcceptAction(handler=_handler) @property def is_returnable(self): """ True when there is something handling accept. """ return bool(self.handler) def validate_and_handle(self, cli, buffer): """ Validate buffer and handle the accept action. """ if buffer.validate(): if self.handler: self.handler(cli, buffer) buffer.append_to_history() def _return_document_handler(cli, buffer): # Set return value. cli.set_return_value(buffer.document) # Make sure that if we run this UI again, that we reset this buffer, next # time. def reset_this_buffer(): buffer.reset() cli.pre_run_callables.append(reset_this_buffer) AcceptAction.RETURN_DOCUMENT = AcceptAction(_return_document_handler) AcceptAction.IGNORE = AcceptAction(handler=None) class ValidationState(object): " The validation state of a buffer. This is set after the validation. " VALID = 'VALID' INVALID = 'INVALID' UNKNOWN = 'UNKNOWN' class CompletionState(object): """ Immutable class that contains a completion state. """ def __init__(self, original_document, current_completions=None, complete_index=None): #: Document as it was when the completion started. self.original_document = original_document #: List of all the current Completion instances which are possible at #: this point. self.current_completions = current_completions or [] #: Position in the `current_completions` array. #: This can be `None` to indicate "no completion", the original text. self.complete_index = complete_index # Position in the `_completions` array. def __repr__(self): return '%s(%r, <%r> completions, index=%r)' % ( self.__class__.__name__, self.original_document, len(self.current_completions), self.complete_index) def go_to_index(self, index): """ Create a new :class:`.CompletionState` object with the new index. """ return CompletionState(self.original_document, self.current_completions, complete_index=index) def new_text_and_position(self): """ Return (new_text, new_cursor_position) for this completion. """ if self.complete_index is None: return self.original_document.text, self.original_document.cursor_position else: original_text_before_cursor = self.original_document.text_before_cursor original_text_after_cursor = self.original_document.text_after_cursor c = self.current_completions[self.complete_index] if c.start_position == 0: before = original_text_before_cursor else: before = original_text_before_cursor[:c.start_position] new_text = before + c.text + original_text_after_cursor new_cursor_position = len(before) + len(c.text) return new_text, new_cursor_position @property def current_completion(self): """ Return the current completion, or return `None` when no completion is selected. """ if self.complete_index is not None: return self.current_completions[self.complete_index] _QUOTED_WORDS_RE = re.compile(r"""(\s+|".*?"|'.*?')""") class YankNthArgState(object): """ For yank-last-arg/yank-nth-arg: Keep track of where we are in the history. """ def __init__(self, history_position=0, n=-1, previous_inserted_word=''): self.history_position = history_position self.previous_inserted_word = previous_inserted_word self.n = n def __repr__(self): return '%s(history_position=%r, n=%r, previous_inserted_word=%r)' % ( self.__class__.__name__, self.history_position, self.n, self.previous_inserted_word) 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 also implements the history, undo stack and the completion state. :param completer: :class:`~prompt_toolkit.completion.Completer` instance. :param history: :class:`~prompt_toolkit.history.History` instance. :param tempfile_suffix: Suffix to be appended to the tempfile for the 'open in editor' function. Events: :param on_text_changed: When the buffer text changes. (Callable on None.) :param on_text_insert: When new text is inserted. (Callable on None.) :param on_cursor_position_changed: When the cursor moves. (Callable on None.) Filters: :param is_multiline: :class:`~prompt_toolkit.filters.SimpleFilter` to indicate whether we should consider this buffer a multiline input. If so, key bindings can decide to insert newlines when pressing [Enter]. (Instead of accepting the input.) :param complete_while_typing: :class:`~prompt_toolkit.filters.SimpleFilter` instance. Decide whether or not to do asynchronous autocompleting while typing. :param enable_history_search: :class:`~prompt_toolkit.filters.SimpleFilter` to indicate when up-arrow partial string matching is enabled. It is adviced to not enable this at the same time as `complete_while_typing`, because when there is an autocompletion found, the up arrows usually browse through the completions, rather than through the history. :param read_only: :class:`~prompt_toolkit.filters.SimpleFilter`. When True, changes will not be allowed. """ def __init__(self, completer=None, auto_suggest=None, history=None, validator=None, tempfile_suffix='', is_multiline=False, complete_while_typing=False, enable_history_search=False, initial_document=None, accept_action=AcceptAction.IGNORE, read_only=False, on_text_changed=None, on_text_insert=None, on_cursor_position_changed=None): # Accept both filters and booleans as input. enable_history_search = to_simple_filter(enable_history_search) is_multiline = to_simple_filter(is_multiline) complete_while_typing = to_simple_filter(complete_while_typing) read_only = to_simple_filter(read_only) # Validate input. assert completer is None or isinstance(completer, Completer) assert auto_suggest is None or isinstance(auto_suggest, AutoSuggest) assert history is None or isinstance(history, History) assert on_text_changed is None or callable(on_text_changed) assert on_text_insert is None or callable(on_text_insert) assert on_cursor_position_changed is None or callable(on_cursor_position_changed) self.completer = completer self.auto_suggest = auto_suggest self.validator = validator self.tempfile_suffix = tempfile_suffix self.accept_action = accept_action # Filters. (Usually, used by the key bindings to drive the buffer.) self.is_multiline = is_multiline self.complete_while_typing = complete_while_typing self.enable_history_search = enable_history_search self.read_only = read_only # Text width. (For wrapping, used by the Vi 'gq' operator.) self.text_width = 0 #: The command buffer history. # Note that we shouldn't use a lazy 'or' here. bool(history) could be # False when empty. self.history = InMemoryHistory() if history is None else history self.__cursor_position = 0 # Events self.on_text_changed = Event(self, on_text_changed) self.on_text_insert = Event(self, on_text_insert) self.on_cursor_position_changed = Event(self, on_cursor_position_changed) # Document cache. (Avoid creating new Document instances.) self._document_cache = FastDictCache(Document, size=10) self.reset(initial_document=initial_document) def reset(self, initial_document=None, append_to_history=False): """ :param append_to_history: Append current input to history first. """ assert initial_document is None or isinstance(initial_document, Document) if append_to_history: self.append_to_history() initial_document = initial_document or Document() self.__cursor_position = initial_document.cursor_position # `ValidationError` instance. (Will be set when the input is wrong.) self.validation_error = None self.validation_state = ValidationState.UNKNOWN # State of the selection. self.selection_state = None # Multiple cursor mode. (When we press 'I' or 'A' in visual-block mode, # we can insert text on multiple lines at once. This is implemented by # using multiple cursors.) self.multiple_cursor_positions = [] # When doing consecutive up/down movements, prefer to stay at this column. self.preferred_column = None # State of complete browser self.complete_state = None # For interactive completion through Ctrl-N/Ctrl-P. # State of Emacs yank-nth-arg completion. self.yank_nth_arg_state = None # for yank-nth-arg. # Remember the document that we had *right before* the last paste # operation. This is used for rotating through the kill ring. self.document_before_paste = None # Current suggestion. self.suggestion = None # The history search text. (Used for filtering the history when we # browse through it.) self.history_search_text = None # Undo/redo stacks self._undo_stack = [] # Stack of (text, cursor_position) self._redo_stack = [] #: The working lines. Similar to history, except that this can be #: modified. The user can press arrow_up and edit previous entries. #: Ctrl-C should reset this, and copy the whole history back in here. #: Enter should process the current command and append to the real #: history. self._working_lines = self.history.strings[:] self._working_lines.append(initial_document.text) self.__working_index = len(self._working_lines) - 1 # def _set_text(self, value): """ set text at current working_index. Return whether it changed. """ working_index = self.working_index working_lines = self._working_lines original_value = working_lines[working_index] working_lines[working_index] = value # Return True when this text has been changed. if len(value) != len(original_value): # For Python 2, it seems that when two strings have a different # length and one is a prefix of the other, Python still scans # character by character to see whether the strings are different. # (Some benchmarking showed significant differences for big # documents. >100,000 of lines.) return True elif value != original_value: return True return False def _set_cursor_position(self, value): """ Set cursor position. Return whether it changed. """ original_position = self.__cursor_position self.__cursor_position = max(0, value) return value != original_position @property def text(self): return self._working_lines[self.working_index] @text.setter def text(self, value): """ Setting text. (When doing this, make sure that the cursor_position is valid for this text. text/cursor_position should be consistent at any time, otherwise set a Document instead.) """ assert isinstance(value, six.text_type), 'Got %r' % value assert self.cursor_position <= len(value) # Don't allow editing of read-only buffers. if self.read_only(): raise EditReadOnlyBuffer() changed = self._set_text(value) if changed: self._text_changed() # Reset history search text. self.history_search_text = None @property def cursor_position(self): return self.__cursor_position @cursor_position.setter def cursor_position(self, value): """ Setting cursor position. """ assert isinstance(value, int) assert value <= len(self.text) changed = self._set_cursor_position(value) if changed: self._cursor_position_changed() @property def working_index(self): return self.__working_index @working_index.setter def working_index(self, value): if self.__working_index != value: self.__working_index = value self._text_changed() def _text_changed(self): # Remove any validation errors and complete state. self.validation_error = None self.validation_state = ValidationState.UNKNOWN self.complete_state = None self.yank_nth_arg_state = None self.document_before_paste = None self.selection_state = None self.suggestion = None self.preferred_column = None # fire 'on_text_changed' event. self.on_text_changed.fire() def _cursor_position_changed(self): # Remove any validation errors and complete state. self.validation_error = None self.validation_state = ValidationState.UNKNOWN self.complete_state = None self.yank_nth_arg_state = None self.document_before_paste = None # Unset preferred_column. (Will be set after the cursor movement, if # required.) self.preferred_column = 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. # fire 'on_cursor_position_changed' event. self.on_cursor_position_changed.fire() @property def document(self): """ Return :class:`~prompt_toolkit.document.Document` instance from the current text, cursor position and selection state. """ return self._document_cache[ self.text, self.cursor_position, self.selection_state] @document.setter def document(self, value): """ Set :class:`~prompt_toolkit.document.Document` instance. This will set both the text and cursor position at the same time, but atomically. (Change events will be triggered only after both have been set.) """ self.set_document(value) def set_document(self, value, bypass_readonly=False): """ Set :class:`~prompt_toolkit.document.Document` instance. Like the ``document`` property, but accept an ``bypass_readonly`` argument. :param bypass_readonly: When True, don't raise an :class:`.EditReadOnlyBuffer` exception, even when the buffer is read-only. """ assert isinstance(value, Document) # Don't allow editing of read-only buffers. if not bypass_readonly and self.read_only(): raise EditReadOnlyBuffer() # Set text and cursor position first. text_changed = self._set_text(value.text) cursor_position_changed = self._set_cursor_position(value.cursor_position) # Now handle change events. (We do this when text/cursor position is # both set and consistent.) if text_changed: self._text_changed() if cursor_position_changed: self._cursor_position_changed() # End of def save_to_undo_stack(self, clear_redo_stack=True): """ Safe current state (input text and cursor position), so that we can restore it by calling undo. """ # Safe if the text is different from the text at the top of the stack # is different. If the text is the same, just update the cursor position. if self._undo_stack and self._undo_stack[-1][0] == self.text: self._undo_stack[-1] = (self._undo_stack[-1][0], self.cursor_position) else: self._undo_stack.append((self.text, self.cursor_position)) # Saving anything to the undo stack, clears the redo stack. if clear_redo_stack: self._redo_stack = [] def transform_lines(self, line_index_iterator, transform_callback): """ Transforms the text on a range of lines. When the iterator yield an index not in the range of lines that the document contains, it skips them silently. To uppercase some lines:: new_text = transform_lines(range(5,10), lambda text: text.upper()) :param line_index_iterator: Iterator of line numbers (int) :param transform_callback: callable that takes the original text of a line, and return the new text for this line. :returns: The new text. """ # Split lines lines = self.text.split('\n') # Apply transformation for index in line_index_iterator: try: lines[index] = transform_callback(lines[index]) except IndexError: pass return '\n'.join(lines) def transform_current_line(self, transform_callback): """ Apply the given transformation function to the current line. :param transform_callback: callable that takes a string and return a new string. """ document = self.document a = document.cursor_position + document.get_start_of_line_position() b = document.cursor_position + document.get_end_of_line_position() self.text = ( document.text[:a] + transform_callback(document.text[a:b]) + document.text[b:]) def transform_region(self, from_, to, transform_callback): """ Transform a part of the input string. :param from_: (int) start position. :param to: (int) end position. :param transform_callback: Callable which accepts a string and returns the transformed string. """ assert from_ < to self.text = ''.join([ self.text[:from_] + transform_callback(self.text[from_:to]) + self.text[to:] ]) def cursor_left(self, count=1): self.cursor_position += self.document.get_cursor_left_position(count=count) def cursor_right(self, count=1): self.cursor_position += self.document.get_cursor_right_position(count=count) def cursor_up(self, count=1): """ (for multiline edit). Move cursor to the previous line. """ original_column = self.preferred_column or self.document.cursor_position_col self.cursor_position += self.document.get_cursor_up_position( count=count, preferred_column=original_column) # Remember the original column for the next up/down movement. self.preferred_column = original_column def cursor_down(self, count=1): """ (for multiline edit). Move cursor to the next line. """ original_column = self.preferred_column or self.document.cursor_position_col self.cursor_position += self.document.get_cursor_down_position( count=count, preferred_column=original_column) # Remember the original column for the next up/down movement. self.preferred_column = original_column def auto_up(self, count=1, go_to_start_of_line_if_history_changes=False): """ If we're not on the first line (of a multiline input) go a line up, otherwise go back in history. (If nothing is selected.) """ if self.complete_state: self.complete_previous(count=count) elif self.document.cursor_position_row > 0: self.cursor_up(count=count) elif not self.selection_state: self.history_backward(count=count) # Go to the start of the line? if go_to_start_of_line_if_history_changes: self.cursor_position += self.document.get_start_of_line_position() def auto_down(self, count=1, go_to_start_of_line_if_history_changes=False): """ If we're not on the last line (of a multiline input) go a line down, otherwise go forward in history. (If nothing is selected.) """ if self.complete_state: self.complete_next(count=count) elif self.document.cursor_position_row < self.document.line_count - 1: self.cursor_down(count=count) elif not self.selection_state: self.history_forward(count=count) # Go to the start of the line? if go_to_start_of_line_if_history_changes: self.cursor_position += self.document.get_start_of_line_position() def delete_before_cursor(self, count=1): """ Delete specified number of characters before cursor and return the deleted text. """ assert count >= 0 deleted = '' if self.cursor_position > 0: deleted = self.text[self.cursor_position - count:self.cursor_position] new_text = self.text[:self.cursor_position - count] + self.text[self.cursor_position:] new_cursor_position = self.cursor_position - len(deleted) # Set new Document atomically. self.document = Document(new_text, new_cursor_position) return deleted def delete(self, count=1): """ Delete specified number of characters and Return the deleted text. """ if self.cursor_position < len(self.text): deleted = self.document.text_after_cursor[:count] self.text = self.text[:self.cursor_position] + \ self.text[self.cursor_position + len(deleted):] return deleted else: return '' def join_next_line(self, separator=' '): """ Join the next line to the current one by deleting the line ending after the current line. """ if not self.document.on_last_line: self.cursor_position += self.document.get_end_of_line_position() self.delete() # Remove spaces. self.text = (self.document.text_before_cursor + separator + self.document.text_after_cursor.lstrip(' ')) def join_selected_lines(self, separator=' '): """ Join the selected lines. """ assert self.selection_state # Get lines. from_, to = sorted([self.cursor_position, self.selection_state.original_cursor_position]) before = self.text[:from_] lines = self.text[from_:to].splitlines() after = self.text[to:] # Replace leading spaces with just one space. lines = [l.lstrip(' ') + separator for l in lines] # Set new document. self.document = Document(text=before + ''.join(lines) + after, cursor_position=len(before + ''.join(lines[:-1])) - 1) def swap_characters_before_cursor(self): """ Swap the last two characters before the cursor. """ pos = self.cursor_position if pos >= 2: a = self.text[pos - 2] b = self.text[pos - 1] self.text = self.text[:pos-2] + b + a + self.text[pos:] def go_to_history(self, index): """ Go to this item in the history. """ if index < len(self._working_lines): self.working_index = index self.cursor_position = len(self.text) def complete_next(self, count=1, disable_wrap_around=False): """ Browse to the next completions. (Does nothing if there are no completion.) """ if self.complete_state: completions_count = len(self.complete_state.current_completions) if self.complete_state.complete_index is None: index = 0 elif self.complete_state.complete_index == completions_count - 1: index = None if disable_wrap_around: return else: index = min(completions_count-1, self.complete_state.complete_index + count) self.go_to_completion(index) def complete_previous(self, count=1, disable_wrap_around=False): """ Browse to the previous completions. (Does nothing if there are no completion.) """ if self.complete_state: if self.complete_state.complete_index == 0: index = None if disable_wrap_around: return elif self.complete_state.complete_index is None: index = len(self.complete_state.current_completions) - 1 else: index = max(0, self.complete_state.complete_index - count) self.go_to_completion(index) def cancel_completion(self): """ Cancel completion, go back to the original text. """ if self.complete_state: self.go_to_completion(None) self.complete_state = None def set_completions(self, completions, go_to_first=True, go_to_last=False): """ Start completions. (Generate list of completions and initialize.) """ assert not (go_to_first and go_to_last) # Generate list of all completions. if completions is None: if self.completer: completions = list(self.completer.get_completions( self.document, CompleteEvent(completion_requested=True) )) else: completions = [] # Set `complete_state`. if completions: self.complete_state = CompletionState( original_document=self.document, current_completions=completions) if go_to_first: self.go_to_completion(0) elif go_to_last: self.go_to_completion(len(completions) - 1) else: self.go_to_completion(None) else: self.complete_state = None def start_history_lines_completion(self): """ Start a completion based on all the other lines in the document and the history. """ found_completions = set() completions = [] # For every line of the whole history, find matches with the current line. current_line = self.document.current_line_before_cursor.lstrip() for i, string in enumerate(self._working_lines): for j, l in enumerate(string.split('\n')): l = l.strip() if l and l.startswith(current_line): # When a new line has been found. if l not in found_completions: found_completions.add(l) # Create completion. if i == self.working_index: display_meta = "Current, line %s" % (j+1) else: display_meta = "History %s, line %s" % (i+1, j+1) completions.append(Completion( l, start_position=-len(current_line), display_meta=display_meta)) self.set_completions(completions=completions[::-1]) def go_to_completion(self, index): """ Select a completion from the list of current completions. """ assert index is None or isinstance(index, int) assert self.complete_state # Set new completion state = self.complete_state.go_to_index(index) # Set text/cursor position new_text, new_cursor_position = state.new_text_and_position() self.document = Document(new_text, new_cursor_position) # (changing text/cursor position will unset complete_state.) self.complete_state = state def apply_completion(self, completion): """ Insert a given completion. """ assert isinstance(completion, Completion) # If there was already a completion active, cancel that one. if self.complete_state: self.go_to_completion(None) self.complete_state = None # Insert text from the given completion. self.delete_before_cursor(-completion.start_position) self.insert_text(completion.text) def _set_history_search(self): """ Set `history_search_text`. """ if self.enable_history_search(): if self.history_search_text is None: self.history_search_text = self.document.text_before_cursor else: self.history_search_text = None def _history_matches(self, i): """ True when the current entry matches the history search. (when we don't have history search, it's also True.) """ return (self.history_search_text is None or self._working_lines[i].startswith(self.history_search_text)) def history_forward(self, count=1): """ Move forwards through the history. :param count: Amount of items to move forward. """ self._set_history_search() # Go forward in history. found_something = False for i in range(self.working_index + 1, len(self._working_lines)): if self._history_matches(i): self.working_index = i count -= 1 found_something = True if count == 0: break # If we found an entry, move cursor to the end of the first line. if found_something: self.cursor_position = 0 self.cursor_position += self.document.get_end_of_line_position() def history_backward(self, count=1): """ Move backwards through history. """ self._set_history_search() # Go back in history. found_something = False for i in range(self.working_index - 1, -1, -1): if self._history_matches(i): self.working_index = i count -= 1 found_something = True if count == 0: break # If we move to another entry, move cursor to the end of the line. if found_something: self.cursor_position = len(self.text) def yank_nth_arg(self, n=None, _yank_last_arg=False): """ Pick nth word from previous history entry (depending on current `yank_nth_arg_state`) and insert it at current position. Rotate through history if called repeatedly. If no `n` has been given, take the first argument. (The second word.) :param n: (None or int), The index of the word from the previous line to take. """ assert n is None or isinstance(n, int) if not len(self.history): return # Make sure we have a `YankNthArgState`. if self.yank_nth_arg_state is None: state = YankNthArgState(n=-1 if _yank_last_arg else 1) else: state = self.yank_nth_arg_state if n is not None: state.n = n # Get new history position. new_pos = state.history_position - 1 if -new_pos > len(self.history): new_pos = -1 # Take argument from line. line = self.history[new_pos] words = [w.strip() for w in _QUOTED_WORDS_RE.split(line)] words = [w for w in words if w] try: word = words[state.n] except IndexError: word = '' # Insert new argument. if state.previous_inserted_word: self.delete_before_cursor(len(state.previous_inserted_word)) self.insert_text(word) # Save state again for next completion. (Note that the 'insert' # operation from above clears `self.yank_nth_arg_state`.) state.previous_inserted_word = word state.history_position = new_pos self.yank_nth_arg_state = state def yank_last_arg(self, n=None): """ Like `yank_nth_arg`, but if no argument has been given, yank the last word by default. """ self.yank_nth_arg(n=n, _yank_last_arg=True) def start_selection(self, selection_type=SelectionType.CHARACTERS): """ Take the current cursor position as the start of this selection. """ self.selection_state = SelectionState(self.cursor_position, selection_type) def copy_selection(self, _cut=False): """ Copy selected text and return :class:`.ClipboardData` instance. """ new_document, clipboard_data = self.document.cut_selection() if _cut: self.document = new_document self.selection_state = None return clipboard_data def cut_selection(self): """ Delete selected text and return :class:`.ClipboardData` instance. """ return self.copy_selection(_cut=True) def paste_clipboard_data(self, data, paste_mode=PasteMode.EMACS, count=1): """ Insert the data from the clipboard. """ assert isinstance(data, ClipboardData) assert paste_mode in (PasteMode.VI_BEFORE, PasteMode.VI_AFTER, PasteMode.EMACS) original_document = self.document self.document = self.document.paste_clipboard_data(data, paste_mode=paste_mode, count=count) # Remember original document. This assignment should come at the end, # because assigning to 'document' will erase it. self.document_before_paste = original_document def newline(self, copy_margin=True): """ Insert a line ending at the current position. """ if copy_margin: self.insert_text('\n' + self.document.leading_whitespace_in_current_line) else: self.insert_text('\n') def insert_line_above(self, copy_margin=True): """ Insert a new line above the current one. """ if copy_margin: insert = self.document.leading_whitespace_in_current_line + '\n' else: insert = '\n' self.cursor_position += self.document.get_start_of_line_position() self.insert_text(insert) self.cursor_position -= 1 def insert_line_below(self, copy_margin=True): """ Insert a new line below the current one. """ if copy_margin: insert = '\n' + self.document.leading_whitespace_in_current_line else: insert = '\n' self.cursor_position += self.document.get_end_of_line_position() self.insert_text(insert) def insert_text(self, data, overwrite=False, move_cursor=True, fire_event=True): """ Insert characters at cursor position. :param fire_event: Fire `on_text_insert` event. This is mainly used to trigger autocompletion while typing. """ # Original text & cursor position. otext = self.text ocpos = self.cursor_position # In insert/text mode. if overwrite: # Don't overwrite the newline itself. Just before the line ending, # it should act like insert mode. overwritten_text = otext[ocpos:ocpos + len(data)] if '\n' in overwritten_text: overwritten_text = overwritten_text[:overwritten_text.find('\n')] self.text = otext[:ocpos] + data + otext[ocpos + len(overwritten_text):] else: self.text = otext[:ocpos] + data + otext[ocpos:] if move_cursor: self.cursor_position += len(data) # Fire 'on_text_insert' event. if fire_event: self.on_text_insert.fire() def undo(self): # Pop from the undo-stack until we find a text that if different from # the current text. (The current logic of `save_to_undo_stack` will # cause that the top of the undo stack is usually the same as the # current text, so in that case we have to pop twice.) while self._undo_stack: text, pos = self._undo_stack.pop() if text != self.text: # Push current text to redo stack. self._redo_stack.append((self.text, self.cursor_position)) # Set new text/cursor_position. self.document = Document(text, cursor_position=pos) break def redo(self): if self._redo_stack: # Copy current state on undo stack. self.save_to_undo_stack(clear_redo_stack=False) # Pop state from redo stack. text, pos = self._redo_stack.pop() self.document = Document(text, cursor_position=pos) def validate(self): """ Returns `True` if valid. """ # Don't call the validator again, if it was already called for the # current input. if self.validation_state != ValidationState.UNKNOWN: return self.validation_state == ValidationState.VALID # Validate first. If not valid, set validation exception. if self.validator: try: self.validator.validate(self.document) except ValidationError as e: # Set cursor position (don't allow invalid values.) cursor_position = e.cursor_position self.cursor_position = min(max(0, cursor_position), len(self.text)) self.validation_state = ValidationState.INVALID self.validation_error = e return False self.validation_state = ValidationState.VALID self.validation_error = None return True def append_to_history(self): """ Append the current input to the history. (Only if valid input.) """ # Validate first. If not valid, set validation exception. if not self.validate(): return # Save at the tail of the history. (But don't if the last entry the # history is already the same.) if self.text and (not len(self.history) or self.history[-1] != self.text): self.history.append(self.text) def _search(self, search_state, include_current_position=False, count=1): """ Execute search. Return (working_index, cursor_position) tuple when this search is applied. Returns `None` when this text cannot be found. """ assert isinstance(search_state, SearchState) assert isinstance(count, int) and count > 0 text = search_state.text direction = search_state.direction ignore_case = search_state.ignore_case() def search_once(working_index, document): """ Do search one time. Return (working_index, document) or `None` """ if direction == IncrementalSearchDirection.FORWARD: # Try find at the current input. new_index = document.find( text, include_current_position=include_current_position, ignore_case=ignore_case) if new_index is not None: return (working_index, Document(document.text, document.cursor_position + new_index)) else: # No match, go forward in the history. (Include len+1 to wrap around.) # (Here we should always include all cursor positions, because # it's a different line.) for i in range(working_index + 1, len(self._working_lines) + 1): i %= len(self._working_lines) document = Document(self._working_lines[i], 0) new_index = document.find(text, include_current_position=True, ignore_case=ignore_case) if new_index is not None: return (i, Document(document.text, new_index)) else: # Try find at the current input. new_index = document.find_backwards( text, ignore_case=ignore_case) if new_index is not None: return (working_index, Document(document.text, document.cursor_position + new_index)) else: # No match, go back in the history. (Include -1 to wrap around.) for i in range(working_index - 1, -2, -1): i %= len(self._working_lines) document = Document(self._working_lines[i], len(self._working_lines[i])) new_index = document.find_backwards( text, ignore_case=ignore_case) if new_index is not None: return (i, Document(document.text, len(document.text) + new_index)) # Do 'count' search iterations. working_index = self.working_index document = self.document for _ in range(count): result = search_once(working_index, document) if result is None: return # Nothing found. else: working_index, document = result return (working_index, document.cursor_position) def document_for_search(self, search_state): """ Return a :class:`~prompt_toolkit.document.Document` instance that has the text/cursor position for this search, if we would apply it. This will be used in the :class:`~prompt_toolkit.layout.controls.BufferControl` to display feedback while searching. """ search_result = self._search(search_state, include_current_position=True) if search_result is None: return self.document else: working_index, cursor_position = search_result # Keep selection, when `working_index` was not changed. if working_index == self.working_index: selection = self.selection_state else: selection = None return Document(self._working_lines[working_index], cursor_position, selection=selection) def get_search_position(self, search_state, include_current_position=True, count=1): """ Get the cursor position for this search. (This operation won't change the `working_index`. It's won't go through the history. Vi text objects can't span multiple items.) """ search_result = self._search( search_state, include_current_position=include_current_position, count=count) if search_result is None: return self.cursor_position else: working_index, cursor_position = search_result return cursor_position def apply_search(self, search_state, include_current_position=True, count=1): """ Apply search. If something is found, set `working_index` and `cursor_position`. """ search_result = self._search( search_state, include_current_position=include_current_position, count=count) if search_result is not None: working_index, cursor_position = search_result self.working_index = working_index self.cursor_position = cursor_position def exit_selection(self): self.selection_state = None def open_in_editor(self, cli): """ Open code in editor. :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface` instance. """ if self.read_only(): raise EditReadOnlyBuffer() # Write to temporary file descriptor, filename = tempfile.mkstemp(self.tempfile_suffix) os.write(descriptor, self.text.encode('utf-8')) os.close(descriptor) # Open in editor # (We need to use `cli.run_in_terminal`, because not all editors go to # the alternate screen buffer, and some could influence the cursor # position.) succes = cli.run_in_terminal(lambda: self._open_file_in_editor(filename)) # Read content again. if succes: with open(filename, 'rb') as f: text = f.read().decode('utf-8') # Drop trailing newline. (Editors are supposed to add it at the # end, but we don't need it.) if text.endswith('\n'): text = text[:-1] self.document = Document( text=text, cursor_position=len(text)) # Clean up temp file. os.remove(filename) def _open_file_in_editor(self, filename): """ Call editor executable. Return True when we received a zero return code. """ # If the 'VISUAL' or 'EDITOR' environment variable has been set, use that. # Otherwise, fall back to the first available editor that we can find. visual = os.environ.get('VISUAL') editor = os.environ.get('EDITOR') editors = [ visual, editor, # Order of preference. '/usr/bin/editor', '/usr/bin/nano', '/usr/bin/pico', '/usr/bin/vi', '/usr/bin/emacs', ] for e in editors: if e: try: # Use 'shlex.split()', because $VISUAL can contain spaces # and quotes. returncode = subprocess.call(shlex.split(e) + [filename]) return returncode == 0 except OSError: # Executable does not exist, try the next one. pass return False def indent(buffer, from_row, to_row, count=1): """ Indent text of a :class:`.Buffer` object. """ current_row = buffer.document.cursor_position_row line_range = range(from_row, to_row) # Apply transformation. new_text = buffer.transform_lines(line_range, lambda l: ' ' * count + l) buffer.document = Document( new_text, Document(new_text).translate_row_col_to_index(current_row, 0)) # Go to the start of the line. buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) def unindent(buffer, from_row, to_row, count=1): """ Unindent text of a :class:`.Buffer` object. """ current_row = buffer.document.cursor_position_row line_range = range(from_row, to_row) def transform(text): remove = ' ' * count if text.startswith(remove): return text[len(remove):] else: return text.lstrip() # Apply transformation. new_text = buffer.transform_lines(line_range, transform) buffer.document = Document( new_text, Document(new_text).translate_row_col_to_index(current_row, 0)) # Go to the start of the line. buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) def reshape_text(buffer, from_row, to_row): """ Reformat text, taking the width into account. `to_row` is included. (Vi 'gq' operator.) """ lines = buffer.text.splitlines(True) lines_before = lines[:from_row] lines_after = lines[to_row + 1:] lines_to_reformat = lines[from_row:to_row + 1] if lines_to_reformat: # Take indentation from the first line. length = re.search(r'^\s*', lines_to_reformat[0]).end() indent = lines_to_reformat[0][:length].replace('\n', '') # Now, take all the 'words' from the lines to be reshaped. words = ''.join(lines_to_reformat).split() # And reshape. width = (buffer.text_width or 80) - len(indent) reshaped_text = [indent] current_width = 0 for w in words: if current_width: if len(w) + current_width + 1 > width: reshaped_text.append('\n') reshaped_text.append(indent) current_width = 0 else: reshaped_text.append(' ') current_width += 1 reshaped_text.append(w) current_width += len(w) if reshaped_text[-1] != '\n': reshaped_text.append('\n') # Apply result. buffer.document = Document( text=''.join(lines_before + reshaped_text + lines_after), cursor_position=len(''.join(lines_before + reshaped_text))) prompt_toolkit-1.0.15/prompt_toolkit/output.py0000664000175000017500000001164613136130420023315 0ustar jonathanjonathan00000000000000""" Interface for an output. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from prompt_toolkit.layout.screen import Size __all__ = ( 'Output', ) class Output(with_metaclass(ABCMeta, object)): """ Base class defining the output interface for a :class:`~prompt_toolkit.renderer.Renderer`. Actual implementations are :class:`~prompt_toolkit.terminal.vt100_output.Vt100_Output` and :class:`~prompt_toolkit.terminal.win32_output.Win32Output`. """ @abstractmethod def fileno(self): " Return the file descriptor to which we can write for the output. " @abstractmethod def encoding(self): """ Return the encoding for this output, e.g. 'utf-8'. (This is used mainly to know which characters are supported by the output the data, so that the UI can provide alternatives, when required.) """ @abstractmethod def write(self, data): " Write text (Terminal escape sequences will be removed/escaped.) " @abstractmethod def write_raw(self, data): " Write text. " @abstractmethod def set_title(self, title): " Set terminal title. " @abstractmethod def clear_title(self): " Clear title again. (or restore previous title.) " @abstractmethod def flush(self): " Write to output stream and flush. " @abstractmethod def erase_screen(self): """ Erases the screen with the background colour and moves the cursor to home. """ @abstractmethod def enter_alternate_screen(self): " Go to the alternate screen buffer. (For full screen applications). " @abstractmethod def quit_alternate_screen(self): " Leave the alternate screen buffer. " @abstractmethod def enable_mouse_support(self): " Enable mouse. " @abstractmethod def disable_mouse_support(self): " Disable mouse. " @abstractmethod def erase_end_of_line(self): """ Erases from the current cursor position to the end of the current line. """ @abstractmethod def erase_down(self): """ Erases the screen from the current line down to the bottom of the screen. """ @abstractmethod def reset_attributes(self): " Reset color and styling attributes. " @abstractmethod def set_attributes(self, attrs): " Set new color and styling attributes. " @abstractmethod def disable_autowrap(self): " Disable auto line wrapping. " @abstractmethod def enable_autowrap(self): " Enable auto line wrapping. " @abstractmethod def cursor_goto(self, row=0, column=0): " Move cursor position. " @abstractmethod def cursor_up(self, amount): " Move cursor `amount` place up. " @abstractmethod def cursor_down(self, amount): " Move cursor `amount` place down. " @abstractmethod def cursor_forward(self, amount): " Move cursor `amount` place forward. " @abstractmethod def cursor_backward(self, amount): " Move cursor `amount` place backward. " @abstractmethod def hide_cursor(self): " Hide cursor. " @abstractmethod def show_cursor(self): " Show cursor. " def ask_for_cpr(self): """ Asks for a cursor position report (CPR). (VT100 only.) """ def bell(self): " Sound bell. " def enable_bracketed_paste(self): " For vt100 only. " def disable_bracketed_paste(self): " For vt100 only. " class DummyOutput(Output): """ For testing. An output class that doesn't render anything. """ def fileno(self): " There is no sensible default for fileno(). " raise NotImplementedError def encoding(self): return 'utf-8' def write(self, data): pass def write_raw(self, data): pass def set_title(self, title): pass def clear_title(self): pass def flush(self): pass def erase_screen(self): pass def enter_alternate_screen(self): pass def quit_alternate_screen(self): pass def enable_mouse_support(self): pass def disable_mouse_support(self): pass def erase_end_of_line(self): pass def erase_down(self): pass def reset_attributes(self): pass def set_attributes(self, attrs): pass def disable_autowrap(self): pass def enable_autowrap(self): pass def cursor_goto(self, row=0, column=0): pass def cursor_up(self, amount): pass def cursor_down(self, amount): pass def cursor_forward(self, amount): pass def cursor_backward(self, amount): pass def hide_cursor(self): pass def show_cursor(self): pass def ask_for_cpr(self): pass def bell(self): pass def enable_bracketed_paste(self): pass def disable_bracketed_paste(self): pass def get_size(self): return Size(rows=40, columns=80) prompt_toolkit-1.0.15/prompt_toolkit/key_binding/0000775000175000017500000000000013136335632023671 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/key_binding/defaults.py0000664000175000017500000001146613136130420026046 0ustar jonathanjonathan00000000000000""" Default key bindings.:: registry = load_key_bindings() app = Application(key_bindings_registry=registry) """ from __future__ import unicode_literals from prompt_toolkit.key_binding.registry import ConditionalRegistry, MergedRegistry from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings, load_abort_and_exit_bindings, load_basic_system_bindings, load_auto_suggestion_bindings, load_mouse_bindings from prompt_toolkit.key_binding.bindings.emacs import load_emacs_bindings, load_emacs_system_bindings, load_emacs_search_bindings, load_emacs_open_in_editor_bindings, load_extra_emacs_page_navigation_bindings from prompt_toolkit.key_binding.bindings.vi import load_vi_bindings, load_vi_system_bindings, load_vi_search_bindings, load_vi_open_in_editor_bindings, load_extra_vi_page_navigation_bindings from prompt_toolkit.filters import to_cli_filter __all__ = ( 'load_key_bindings', 'load_key_bindings_for_prompt', ) def load_key_bindings( get_search_state=None, enable_abort_and_exit_bindings=False, enable_system_bindings=False, enable_search=False, enable_open_in_editor=False, enable_extra_page_navigation=False, enable_auto_suggest_bindings=False): """ Create a Registry object that contains the default key bindings. :param enable_abort_and_exit_bindings: Filter to enable Ctrl-C and Ctrl-D. :param enable_system_bindings: Filter to enable the system bindings (meta-! prompt and Control-Z suspension.) :param enable_search: Filter to enable the search bindings. :param enable_open_in_editor: Filter to enable open-in-editor. :param enable_open_in_editor: Filter to enable open-in-editor. :param enable_extra_page_navigation: Filter for enabling extra page navigation. (Bindings for up/down scrolling through long pages, like in Emacs or Vi.) :param enable_auto_suggest_bindings: Filter to enable fish-style suggestions. """ assert get_search_state is None or callable(get_search_state) # Accept both Filters and booleans as input. enable_abort_and_exit_bindings = to_cli_filter(enable_abort_and_exit_bindings) enable_system_bindings = to_cli_filter(enable_system_bindings) enable_search = to_cli_filter(enable_search) enable_open_in_editor = to_cli_filter(enable_open_in_editor) enable_extra_page_navigation = to_cli_filter(enable_extra_page_navigation) enable_auto_suggest_bindings = to_cli_filter(enable_auto_suggest_bindings) registry = MergedRegistry([ # Load basic bindings. load_basic_bindings(), load_mouse_bindings(), ConditionalRegistry(load_abort_and_exit_bindings(), enable_abort_and_exit_bindings), ConditionalRegistry(load_basic_system_bindings(), enable_system_bindings), # Load emacs bindings. load_emacs_bindings(), ConditionalRegistry(load_emacs_open_in_editor_bindings(), enable_open_in_editor), ConditionalRegistry(load_emacs_search_bindings(get_search_state=get_search_state), enable_search), ConditionalRegistry(load_emacs_system_bindings(), enable_system_bindings), ConditionalRegistry(load_extra_emacs_page_navigation_bindings(), enable_extra_page_navigation), # Load Vi bindings. load_vi_bindings(get_search_state=get_search_state), ConditionalRegistry(load_vi_open_in_editor_bindings(), enable_open_in_editor), ConditionalRegistry(load_vi_search_bindings(get_search_state=get_search_state), enable_search), ConditionalRegistry(load_vi_system_bindings(), enable_system_bindings), ConditionalRegistry(load_extra_vi_page_navigation_bindings(), enable_extra_page_navigation), # Suggestion bindings. # (This has to come at the end, because the Vi bindings also have an # implementation for the "right arrow", but we really want the # suggestion binding when a suggestion is available.) ConditionalRegistry(load_auto_suggestion_bindings(), enable_auto_suggest_bindings), ]) return registry def load_key_bindings_for_prompt(**kw): """ Create a ``Registry`` object with the defaults key bindings for an input prompt. This activates the key bindings for abort/exit (Ctrl-C/Ctrl-D), incremental search and auto suggestions. (Not for full screen applications.) """ kw.setdefault('enable_abort_and_exit_bindings', True) kw.setdefault('enable_search', True) kw.setdefault('enable_auto_suggest_bindings', True) return load_key_bindings(**kw) prompt_toolkit-1.0.15/prompt_toolkit/key_binding/digraphs.py0000664000175000017500000010011213136130420026023 0ustar jonathanjonathan00000000000000# encoding: utf-8 from __future__ import unicode_literals """ Vi Digraphs. This is a list of special characters that can be inserted in Vi insert mode by pressing Control-K followed by to normal characters. Taken from Neovim and translated to Python: https://raw.githubusercontent.com/neovim/neovim/master/src/nvim/digraph.c """ __all__ = ('DIGRAPHS', ) # digraphs for Unicode from RFC1345 # (also work for ISO-8859-1 aka latin1) DIGRAPHS = { ('N', 'U'): 0x00, ('S', 'H'): 0x01, ('S', 'X'): 0x02, ('E', 'X'): 0x03, ('E', 'T'): 0x04, ('E', 'Q'): 0x05, ('A', 'K'): 0x06, ('B', 'L'): 0x07, ('B', 'S'): 0x08, ('H', 'T'): 0x09, ('L', 'F'): 0x0a, ('V', 'T'): 0x0b, ('F', 'F'): 0x0c, ('C', 'R'): 0x0d, ('S', 'O'): 0x0e, ('S', 'I'): 0x0f, ('D', 'L'): 0x10, ('D', '1'): 0x11, ('D', '2'): 0x12, ('D', '3'): 0x13, ('D', '4'): 0x14, ('N', 'K'): 0x15, ('S', 'Y'): 0x16, ('E', 'B'): 0x17, ('C', 'N'): 0x18, ('E', 'M'): 0x19, ('S', 'B'): 0x1a, ('E', 'C'): 0x1b, ('F', 'S'): 0x1c, ('G', 'S'): 0x1d, ('R', 'S'): 0x1e, ('U', 'S'): 0x1f, ('S', 'P'): 0x20, ('N', 'b'): 0x23, ('D', 'O'): 0x24, ('A', 't'): 0x40, ('<', '('): 0x5b, ('/', '/'): 0x5c, (')', '>'): 0x5d, ('\'', '>'): 0x5e, ('\'', '!'): 0x60, ('(', '!'): 0x7b, ('!', '!'): 0x7c, ('!', ')'): 0x7d, ('\'', '?'): 0x7e, ('D', 'T'): 0x7f, ('P', 'A'): 0x80, ('H', 'O'): 0x81, ('B', 'H'): 0x82, ('N', 'H'): 0x83, ('I', 'N'): 0x84, ('N', 'L'): 0x85, ('S', 'A'): 0x86, ('E', 'S'): 0x87, ('H', 'S'): 0x88, ('H', 'J'): 0x89, ('V', 'S'): 0x8a, ('P', 'D'): 0x8b, ('P', 'U'): 0x8c, ('R', 'I'): 0x8d, ('S', '2'): 0x8e, ('S', '3'): 0x8f, ('D', 'C'): 0x90, ('P', '1'): 0x91, ('P', '2'): 0x92, ('T', 'S'): 0x93, ('C', 'C'): 0x94, ('M', 'W'): 0x95, ('S', 'G'): 0x96, ('E', 'G'): 0x97, ('S', 'S'): 0x98, ('G', 'C'): 0x99, ('S', 'C'): 0x9a, ('C', 'I'): 0x9b, ('S', 'T'): 0x9c, ('O', 'C'): 0x9d, ('P', 'M'): 0x9e, ('A', 'C'): 0x9f, ('N', 'S'): 0xa0, ('!', 'I'): 0xa1, ('C', 't'): 0xa2, ('P', 'd'): 0xa3, ('C', 'u'): 0xa4, ('Y', 'e'): 0xa5, ('B', 'B'): 0xa6, ('S', 'E'): 0xa7, ('\'', ':'): 0xa8, ('C', 'o'): 0xa9, ('-', 'a'): 0xaa, ('<', '<'): 0xab, ('N', 'O'): 0xac, ('-', '-'): 0xad, ('R', 'g'): 0xae, ('\'', 'm'): 0xaf, ('D', 'G'): 0xb0, ('+', '-'): 0xb1, ('2', 'S'): 0xb2, ('3', 'S'): 0xb3, ('\'', '\''): 0xb4, ('M', 'y'): 0xb5, ('P', 'I'): 0xb6, ('.', 'M'): 0xb7, ('\'', ','): 0xb8, ('1', 'S'): 0xb9, ('-', 'o'): 0xba, ('>', '>'): 0xbb, ('1', '4'): 0xbc, ('1', '2'): 0xbd, ('3', '4'): 0xbe, ('?', 'I'): 0xbf, ('A', '!'): 0xc0, ('A', '\''): 0xc1, ('A', '>'): 0xc2, ('A', '?'): 0xc3, ('A', ':'): 0xc4, ('A', 'A'): 0xc5, ('A', 'E'): 0xc6, ('C', ','): 0xc7, ('E', '!'): 0xc8, ('E', '\''): 0xc9, ('E', '>'): 0xca, ('E', ':'): 0xcb, ('I', '!'): 0xcc, ('I', '\''): 0xcd, ('I', '>'): 0xce, ('I', ':'): 0xcf, ('D', '-'): 0xd0, ('N', '?'): 0xd1, ('O', '!'): 0xd2, ('O', '\''): 0xd3, ('O', '>'): 0xd4, ('O', '?'): 0xd5, ('O', ':'): 0xd6, ('*', 'X'): 0xd7, ('O', '/'): 0xd8, ('U', '!'): 0xd9, ('U', '\''): 0xda, ('U', '>'): 0xdb, ('U', ':'): 0xdc, ('Y', '\''): 0xdd, ('T', 'H'): 0xde, ('s', 's'): 0xdf, ('a', '!'): 0xe0, ('a', '\''): 0xe1, ('a', '>'): 0xe2, ('a', '?'): 0xe3, ('a', ':'): 0xe4, ('a', 'a'): 0xe5, ('a', 'e'): 0xe6, ('c', ','): 0xe7, ('e', '!'): 0xe8, ('e', '\''): 0xe9, ('e', '>'): 0xea, ('e', ':'): 0xeb, ('i', '!'): 0xec, ('i', '\''): 0xed, ('i', '>'): 0xee, ('i', ':'): 0xef, ('d', '-'): 0xf0, ('n', '?'): 0xf1, ('o', '!'): 0xf2, ('o', '\''): 0xf3, ('o', '>'): 0xf4, ('o', '?'): 0xf5, ('o', ':'): 0xf6, ('-', ':'): 0xf7, ('o', '/'): 0xf8, ('u', '!'): 0xf9, ('u', '\''): 0xfa, ('u', '>'): 0xfb, ('u', ':'): 0xfc, ('y', '\''): 0xfd, ('t', 'h'): 0xfe, ('y', ':'): 0xff, ('A', '-'): 0x0100, ('a', '-'): 0x0101, ('A', '('): 0x0102, ('a', '('): 0x0103, ('A', ';'): 0x0104, ('a', ';'): 0x0105, ('C', '\''): 0x0106, ('c', '\''): 0x0107, ('C', '>'): 0x0108, ('c', '>'): 0x0109, ('C', '.'): 0x010a, ('c', '.'): 0x010b, ('C', '<'): 0x010c, ('c', '<'): 0x010d, ('D', '<'): 0x010e, ('d', '<'): 0x010f, ('D', '/'): 0x0110, ('d', '/'): 0x0111, ('E', '-'): 0x0112, ('e', '-'): 0x0113, ('E', '('): 0x0114, ('e', '('): 0x0115, ('E', '.'): 0x0116, ('e', '.'): 0x0117, ('E', ';'): 0x0118, ('e', ';'): 0x0119, ('E', '<'): 0x011a, ('e', '<'): 0x011b, ('G', '>'): 0x011c, ('g', '>'): 0x011d, ('G', '('): 0x011e, ('g', '('): 0x011f, ('G', '.'): 0x0120, ('g', '.'): 0x0121, ('G', ','): 0x0122, ('g', ','): 0x0123, ('H', '>'): 0x0124, ('h', '>'): 0x0125, ('H', '/'): 0x0126, ('h', '/'): 0x0127, ('I', '?'): 0x0128, ('i', '?'): 0x0129, ('I', '-'): 0x012a, ('i', '-'): 0x012b, ('I', '('): 0x012c, ('i', '('): 0x012d, ('I', ';'): 0x012e, ('i', ';'): 0x012f, ('I', '.'): 0x0130, ('i', '.'): 0x0131, ('I', 'J'): 0x0132, ('i', 'j'): 0x0133, ('J', '>'): 0x0134, ('j', '>'): 0x0135, ('K', ','): 0x0136, ('k', ','): 0x0137, ('k', 'k'): 0x0138, ('L', '\''): 0x0139, ('l', '\''): 0x013a, ('L', ','): 0x013b, ('l', ','): 0x013c, ('L', '<'): 0x013d, ('l', '<'): 0x013e, ('L', '.'): 0x013f, ('l', '.'): 0x0140, ('L', '/'): 0x0141, ('l', '/'): 0x0142, ('N', '\''): 0x0143, ('n', '\''): 0x0144, ('N', ','): 0x0145, ('n', ','): 0x0146, ('N', '<'): 0x0147, ('n', '<'): 0x0148, ('\'', 'n'): 0x0149, ('N', 'G'): 0x014a, ('n', 'g'): 0x014b, ('O', '-'): 0x014c, ('o', '-'): 0x014d, ('O', '('): 0x014e, ('o', '('): 0x014f, ('O', '"'): 0x0150, ('o', '"'): 0x0151, ('O', 'E'): 0x0152, ('o', 'e'): 0x0153, ('R', '\''): 0x0154, ('r', '\''): 0x0155, ('R', ','): 0x0156, ('r', ','): 0x0157, ('R', '<'): 0x0158, ('r', '<'): 0x0159, ('S', '\''): 0x015a, ('s', '\''): 0x015b, ('S', '>'): 0x015c, ('s', '>'): 0x015d, ('S', ','): 0x015e, ('s', ','): 0x015f, ('S', '<'): 0x0160, ('s', '<'): 0x0161, ('T', ','): 0x0162, ('t', ','): 0x0163, ('T', '<'): 0x0164, ('t', '<'): 0x0165, ('T', '/'): 0x0166, ('t', '/'): 0x0167, ('U', '?'): 0x0168, ('u', '?'): 0x0169, ('U', '-'): 0x016a, ('u', '-'): 0x016b, ('U', '('): 0x016c, ('u', '('): 0x016d, ('U', '0'): 0x016e, ('u', '0'): 0x016f, ('U', '"'): 0x0170, ('u', '"'): 0x0171, ('U', ';'): 0x0172, ('u', ';'): 0x0173, ('W', '>'): 0x0174, ('w', '>'): 0x0175, ('Y', '>'): 0x0176, ('y', '>'): 0x0177, ('Y', ':'): 0x0178, ('Z', '\''): 0x0179, ('z', '\''): 0x017a, ('Z', '.'): 0x017b, ('z', '.'): 0x017c, ('Z', '<'): 0x017d, ('z', '<'): 0x017e, ('O', '9'): 0x01a0, ('o', '9'): 0x01a1, ('O', 'I'): 0x01a2, ('o', 'i'): 0x01a3, ('y', 'r'): 0x01a6, ('U', '9'): 0x01af, ('u', '9'): 0x01b0, ('Z', '/'): 0x01b5, ('z', '/'): 0x01b6, ('E', 'D'): 0x01b7, ('A', '<'): 0x01cd, ('a', '<'): 0x01ce, ('I', '<'): 0x01cf, ('i', '<'): 0x01d0, ('O', '<'): 0x01d1, ('o', '<'): 0x01d2, ('U', '<'): 0x01d3, ('u', '<'): 0x01d4, ('A', '1'): 0x01de, ('a', '1'): 0x01df, ('A', '7'): 0x01e0, ('a', '7'): 0x01e1, ('A', '3'): 0x01e2, ('a', '3'): 0x01e3, ('G', '/'): 0x01e4, ('g', '/'): 0x01e5, ('G', '<'): 0x01e6, ('g', '<'): 0x01e7, ('K', '<'): 0x01e8, ('k', '<'): 0x01e9, ('O', ';'): 0x01ea, ('o', ';'): 0x01eb, ('O', '1'): 0x01ec, ('o', '1'): 0x01ed, ('E', 'Z'): 0x01ee, ('e', 'z'): 0x01ef, ('j', '<'): 0x01f0, ('G', '\''): 0x01f4, ('g', '\''): 0x01f5, (';', 'S'): 0x02bf, ('\'', '<'): 0x02c7, ('\'', '('): 0x02d8, ('\'', '.'): 0x02d9, ('\'', '0'): 0x02da, ('\'', ';'): 0x02db, ('\'', '"'): 0x02dd, ('A', '%'): 0x0386, ('E', '%'): 0x0388, ('Y', '%'): 0x0389, ('I', '%'): 0x038a, ('O', '%'): 0x038c, ('U', '%'): 0x038e, ('W', '%'): 0x038f, ('i', '3'): 0x0390, ('A', '*'): 0x0391, ('B', '*'): 0x0392, ('G', '*'): 0x0393, ('D', '*'): 0x0394, ('E', '*'): 0x0395, ('Z', '*'): 0x0396, ('Y', '*'): 0x0397, ('H', '*'): 0x0398, ('I', '*'): 0x0399, ('K', '*'): 0x039a, ('L', '*'): 0x039b, ('M', '*'): 0x039c, ('N', '*'): 0x039d, ('C', '*'): 0x039e, ('O', '*'): 0x039f, ('P', '*'): 0x03a0, ('R', '*'): 0x03a1, ('S', '*'): 0x03a3, ('T', '*'): 0x03a4, ('U', '*'): 0x03a5, ('F', '*'): 0x03a6, ('X', '*'): 0x03a7, ('Q', '*'): 0x03a8, ('W', '*'): 0x03a9, ('J', '*'): 0x03aa, ('V', '*'): 0x03ab, ('a', '%'): 0x03ac, ('e', '%'): 0x03ad, ('y', '%'): 0x03ae, ('i', '%'): 0x03af, ('u', '3'): 0x03b0, ('a', '*'): 0x03b1, ('b', '*'): 0x03b2, ('g', '*'): 0x03b3, ('d', '*'): 0x03b4, ('e', '*'): 0x03b5, ('z', '*'): 0x03b6, ('y', '*'): 0x03b7, ('h', '*'): 0x03b8, ('i', '*'): 0x03b9, ('k', '*'): 0x03ba, ('l', '*'): 0x03bb, ('m', '*'): 0x03bc, ('n', '*'): 0x03bd, ('c', '*'): 0x03be, ('o', '*'): 0x03bf, ('p', '*'): 0x03c0, ('r', '*'): 0x03c1, ('*', 's'): 0x03c2, ('s', '*'): 0x03c3, ('t', '*'): 0x03c4, ('u', '*'): 0x03c5, ('f', '*'): 0x03c6, ('x', '*'): 0x03c7, ('q', '*'): 0x03c8, ('w', '*'): 0x03c9, ('j', '*'): 0x03ca, ('v', '*'): 0x03cb, ('o', '%'): 0x03cc, ('u', '%'): 0x03cd, ('w', '%'): 0x03ce, ('\'', 'G'): 0x03d8, (',', 'G'): 0x03d9, ('T', '3'): 0x03da, ('t', '3'): 0x03db, ('M', '3'): 0x03dc, ('m', '3'): 0x03dd, ('K', '3'): 0x03de, ('k', '3'): 0x03df, ('P', '3'): 0x03e0, ('p', '3'): 0x03e1, ('\'', '%'): 0x03f4, ('j', '3'): 0x03f5, ('I', 'O'): 0x0401, ('D', '%'): 0x0402, ('G', '%'): 0x0403, ('I', 'E'): 0x0404, ('D', 'S'): 0x0405, ('I', 'I'): 0x0406, ('Y', 'I'): 0x0407, ('J', '%'): 0x0408, ('L', 'J'): 0x0409, ('N', 'J'): 0x040a, ('T', 's'): 0x040b, ('K', 'J'): 0x040c, ('V', '%'): 0x040e, ('D', 'Z'): 0x040f, ('A', '='): 0x0410, ('B', '='): 0x0411, ('V', '='): 0x0412, ('G', '='): 0x0413, ('D', '='): 0x0414, ('E', '='): 0x0415, ('Z', '%'): 0x0416, ('Z', '='): 0x0417, ('I', '='): 0x0418, ('J', '='): 0x0419, ('K', '='): 0x041a, ('L', '='): 0x041b, ('M', '='): 0x041c, ('N', '='): 0x041d, ('O', '='): 0x041e, ('P', '='): 0x041f, ('R', '='): 0x0420, ('S', '='): 0x0421, ('T', '='): 0x0422, ('U', '='): 0x0423, ('F', '='): 0x0424, ('H', '='): 0x0425, ('C', '='): 0x0426, ('C', '%'): 0x0427, ('S', '%'): 0x0428, ('S', 'c'): 0x0429, ('=', '"'): 0x042a, ('Y', '='): 0x042b, ('%', '"'): 0x042c, ('J', 'E'): 0x042d, ('J', 'U'): 0x042e, ('J', 'A'): 0x042f, ('a', '='): 0x0430, ('b', '='): 0x0431, ('v', '='): 0x0432, ('g', '='): 0x0433, ('d', '='): 0x0434, ('e', '='): 0x0435, ('z', '%'): 0x0436, ('z', '='): 0x0437, ('i', '='): 0x0438, ('j', '='): 0x0439, ('k', '='): 0x043a, ('l', '='): 0x043b, ('m', '='): 0x043c, ('n', '='): 0x043d, ('o', '='): 0x043e, ('p', '='): 0x043f, ('r', '='): 0x0440, ('s', '='): 0x0441, ('t', '='): 0x0442, ('u', '='): 0x0443, ('f', '='): 0x0444, ('h', '='): 0x0445, ('c', '='): 0x0446, ('c', '%'): 0x0447, ('s', '%'): 0x0448, ('s', 'c'): 0x0449, ('=', '\''): 0x044a, ('y', '='): 0x044b, ('%', '\''): 0x044c, ('j', 'e'): 0x044d, ('j', 'u'): 0x044e, ('j', 'a'): 0x044f, ('i', 'o'): 0x0451, ('d', '%'): 0x0452, ('g', '%'): 0x0453, ('i', 'e'): 0x0454, ('d', 's'): 0x0455, ('i', 'i'): 0x0456, ('y', 'i'): 0x0457, ('j', '%'): 0x0458, ('l', 'j'): 0x0459, ('n', 'j'): 0x045a, ('t', 's'): 0x045b, ('k', 'j'): 0x045c, ('v', '%'): 0x045e, ('d', 'z'): 0x045f, ('Y', '3'): 0x0462, ('y', '3'): 0x0463, ('O', '3'): 0x046a, ('o', '3'): 0x046b, ('F', '3'): 0x0472, ('f', '3'): 0x0473, ('V', '3'): 0x0474, ('v', '3'): 0x0475, ('C', '3'): 0x0480, ('c', '3'): 0x0481, ('G', '3'): 0x0490, ('g', '3'): 0x0491, ('A', '+'): 0x05d0, ('B', '+'): 0x05d1, ('G', '+'): 0x05d2, ('D', '+'): 0x05d3, ('H', '+'): 0x05d4, ('W', '+'): 0x05d5, ('Z', '+'): 0x05d6, ('X', '+'): 0x05d7, ('T', 'j'): 0x05d8, ('J', '+'): 0x05d9, ('K', '%'): 0x05da, ('K', '+'): 0x05db, ('L', '+'): 0x05dc, ('M', '%'): 0x05dd, ('M', '+'): 0x05de, ('N', '%'): 0x05df, ('N', '+'): 0x05e0, ('S', '+'): 0x05e1, ('E', '+'): 0x05e2, ('P', '%'): 0x05e3, ('P', '+'): 0x05e4, ('Z', 'j'): 0x05e5, ('Z', 'J'): 0x05e6, ('Q', '+'): 0x05e7, ('R', '+'): 0x05e8, ('S', 'h'): 0x05e9, ('T', '+'): 0x05ea, (',', '+'): 0x060c, (';', '+'): 0x061b, ('?', '+'): 0x061f, ('H', '\''): 0x0621, ('a', 'M'): 0x0622, ('a', 'H'): 0x0623, ('w', 'H'): 0x0624, ('a', 'h'): 0x0625, ('y', 'H'): 0x0626, ('a', '+'): 0x0627, ('b', '+'): 0x0628, ('t', 'm'): 0x0629, ('t', '+'): 0x062a, ('t', 'k'): 0x062b, ('g', '+'): 0x062c, ('h', 'k'): 0x062d, ('x', '+'): 0x062e, ('d', '+'): 0x062f, ('d', 'k'): 0x0630, ('r', '+'): 0x0631, ('z', '+'): 0x0632, ('s', '+'): 0x0633, ('s', 'n'): 0x0634, ('c', '+'): 0x0635, ('d', 'd'): 0x0636, ('t', 'j'): 0x0637, ('z', 'H'): 0x0638, ('e', '+'): 0x0639, ('i', '+'): 0x063a, ('+', '+'): 0x0640, ('f', '+'): 0x0641, ('q', '+'): 0x0642, ('k', '+'): 0x0643, ('l', '+'): 0x0644, ('m', '+'): 0x0645, ('n', '+'): 0x0646, ('h', '+'): 0x0647, ('w', '+'): 0x0648, ('j', '+'): 0x0649, ('y', '+'): 0x064a, (':', '+'): 0x064b, ('"', '+'): 0x064c, ('=', '+'): 0x064d, ('/', '+'): 0x064e, ('\'', '+'): 0x064f, ('1', '+'): 0x0650, ('3', '+'): 0x0651, ('0', '+'): 0x0652, ('a', 'S'): 0x0670, ('p', '+'): 0x067e, ('v', '+'): 0x06a4, ('g', 'f'): 0x06af, ('0', 'a'): 0x06f0, ('1', 'a'): 0x06f1, ('2', 'a'): 0x06f2, ('3', 'a'): 0x06f3, ('4', 'a'): 0x06f4, ('5', 'a'): 0x06f5, ('6', 'a'): 0x06f6, ('7', 'a'): 0x06f7, ('8', 'a'): 0x06f8, ('9', 'a'): 0x06f9, ('B', '.'): 0x1e02, ('b', '.'): 0x1e03, ('B', '_'): 0x1e06, ('b', '_'): 0x1e07, ('D', '.'): 0x1e0a, ('d', '.'): 0x1e0b, ('D', '_'): 0x1e0e, ('d', '_'): 0x1e0f, ('D', ','): 0x1e10, ('d', ','): 0x1e11, ('F', '.'): 0x1e1e, ('f', '.'): 0x1e1f, ('G', '-'): 0x1e20, ('g', '-'): 0x1e21, ('H', '.'): 0x1e22, ('h', '.'): 0x1e23, ('H', ':'): 0x1e26, ('h', ':'): 0x1e27, ('H', ','): 0x1e28, ('h', ','): 0x1e29, ('K', '\''): 0x1e30, ('k', '\''): 0x1e31, ('K', '_'): 0x1e34, ('k', '_'): 0x1e35, ('L', '_'): 0x1e3a, ('l', '_'): 0x1e3b, ('M', '\''): 0x1e3e, ('m', '\''): 0x1e3f, ('M', '.'): 0x1e40, ('m', '.'): 0x1e41, ('N', '.'): 0x1e44, ('n', '.'): 0x1e45, ('N', '_'): 0x1e48, ('n', '_'): 0x1e49, ('P', '\''): 0x1e54, ('p', '\''): 0x1e55, ('P', '.'): 0x1e56, ('p', '.'): 0x1e57, ('R', '.'): 0x1e58, ('r', '.'): 0x1e59, ('R', '_'): 0x1e5e, ('r', '_'): 0x1e5f, ('S', '.'): 0x1e60, ('s', '.'): 0x1e61, ('T', '.'): 0x1e6a, ('t', '.'): 0x1e6b, ('T', '_'): 0x1e6e, ('t', '_'): 0x1e6f, ('V', '?'): 0x1e7c, ('v', '?'): 0x1e7d, ('W', '!'): 0x1e80, ('w', '!'): 0x1e81, ('W', '\''): 0x1e82, ('w', '\''): 0x1e83, ('W', ':'): 0x1e84, ('w', ':'): 0x1e85, ('W', '.'): 0x1e86, ('w', '.'): 0x1e87, ('X', '.'): 0x1e8a, ('x', '.'): 0x1e8b, ('X', ':'): 0x1e8c, ('x', ':'): 0x1e8d, ('Y', '.'): 0x1e8e, ('y', '.'): 0x1e8f, ('Z', '>'): 0x1e90, ('z', '>'): 0x1e91, ('Z', '_'): 0x1e94, ('z', '_'): 0x1e95, ('h', '_'): 0x1e96, ('t', ':'): 0x1e97, ('w', '0'): 0x1e98, ('y', '0'): 0x1e99, ('A', '2'): 0x1ea2, ('a', '2'): 0x1ea3, ('E', '2'): 0x1eba, ('e', '2'): 0x1ebb, ('E', '?'): 0x1ebc, ('e', '?'): 0x1ebd, ('I', '2'): 0x1ec8, ('i', '2'): 0x1ec9, ('O', '2'): 0x1ece, ('o', '2'): 0x1ecf, ('U', '2'): 0x1ee6, ('u', '2'): 0x1ee7, ('Y', '!'): 0x1ef2, ('y', '!'): 0x1ef3, ('Y', '2'): 0x1ef6, ('y', '2'): 0x1ef7, ('Y', '?'): 0x1ef8, ('y', '?'): 0x1ef9, (';', '\''): 0x1f00, (',', '\''): 0x1f01, (';', '!'): 0x1f02, (',', '!'): 0x1f03, ('?', ';'): 0x1f04, ('?', ','): 0x1f05, ('!', ':'): 0x1f06, ('?', ':'): 0x1f07, ('1', 'N'): 0x2002, ('1', 'M'): 0x2003, ('3', 'M'): 0x2004, ('4', 'M'): 0x2005, ('6', 'M'): 0x2006, ('1', 'T'): 0x2009, ('1', 'H'): 0x200a, ('-', '1'): 0x2010, ('-', 'N'): 0x2013, ('-', 'M'): 0x2014, ('-', '3'): 0x2015, ('!', '2'): 0x2016, ('=', '2'): 0x2017, ('\'', '6'): 0x2018, ('\'', '9'): 0x2019, ('.', '9'): 0x201a, ('9', '\''): 0x201b, ('"', '6'): 0x201c, ('"', '9'): 0x201d, (':', '9'): 0x201e, ('9', '"'): 0x201f, ('/', '-'): 0x2020, ('/', '='): 0x2021, ('.', '.'): 0x2025, ('%', '0'): 0x2030, ('1', '\''): 0x2032, ('2', '\''): 0x2033, ('3', '\''): 0x2034, ('1', '"'): 0x2035, ('2', '"'): 0x2036, ('3', '"'): 0x2037, ('C', 'a'): 0x2038, ('<', '1'): 0x2039, ('>', '1'): 0x203a, (':', 'X'): 0x203b, ('\'', '-'): 0x203e, ('/', 'f'): 0x2044, ('0', 'S'): 0x2070, ('4', 'S'): 0x2074, ('5', 'S'): 0x2075, ('6', 'S'): 0x2076, ('7', 'S'): 0x2077, ('8', 'S'): 0x2078, ('9', 'S'): 0x2079, ('+', 'S'): 0x207a, ('-', 'S'): 0x207b, ('=', 'S'): 0x207c, ('(', 'S'): 0x207d, (')', 'S'): 0x207e, ('n', 'S'): 0x207f, ('0', 's'): 0x2080, ('1', 's'): 0x2081, ('2', 's'): 0x2082, ('3', 's'): 0x2083, ('4', 's'): 0x2084, ('5', 's'): 0x2085, ('6', 's'): 0x2086, ('7', 's'): 0x2087, ('8', 's'): 0x2088, ('9', 's'): 0x2089, ('+', 's'): 0x208a, ('-', 's'): 0x208b, ('=', 's'): 0x208c, ('(', 's'): 0x208d, (')', 's'): 0x208e, ('L', 'i'): 0x20a4, ('P', 't'): 0x20a7, ('W', '='): 0x20a9, ('=', 'e'): 0x20ac, # euro ('E', 'u'): 0x20ac, # euro ('=', 'R'): 0x20bd, # rouble ('=', 'P'): 0x20bd, # rouble ('o', 'C'): 0x2103, ('c', 'o'): 0x2105, ('o', 'F'): 0x2109, ('N', '0'): 0x2116, ('P', 'O'): 0x2117, ('R', 'x'): 0x211e, ('S', 'M'): 0x2120, ('T', 'M'): 0x2122, ('O', 'm'): 0x2126, ('A', 'O'): 0x212b, ('1', '3'): 0x2153, ('2', '3'): 0x2154, ('1', '5'): 0x2155, ('2', '5'): 0x2156, ('3', '5'): 0x2157, ('4', '5'): 0x2158, ('1', '6'): 0x2159, ('5', '6'): 0x215a, ('1', '8'): 0x215b, ('3', '8'): 0x215c, ('5', '8'): 0x215d, ('7', '8'): 0x215e, ('1', 'R'): 0x2160, ('2', 'R'): 0x2161, ('3', 'R'): 0x2162, ('4', 'R'): 0x2163, ('5', 'R'): 0x2164, ('6', 'R'): 0x2165, ('7', 'R'): 0x2166, ('8', 'R'): 0x2167, ('9', 'R'): 0x2168, ('a', 'R'): 0x2169, ('b', 'R'): 0x216a, ('c', 'R'): 0x216b, ('1', 'r'): 0x2170, ('2', 'r'): 0x2171, ('3', 'r'): 0x2172, ('4', 'r'): 0x2173, ('5', 'r'): 0x2174, ('6', 'r'): 0x2175, ('7', 'r'): 0x2176, ('8', 'r'): 0x2177, ('9', 'r'): 0x2178, ('a', 'r'): 0x2179, ('b', 'r'): 0x217a, ('c', 'r'): 0x217b, ('<', '-'): 0x2190, ('-', '!'): 0x2191, ('-', '>'): 0x2192, ('-', 'v'): 0x2193, ('<', '>'): 0x2194, ('U', 'D'): 0x2195, ('<', '='): 0x21d0, ('=', '>'): 0x21d2, ('=', '='): 0x21d4, ('F', 'A'): 0x2200, ('d', 'P'): 0x2202, ('T', 'E'): 0x2203, ('/', '0'): 0x2205, ('D', 'E'): 0x2206, ('N', 'B'): 0x2207, ('(', '-'): 0x2208, ('-', ')'): 0x220b, ('*', 'P'): 0x220f, ('+', 'Z'): 0x2211, ('-', '2'): 0x2212, ('-', '+'): 0x2213, ('*', '-'): 0x2217, ('O', 'b'): 0x2218, ('S', 'b'): 0x2219, ('R', 'T'): 0x221a, ('0', '('): 0x221d, ('0', '0'): 0x221e, ('-', 'L'): 0x221f, ('-', 'V'): 0x2220, ('P', 'P'): 0x2225, ('A', 'N'): 0x2227, ('O', 'R'): 0x2228, ('(', 'U'): 0x2229, (')', 'U'): 0x222a, ('I', 'n'): 0x222b, ('D', 'I'): 0x222c, ('I', 'o'): 0x222e, ('.', ':'): 0x2234, (':', '.'): 0x2235, (':', 'R'): 0x2236, (':', ':'): 0x2237, ('?', '1'): 0x223c, ('C', 'G'): 0x223e, ('?', '-'): 0x2243, ('?', '='): 0x2245, ('?', '2'): 0x2248, ('=', '?'): 0x224c, ('H', 'I'): 0x2253, ('!', '='): 0x2260, ('=', '3'): 0x2261, ('=', '<'): 0x2264, ('>', '='): 0x2265, ('<', '*'): 0x226a, ('*', '>'): 0x226b, ('!', '<'): 0x226e, ('!', '>'): 0x226f, ('(', 'C'): 0x2282, (')', 'C'): 0x2283, ('(', '_'): 0x2286, (')', '_'): 0x2287, ('0', '.'): 0x2299, ('0', '2'): 0x229a, ('-', 'T'): 0x22a5, ('.', 'P'): 0x22c5, (':', '3'): 0x22ee, ('.', '3'): 0x22ef, ('E', 'h'): 0x2302, ('<', '7'): 0x2308, ('>', '7'): 0x2309, ('7', '<'): 0x230a, ('7', '>'): 0x230b, ('N', 'I'): 0x2310, ('(', 'A'): 0x2312, ('T', 'R'): 0x2315, ('I', 'u'): 0x2320, ('I', 'l'): 0x2321, ('<', '/'): 0x2329, ('/', '>'): 0x232a, ('V', 's'): 0x2423, ('1', 'h'): 0x2440, ('3', 'h'): 0x2441, ('2', 'h'): 0x2442, ('4', 'h'): 0x2443, ('1', 'j'): 0x2446, ('2', 'j'): 0x2447, ('3', 'j'): 0x2448, ('4', 'j'): 0x2449, ('1', '.'): 0x2488, ('2', '.'): 0x2489, ('3', '.'): 0x248a, ('4', '.'): 0x248b, ('5', '.'): 0x248c, ('6', '.'): 0x248d, ('7', '.'): 0x248e, ('8', '.'): 0x248f, ('9', '.'): 0x2490, ('h', 'h'): 0x2500, ('H', 'H'): 0x2501, ('v', 'v'): 0x2502, ('V', 'V'): 0x2503, ('3', '-'): 0x2504, ('3', '_'): 0x2505, ('3', '!'): 0x2506, ('3', '/'): 0x2507, ('4', '-'): 0x2508, ('4', '_'): 0x2509, ('4', '!'): 0x250a, ('4', '/'): 0x250b, ('d', 'r'): 0x250c, ('d', 'R'): 0x250d, ('D', 'r'): 0x250e, ('D', 'R'): 0x250f, ('d', 'l'): 0x2510, ('d', 'L'): 0x2511, ('D', 'l'): 0x2512, ('L', 'D'): 0x2513, ('u', 'r'): 0x2514, ('u', 'R'): 0x2515, ('U', 'r'): 0x2516, ('U', 'R'): 0x2517, ('u', 'l'): 0x2518, ('u', 'L'): 0x2519, ('U', 'l'): 0x251a, ('U', 'L'): 0x251b, ('v', 'r'): 0x251c, ('v', 'R'): 0x251d, ('V', 'r'): 0x2520, ('V', 'R'): 0x2523, ('v', 'l'): 0x2524, ('v', 'L'): 0x2525, ('V', 'l'): 0x2528, ('V', 'L'): 0x252b, ('d', 'h'): 0x252c, ('d', 'H'): 0x252f, ('D', 'h'): 0x2530, ('D', 'H'): 0x2533, ('u', 'h'): 0x2534, ('u', 'H'): 0x2537, ('U', 'h'): 0x2538, ('U', 'H'): 0x253b, ('v', 'h'): 0x253c, ('v', 'H'): 0x253f, ('V', 'h'): 0x2542, ('V', 'H'): 0x254b, ('F', 'D'): 0x2571, ('B', 'D'): 0x2572, ('T', 'B'): 0x2580, ('L', 'B'): 0x2584, ('F', 'B'): 0x2588, ('l', 'B'): 0x258c, ('R', 'B'): 0x2590, ('.', 'S'): 0x2591, (':', 'S'): 0x2592, ('?', 'S'): 0x2593, ('f', 'S'): 0x25a0, ('O', 'S'): 0x25a1, ('R', 'O'): 0x25a2, ('R', 'r'): 0x25a3, ('R', 'F'): 0x25a4, ('R', 'Y'): 0x25a5, ('R', 'H'): 0x25a6, ('R', 'Z'): 0x25a7, ('R', 'K'): 0x25a8, ('R', 'X'): 0x25a9, ('s', 'B'): 0x25aa, ('S', 'R'): 0x25ac, ('O', 'r'): 0x25ad, ('U', 'T'): 0x25b2, ('u', 'T'): 0x25b3, ('P', 'R'): 0x25b6, ('T', 'r'): 0x25b7, ('D', 't'): 0x25bc, ('d', 'T'): 0x25bd, ('P', 'L'): 0x25c0, ('T', 'l'): 0x25c1, ('D', 'b'): 0x25c6, ('D', 'w'): 0x25c7, ('L', 'Z'): 0x25ca, ('0', 'm'): 0x25cb, ('0', 'o'): 0x25ce, ('0', 'M'): 0x25cf, ('0', 'L'): 0x25d0, ('0', 'R'): 0x25d1, ('S', 'n'): 0x25d8, ('I', 'c'): 0x25d9, ('F', 'd'): 0x25e2, ('B', 'd'): 0x25e3, ('*', '2'): 0x2605, ('*', '1'): 0x2606, ('<', 'H'): 0x261c, ('>', 'H'): 0x261e, ('0', 'u'): 0x263a, ('0', 'U'): 0x263b, ('S', 'U'): 0x263c, ('F', 'm'): 0x2640, ('M', 'l'): 0x2642, ('c', 'S'): 0x2660, ('c', 'H'): 0x2661, ('c', 'D'): 0x2662, ('c', 'C'): 0x2663, ('M', 'd'): 0x2669, ('M', '8'): 0x266a, ('M', '2'): 0x266b, ('M', 'b'): 0x266d, ('M', 'x'): 0x266e, ('M', 'X'): 0x266f, ('O', 'K'): 0x2713, ('X', 'X'): 0x2717, ('-', 'X'): 0x2720, ('I', 'S'): 0x3000, (',', '_'): 0x3001, ('.', '_'): 0x3002, ('+', '"'): 0x3003, ('+', '_'): 0x3004, ('*', '_'): 0x3005, (';', '_'): 0x3006, ('0', '_'): 0x3007, ('<', '+'): 0x300a, ('>', '+'): 0x300b, ('<', '\''): 0x300c, ('>', '\''): 0x300d, ('<', '"'): 0x300e, ('>', '"'): 0x300f, ('(', '"'): 0x3010, (')', '"'): 0x3011, ('=', 'T'): 0x3012, ('=', '_'): 0x3013, ('(', '\''): 0x3014, (')', '\''): 0x3015, ('(', 'I'): 0x3016, (')', 'I'): 0x3017, ('-', '?'): 0x301c, ('A', '5'): 0x3041, ('a', '5'): 0x3042, ('I', '5'): 0x3043, ('i', '5'): 0x3044, ('U', '5'): 0x3045, ('u', '5'): 0x3046, ('E', '5'): 0x3047, ('e', '5'): 0x3048, ('O', '5'): 0x3049, ('o', '5'): 0x304a, ('k', 'a'): 0x304b, ('g', 'a'): 0x304c, ('k', 'i'): 0x304d, ('g', 'i'): 0x304e, ('k', 'u'): 0x304f, ('g', 'u'): 0x3050, ('k', 'e'): 0x3051, ('g', 'e'): 0x3052, ('k', 'o'): 0x3053, ('g', 'o'): 0x3054, ('s', 'a'): 0x3055, ('z', 'a'): 0x3056, ('s', 'i'): 0x3057, ('z', 'i'): 0x3058, ('s', 'u'): 0x3059, ('z', 'u'): 0x305a, ('s', 'e'): 0x305b, ('z', 'e'): 0x305c, ('s', 'o'): 0x305d, ('z', 'o'): 0x305e, ('t', 'a'): 0x305f, ('d', 'a'): 0x3060, ('t', 'i'): 0x3061, ('d', 'i'): 0x3062, ('t', 'U'): 0x3063, ('t', 'u'): 0x3064, ('d', 'u'): 0x3065, ('t', 'e'): 0x3066, ('d', 'e'): 0x3067, ('t', 'o'): 0x3068, ('d', 'o'): 0x3069, ('n', 'a'): 0x306a, ('n', 'i'): 0x306b, ('n', 'u'): 0x306c, ('n', 'e'): 0x306d, ('n', 'o'): 0x306e, ('h', 'a'): 0x306f, ('b', 'a'): 0x3070, ('p', 'a'): 0x3071, ('h', 'i'): 0x3072, ('b', 'i'): 0x3073, ('p', 'i'): 0x3074, ('h', 'u'): 0x3075, ('b', 'u'): 0x3076, ('p', 'u'): 0x3077, ('h', 'e'): 0x3078, ('b', 'e'): 0x3079, ('p', 'e'): 0x307a, ('h', 'o'): 0x307b, ('b', 'o'): 0x307c, ('p', 'o'): 0x307d, ('m', 'a'): 0x307e, ('m', 'i'): 0x307f, ('m', 'u'): 0x3080, ('m', 'e'): 0x3081, ('m', 'o'): 0x3082, ('y', 'A'): 0x3083, ('y', 'a'): 0x3084, ('y', 'U'): 0x3085, ('y', 'u'): 0x3086, ('y', 'O'): 0x3087, ('y', 'o'): 0x3088, ('r', 'a'): 0x3089, ('r', 'i'): 0x308a, ('r', 'u'): 0x308b, ('r', 'e'): 0x308c, ('r', 'o'): 0x308d, ('w', 'A'): 0x308e, ('w', 'a'): 0x308f, ('w', 'i'): 0x3090, ('w', 'e'): 0x3091, ('w', 'o'): 0x3092, ('n', '5'): 0x3093, ('v', 'u'): 0x3094, ('"', '5'): 0x309b, ('0', '5'): 0x309c, ('*', '5'): 0x309d, ('+', '5'): 0x309e, ('a', '6'): 0x30a1, ('A', '6'): 0x30a2, ('i', '6'): 0x30a3, ('I', '6'): 0x30a4, ('u', '6'): 0x30a5, ('U', '6'): 0x30a6, ('e', '6'): 0x30a7, ('E', '6'): 0x30a8, ('o', '6'): 0x30a9, ('O', '6'): 0x30aa, ('K', 'a'): 0x30ab, ('G', 'a'): 0x30ac, ('K', 'i'): 0x30ad, ('G', 'i'): 0x30ae, ('K', 'u'): 0x30af, ('G', 'u'): 0x30b0, ('K', 'e'): 0x30b1, ('G', 'e'): 0x30b2, ('K', 'o'): 0x30b3, ('G', 'o'): 0x30b4, ('S', 'a'): 0x30b5, ('Z', 'a'): 0x30b6, ('S', 'i'): 0x30b7, ('Z', 'i'): 0x30b8, ('S', 'u'): 0x30b9, ('Z', 'u'): 0x30ba, ('S', 'e'): 0x30bb, ('Z', 'e'): 0x30bc, ('S', 'o'): 0x30bd, ('Z', 'o'): 0x30be, ('T', 'a'): 0x30bf, ('D', 'a'): 0x30c0, ('T', 'i'): 0x30c1, ('D', 'i'): 0x30c2, ('T', 'U'): 0x30c3, ('T', 'u'): 0x30c4, ('D', 'u'): 0x30c5, ('T', 'e'): 0x30c6, ('D', 'e'): 0x30c7, ('T', 'o'): 0x30c8, ('D', 'o'): 0x30c9, ('N', 'a'): 0x30ca, ('N', 'i'): 0x30cb, ('N', 'u'): 0x30cc, ('N', 'e'): 0x30cd, ('N', 'o'): 0x30ce, ('H', 'a'): 0x30cf, ('B', 'a'): 0x30d0, ('P', 'a'): 0x30d1, ('H', 'i'): 0x30d2, ('B', 'i'): 0x30d3, ('P', 'i'): 0x30d4, ('H', 'u'): 0x30d5, ('B', 'u'): 0x30d6, ('P', 'u'): 0x30d7, ('H', 'e'): 0x30d8, ('B', 'e'): 0x30d9, ('P', 'e'): 0x30da, ('H', 'o'): 0x30db, ('B', 'o'): 0x30dc, ('P', 'o'): 0x30dd, ('M', 'a'): 0x30de, ('M', 'i'): 0x30df, ('M', 'u'): 0x30e0, ('M', 'e'): 0x30e1, ('M', 'o'): 0x30e2, ('Y', 'A'): 0x30e3, ('Y', 'a'): 0x30e4, ('Y', 'U'): 0x30e5, ('Y', 'u'): 0x30e6, ('Y', 'O'): 0x30e7, ('Y', 'o'): 0x30e8, ('R', 'a'): 0x30e9, ('R', 'i'): 0x30ea, ('R', 'u'): 0x30eb, ('R', 'e'): 0x30ec, ('R', 'o'): 0x30ed, ('W', 'A'): 0x30ee, ('W', 'a'): 0x30ef, ('W', 'i'): 0x30f0, ('W', 'e'): 0x30f1, ('W', 'o'): 0x30f2, ('N', '6'): 0x30f3, ('V', 'u'): 0x30f4, ('K', 'A'): 0x30f5, ('K', 'E'): 0x30f6, ('V', 'a'): 0x30f7, ('V', 'i'): 0x30f8, ('V', 'e'): 0x30f9, ('V', 'o'): 0x30fa, ('.', '6'): 0x30fb, ('-', '6'): 0x30fc, ('*', '6'): 0x30fd, ('+', '6'): 0x30fe, ('b', '4'): 0x3105, ('p', '4'): 0x3106, ('m', '4'): 0x3107, ('f', '4'): 0x3108, ('d', '4'): 0x3109, ('t', '4'): 0x310a, ('n', '4'): 0x310b, ('l', '4'): 0x310c, ('g', '4'): 0x310d, ('k', '4'): 0x310e, ('h', '4'): 0x310f, ('j', '4'): 0x3110, ('q', '4'): 0x3111, ('x', '4'): 0x3112, ('z', 'h'): 0x3113, ('c', 'h'): 0x3114, ('s', 'h'): 0x3115, ('r', '4'): 0x3116, ('z', '4'): 0x3117, ('c', '4'): 0x3118, ('s', '4'): 0x3119, ('a', '4'): 0x311a, ('o', '4'): 0x311b, ('e', '4'): 0x311c, ('a', 'i'): 0x311e, ('e', 'i'): 0x311f, ('a', 'u'): 0x3120, ('o', 'u'): 0x3121, ('a', 'n'): 0x3122, ('e', 'n'): 0x3123, ('a', 'N'): 0x3124, ('e', 'N'): 0x3125, ('e', 'r'): 0x3126, ('i', '4'): 0x3127, ('u', '4'): 0x3128, ('i', 'u'): 0x3129, ('v', '4'): 0x312a, ('n', 'G'): 0x312b, ('g', 'n'): 0x312c, ('1', 'c'): 0x3220, ('2', 'c'): 0x3221, ('3', 'c'): 0x3222, ('4', 'c'): 0x3223, ('5', 'c'): 0x3224, ('6', 'c'): 0x3225, ('7', 'c'): 0x3226, ('8', 'c'): 0x3227, ('9', 'c'): 0x3228, # code points 0xe000 - 0xefff excluded, they have no assigned # characters, only used in proposals. ('f', 'f'): 0xfb00, ('f', 'i'): 0xfb01, ('f', 'l'): 0xfb02, ('f', 't'): 0xfb05, ('s', 't'): 0xfb06, # Vim 5.x compatible digraphs that don't conflict with the above ('~', '!'): 161, ('c', '|'): 162, ('$', '$'): 163, ('o', 'x'): 164, # currency symbol in ISO 8859-1 ('Y', '-'): 165, ('|', '|'): 166, ('c', 'O'): 169, ('-', ','): 172, ('-', '='): 175, ('~', 'o'): 176, ('2', '2'): 178, ('3', '3'): 179, ('p', 'p'): 182, ('~', '.'): 183, ('1', '1'): 185, ('~', '?'): 191, ('A', '`'): 192, ('A', '^'): 194, ('A', '~'): 195, ('A', '"'): 196, ('A', '@'): 197, ('E', '`'): 200, ('E', '^'): 202, ('E', '"'): 203, ('I', '`'): 204, ('I', '^'): 206, ('I', '"'): 207, ('N', '~'): 209, ('O', '`'): 210, ('O', '^'): 212, ('O', '~'): 213, ('/', '\\'): 215, # multiplication symbol in ISO 8859-1 ('U', '`'): 217, ('U', '^'): 219, ('I', 'p'): 222, ('a', '`'): 224, ('a', '^'): 226, ('a', '~'): 227, ('a', '"'): 228, ('a', '@'): 229, ('e', '`'): 232, ('e', '^'): 234, ('e', '"'): 235, ('i', '`'): 236, ('i', '^'): 238, ('n', '~'): 241, ('o', '`'): 242, ('o', '^'): 244, ('o', '~'): 245, ('u', '`'): 249, ('u', '^'): 251, ('y', '"'): 255, } prompt_toolkit-1.0.15/prompt_toolkit/key_binding/bindings/0000775000175000017500000000000013136335632025466 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/key_binding/bindings/basic.py0000664000175000017500000003202313136130420027105 0ustar jonathanjonathan00000000000000# pylint: disable=function-redefined from __future__ import unicode_literals from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.filters import HasSelection, Condition, EmacsInsertMode, ViInsertMode from prompt_toolkit.keys import Keys from prompt_toolkit.layout.screen import Point from prompt_toolkit.mouse_events import MouseEventType, MouseEvent from prompt_toolkit.renderer import HeightIsUnknownError from prompt_toolkit.utils import suspend_to_background_supported, is_windows from .named_commands import get_by_name from ..registry import Registry __all__ = ( 'load_basic_bindings', 'load_abort_and_exit_bindings', 'load_basic_system_bindings', 'load_auto_suggestion_bindings', ) def if_no_repeat(event): """ Callable that returns True when the previous event was delivered to another handler. """ return not event.is_repeat def load_basic_bindings(): registry = Registry() insert_mode = ViInsertMode() | EmacsInsertMode() handle = registry.add_binding has_selection = 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.ShiftUp) @handle(Keys.ShiftDown) @handle(Keys.ShiftRight) @handle(Keys.ShiftLeft) @handle(Keys.Home) @handle(Keys.End) @handle(Keys.Delete) @handle(Keys.ShiftDelete) @handle(Keys.ControlDelete) @handle(Keys.PageUp) @handle(Keys.PageDown) @handle(Keys.BackTab) @handle(Keys.Tab) @handle(Keys.ControlLeft) @handle(Keys.ControlRight) @handle(Keys.ControlUp) @handle(Keys.ControlDown) @handle(Keys.Insert) @handle(Keys.Ignore) 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 # Readline-style bindings. handle(Keys.Home)(get_by_name('beginning-of-line')) handle(Keys.End)(get_by_name('end-of-line')) handle(Keys.Left)(get_by_name('backward-char')) handle(Keys.Right)(get_by_name('forward-char')) handle(Keys.ControlUp)(get_by_name('previous-history')) handle(Keys.ControlDown)(get_by_name('next-history')) handle(Keys.ControlL)(get_by_name('clear-screen')) handle(Keys.ControlK, filter=insert_mode)(get_by_name('kill-line')) handle(Keys.ControlU, filter=insert_mode)(get_by_name('unix-line-discard')) handle(Keys.ControlH, filter=insert_mode, save_before=if_no_repeat)( get_by_name('backward-delete-char')) handle(Keys.Backspace, filter=insert_mode, save_before=if_no_repeat)( get_by_name('backward-delete-char')) handle(Keys.Delete, filter=insert_mode, save_before=if_no_repeat)( get_by_name('delete-char')) handle(Keys.ShiftDelete, filter=insert_mode, save_before=if_no_repeat)( get_by_name('delete-char')) handle(Keys.Any, filter=insert_mode, save_before=if_no_repeat)( get_by_name('self-insert')) handle(Keys.ControlT, filter=insert_mode)(get_by_name('transpose-chars')) handle(Keys.ControlW, filter=insert_mode)(get_by_name('unix-word-rubout')) handle(Keys.ControlI, filter=insert_mode)(get_by_name('menu-complete')) handle(Keys.BackTab, filter=insert_mode)(get_by_name('menu-complete-backward')) handle(Keys.PageUp, filter= ~has_selection)(get_by_name('previous-history')) handle(Keys.PageDown, filter= ~has_selection)(get_by_name('next-history')) # CTRL keys. text_before_cursor = Condition(lambda cli: cli.current_buffer.text) handle(Keys.ControlD, filter=text_before_cursor & insert_mode)(get_by_name('delete-char')) is_multiline = Condition(lambda cli: cli.current_buffer.is_multiline()) is_returnable = Condition(lambda cli: cli.current_buffer.accept_action.is_returnable) @handle(Keys.ControlJ, filter=is_multiline & insert_mode) def _(event): " Newline (in case of multiline input. " event.current_buffer.newline(copy_margin=not event.cli.in_paste_mode) @handle(Keys.ControlJ, filter=~is_multiline & is_returnable) def _(event): " Enter, accept input. " buff = event.current_buffer buff.accept_action.validate_and_handle(event.cli, buff) # Delete the word before the cursor. @handle(Keys.Up) def _(event): event.current_buffer.auto_up(count=event.arg) @handle(Keys.Down) def _(event): event.current_buffer.auto_down(count=event.arg) @handle(Keys.Delete, filter=has_selection) def _(event): data = event.current_buffer.cut_selection() event.cli.clipboard.set_data(data) # Global bindings. @handle(Keys.ControlZ) def _(event): """ By default, control-Z should literally insert Ctrl-Z. (Ansi Ctrl-Z, code 26 in MSDOS means End-Of-File. In a Python REPL for instance, it's possible to type Control-Z followed by enter to quit.) When the system bindings are loaded and suspend-to-background is supported, that will override this binding. """ event.current_buffer.insert_text(event.data) @handle(Keys.CPRResponse, save_before=lambda e: False) 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.BracketedPaste) def _(event): " Pasting from clipboard. " data = event.data # Be sure to use \n as line ending. # Some terminals (Like iTerm2) seem to paste \r\n line endings in a # bracketed paste. See: https://github.com/ipython/ipython/issues/9737 data = data.replace('\r\n', '\n') data = data.replace('\r', '\n') event.current_buffer.insert_text(data) @handle(Keys.Any, filter=Condition(lambda cli: cli.quoted_insert), eager=True) def _(event): """ Handle quoted insert. """ event.current_buffer.insert_text(event.data, overwrite=False) event.cli.quoted_insert = False return registry def load_mouse_bindings(): """ Key bindings, required for mouse support. (Mouse events enter through the key binding system.) """ registry = Registry() @registry.add_binding(Keys.Vt100MouseEvent) def _(event): """ Handling of incoming mouse event. """ # Typical: "Esc[MaB*" # Urxvt: "Esc[96;14;13M" # Xterm SGR: "Esc[<64;85;12M" # Parse incoming packet. if event.data[2] == 'M': # Typical. mouse_event, x, y = map(ord, event.data[3:]) mouse_event = { 32: MouseEventType.MOUSE_DOWN, 35: MouseEventType.MOUSE_UP, 96: MouseEventType.SCROLL_UP, 97: MouseEventType.SCROLL_DOWN, }.get(mouse_event) # Handle situations where `PosixStdinReader` used surrogateescapes. if x >= 0xdc00: x-= 0xdc00 if y >= 0xdc00: y-= 0xdc00 x -= 32 y -= 32 else: # Urxvt and Xterm SGR. # When the '<' is not present, we are not using the Xterm SGR mode, # but Urxvt instead. data = event.data[2:] if data[:1] == '<': sgr = True data = data[1:] else: sgr = False # Extract coordinates. mouse_event, x, y = map(int, data[:-1].split(';')) m = data[-1] # Parse event type. if sgr: mouse_event = { (0, 'M'): MouseEventType.MOUSE_DOWN, (0, 'm'): MouseEventType.MOUSE_UP, (64, 'M'): MouseEventType.SCROLL_UP, (65, 'M'): MouseEventType.SCROLL_DOWN, }.get((mouse_event, m)) else: mouse_event = { 32: MouseEventType.MOUSE_DOWN, 35: MouseEventType.MOUSE_UP, 96: MouseEventType.SCROLL_UP, 97: MouseEventType.SCROLL_DOWN, }.get(mouse_event) x -= 1 y -= 1 # Only handle mouse events when we know the window height. if event.cli.renderer.height_is_known and mouse_event is not None: # Take region above the layout into account. The reported # coordinates are absolute to the visible part of the terminal. try: y -= event.cli.renderer.rows_above_layout except HeightIsUnknownError: return # Call the mouse handler from the renderer. handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y] handler(event.cli, MouseEvent(position=Point(x=x, y=y), event_type=mouse_event)) @registry.add_binding(Keys.WindowsMouseEvent) def _(event): """ Handling of mouse events for Windows. """ assert is_windows() # This key binding should only exist for Windows. # Parse data. event_type, x, y = event.data.split(';') x = int(x) y = int(y) # Make coordinates absolute to the visible part of the terminal. screen_buffer_info = event.cli.renderer.output.get_win32_screen_buffer_info() rows_above_cursor = screen_buffer_info.dwCursorPosition.Y - event.cli.renderer._cursor_pos.y y -= rows_above_cursor # Call the mouse event handler. handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y] handler(event.cli, MouseEvent(position=Point(x=x, y=y), event_type=event_type)) return registry def load_abort_and_exit_bindings(): """ Basic bindings for abort (Ctrl-C) and exit (Ctrl-D). """ registry = Registry() handle = registry.add_binding @handle(Keys.ControlC) def _(event): " Abort when Control-C has been pressed. " event.cli.abort() @Condition def ctrl_d_condition(cli): """ Ctrl-D binding is only active when the default buffer is selected and empty. """ return (cli.current_buffer_name == DEFAULT_BUFFER and not cli.current_buffer.text) handle(Keys.ControlD, filter=ctrl_d_condition)(get_by_name('end-of-file')) return registry def load_basic_system_bindings(): """ Basic system bindings (For both Emacs and Vi mode.) """ registry = Registry() suspend_supported = Condition( lambda cli: suspend_to_background_supported()) @registry.add_binding(Keys.ControlZ, filter=suspend_supported) def _(event): """ Suspend process to background. """ event.cli.suspend_to_background() return registry def load_auto_suggestion_bindings(): """ Key bindings for accepting auto suggestion text. """ registry = Registry() handle = registry.add_binding suggestion_available = Condition( lambda cli: cli.current_buffer.suggestion is not None and cli.current_buffer.document.is_cursor_at_the_end) @handle(Keys.ControlF, filter=suggestion_available) @handle(Keys.ControlE, filter=suggestion_available) @handle(Keys.Right, filter=suggestion_available) def _(event): " Accept suggestion. " b = event.current_buffer suggestion = b.suggestion if suggestion: b.insert_text(suggestion.text) return registry prompt_toolkit-1.0.15/prompt_toolkit/key_binding/bindings/emacs.py0000664000175000017500000003624713136130420027130 0ustar jonathanjonathan00000000000000# pylint: disable=function-redefined from __future__ import unicode_literals from prompt_toolkit.buffer import SelectionType, indent, unindent from prompt_toolkit.keys import Keys from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYSTEM_BUFFER from prompt_toolkit.filters import Condition, EmacsMode, HasSelection, EmacsInsertMode, HasFocus, HasArg from prompt_toolkit.completion import CompleteEvent from .scroll import scroll_page_up, scroll_page_down from .named_commands import get_by_name from ..registry import Registry, ConditionalRegistry __all__ = ( 'load_emacs_bindings', 'load_emacs_search_bindings', 'load_emacs_system_bindings', 'load_extra_emacs_page_navigation_bindings', ) def load_emacs_bindings(): """ Some e-macs extensions. """ # Overview of Readline emacs commands: # http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf registry = ConditionalRegistry(Registry(), EmacsMode()) handle = registry.add_binding insert_mode = EmacsInsertMode() has_selection = 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)(get_by_name('beginning-of-line')) handle(Keys.ControlB)(get_by_name('backward-char')) handle(Keys.ControlDelete, filter=insert_mode)(get_by_name('kill-word')) handle(Keys.ControlE)(get_by_name('end-of-line')) handle(Keys.ControlF)(get_by_name('forward-char')) handle(Keys.ControlLeft)(get_by_name('backward-word')) handle(Keys.ControlRight)(get_by_name('forward-word')) handle(Keys.ControlX, 'r', 'y', filter=insert_mode)(get_by_name('yank')) handle(Keys.ControlY, filter=insert_mode)(get_by_name('yank')) handle(Keys.Escape, 'b')(get_by_name('backward-word')) handle(Keys.Escape, 'c', filter=insert_mode)(get_by_name('capitalize-word')) handle(Keys.Escape, 'd', filter=insert_mode)(get_by_name('kill-word')) handle(Keys.Escape, 'f')(get_by_name('forward-word')) handle(Keys.Escape, 'l', filter=insert_mode)(get_by_name('downcase-word')) handle(Keys.Escape, 'u', filter=insert_mode)(get_by_name('uppercase-word')) handle(Keys.Escape, 'y', filter=insert_mode)(get_by_name('yank-pop')) handle(Keys.Escape, Keys.ControlH, filter=insert_mode)(get_by_name('backward-kill-word')) handle(Keys.Escape, Keys.Backspace, filter=insert_mode)(get_by_name('backward-kill-word')) handle(Keys.Escape, '\\', filter=insert_mode)(get_by_name('delete-horizontal-space')) handle(Keys.ControlUnderscore, save_before=(lambda e: False), filter=insert_mode)( get_by_name('undo')) handle(Keys.ControlX, Keys.ControlU, save_before=(lambda e: False), filter=insert_mode)( get_by_name('undo')) handle(Keys.Escape, '<', filter= ~has_selection)(get_by_name('beginning-of-history')) handle(Keys.Escape, '>', filter= ~has_selection)(get_by_name('end-of-history')) handle(Keys.Escape, '.', filter=insert_mode)(get_by_name('yank-last-arg')) handle(Keys.Escape, '_', filter=insert_mode)(get_by_name('yank-last-arg')) handle(Keys.Escape, Keys.ControlY, filter=insert_mode)(get_by_name('yank-nth-arg')) handle(Keys.Escape, '#', filter=insert_mode)(get_by_name('insert-comment')) handle(Keys.ControlO)(get_by_name('operate-and-get-next')) # ControlQ does a quoted insert. Not that for vt100 terminals, you have to # disable flow control by running ``stty -ixon``, otherwise Ctrl-Q and # Ctrl-S are captured by the terminal. handle(Keys.ControlQ, filter= ~has_selection)(get_by_name('quoted-insert')) handle(Keys.ControlX, '(')(get_by_name('start-kbd-macro')) handle(Keys.ControlX, ')')(get_by_name('end-kbd-macro')) handle(Keys.ControlX, 'e')(get_by_name('call-last-kbd-macro')) @handle(Keys.ControlN) def _(event): " Next line. " event.current_buffer.auto_down() @handle(Keys.ControlP) def _(event): " Previous line. " event.current_buffer.auto_up(count=event.arg) def handle_digit(c): """ Handle input of arguments. The first number needs to be preceeded by escape. """ @handle(c, filter=HasArg()) @handle(Keys.Escape, c) def _(event): event.append_to_arg_count(c) for c in '0123456789': handle_digit(c) @handle(Keys.Escape, '-', filter=~HasArg()) def _(event): """ """ if event._arg is None: event.append_to_arg_count('-') @handle('-', filter=Condition(lambda cli: cli.input_processor.arg == '-')) def _(event): """ When '-' is typed again, after exactly '-' has been given as an argument, ignore this. """ event.cli.input_processor.arg = '-' is_returnable = Condition( lambda cli: cli.current_buffer.accept_action.is_returnable) # Meta + Newline: always accept input. handle(Keys.Escape, Keys.ControlJ, filter=insert_mode & is_returnable)( get_by_name('accept-line')) def character_search(buff, char, count): if count < 0: match = buff.document.find_backwards(char, in_current_line=True, count=-count) else: match = buff.document.find(char, in_current_line=True, count=count) if match is not None: buff.cursor_position += match @handle(Keys.ControlSquareClose, Keys.Any) def _(event): " When Ctl-] + a character is pressed. go to that character. " # Also named 'character-search' character_search(event.current_buffer, event.data, event.arg) @handle(Keys.Escape, Keys.ControlSquareClose, Keys.Any) def _(event): " Like Ctl-], but backwards. " # Also named 'character-search-backward' character_search(event.current_buffer, event.data, -event.arg) @handle(Keys.Escape, 'a') def _(event): " Previous sentence. " # TODO: @handle(Keys.Escape, 'e') def _(event): " Move to end of sentence. " # TODO: @handle(Keys.Escape, 't', filter=insert_mode) def _(event): """ Swap the last two words before the cursor. """ # TODO @handle(Keys.Escape, '*', filter=insert_mode) def _(event): """ `meta-*`: Insert all possible completions of the preceding text. """ buff = event.current_buffer # List all completions. complete_event = CompleteEvent(text_inserted=False, completion_requested=True) completions = list(buff.completer.get_completions(buff.document, complete_event)) # Insert them. text_to_insert = ' '.join(c.text for c in completions) buff.insert_text(text_to_insert) @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.is_cursor_at_the_end_of_line: 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 (if the current buffer is not empty). """ # Take the current cursor position as the start of this selection. buff = event.current_buffer if buff.text: buff.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) @handle(Keys.ControlX, 'r', 'k', filter=has_selection) def _(event): """ Cut selected text. """ data = event.current_buffer.cut_selection() event.cli.clipboard.set_data(data) @handle(Keys.Escape, 'w', filter=has_selection) def _(event): """ Copy selected text. """ data = event.current_buffer.copy_selection() event.cli.clipboard.set_data(data) @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.get_end_of_document_position() @handle(Keys.Escape, '/', filter=insert_mode) def _(event): """ M-/: Complete. """ b = event.current_buffer if b.complete_state: b.complete_next() else: event.cli.start_completion(select_first=True) @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_, to + 1, count=event.arg) @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_, to + 1, count=event.arg) return registry def load_emacs_open_in_editor_bindings(): """ Pressing C-X C-E will open the buffer in an external editor. """ registry = Registry() registry.add_binding(Keys.ControlX, Keys.ControlE, filter=EmacsMode() & ~HasSelection())( get_by_name('edit-and-execute-command')) return registry def load_emacs_system_bindings(): registry = ConditionalRegistry(Registry(), EmacsMode()) handle = registry.add_binding has_focus = HasFocus(SYSTEM_BUFFER) @handle(Keys.Escape, '!', filter= ~has_focus) def _(event): """ M-'!' opens the system prompt. """ event.cli.push_focus(SYSTEM_BUFFER) @handle(Keys.Escape, filter=has_focus) @handle(Keys.ControlG, filter=has_focus) @handle(Keys.ControlC, filter=has_focus) def _(event): """ Cancel system prompt. """ event.cli.buffers[SYSTEM_BUFFER].reset() event.cli.pop_focus() @handle(Keys.ControlJ, filter=has_focus) def _(event): """ Run system command. """ system_line = event.cli.buffers[SYSTEM_BUFFER] event.cli.run_system_command(system_line.text) system_line.reset(append_to_history=True) # Focus previous buffer again. event.cli.pop_focus() return registry def load_emacs_search_bindings(get_search_state=None): registry = ConditionalRegistry(Registry(), EmacsMode()) handle = registry.add_binding has_focus = HasFocus(SEARCH_BUFFER) assert get_search_state is None or callable(get_search_state) if not get_search_state: def get_search_state(cli): return cli.search_state @handle(Keys.ControlG, filter=has_focus) @handle(Keys.ControlC, 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_buffer = event.cli.buffers[SEARCH_BUFFER] search_buffer.reset() event.cli.pop_focus() @handle(Keys.ControlJ, filter=has_focus) @handle(Keys.Escape, filter=has_focus, eager=True) def _(event): """ When enter pressed in isearch, quit isearch mode. (Multiline isearch would be too complicated.) """ input_buffer = event.cli.buffers.previous(event.cli) search_buffer = event.cli.buffers[SEARCH_BUFFER] # Update search state. if search_buffer.text: get_search_state(event.cli).text = search_buffer.text # Apply search. input_buffer.apply_search(get_search_state(event.cli), include_current_position=True) # Add query to history of search line. search_buffer.append_to_history() search_buffer.reset() # Focus previous document again. event.cli.pop_focus() @handle(Keys.ControlR, filter= ~has_focus) def _(event): get_search_state(event.cli).direction = IncrementalSearchDirection.BACKWARD event.cli.push_focus(SEARCH_BUFFER) @handle(Keys.ControlS, filter= ~has_focus) def _(event): get_search_state(event.cli).direction = IncrementalSearchDirection.FORWARD event.cli.push_focus(SEARCH_BUFFER) def incremental_search(cli, direction, count=1): " Apply search, but keep search buffer focussed. " # Update search_state. search_state = get_search_state(cli) direction_changed = search_state.direction != direction search_state.text = cli.buffers[SEARCH_BUFFER].text search_state.direction = direction # Apply search to current buffer. if not direction_changed: input_buffer = cli.buffers.previous(cli) input_buffer.apply_search(search_state, include_current_position=False, count=count) @handle(Keys.ControlR, filter=has_focus) @handle(Keys.Up, filter=has_focus) def _(event): incremental_search(event.cli, IncrementalSearchDirection.BACKWARD, count=event.arg) @handle(Keys.ControlS, filter=has_focus) @handle(Keys.Down, filter=has_focus) def _(event): incremental_search(event.cli, IncrementalSearchDirection.FORWARD, count=event.arg) return registry def load_extra_emacs_page_navigation_bindings(): """ Key bindings, for scrolling up and down through pages. This are separate bindings, because GNU readline doesn't have them. """ registry = ConditionalRegistry(Registry(), EmacsMode()) handle = registry.add_binding handle(Keys.ControlV)(scroll_page_down) handle(Keys.PageDown)(scroll_page_down) handle(Keys.Escape, 'v')(scroll_page_up) handle(Keys.PageUp)(scroll_page_up) return registry prompt_toolkit-1.0.15/prompt_toolkit/key_binding/bindings/scroll.py0000664000175000017500000001311413136130420027322 0ustar jonathanjonathan00000000000000""" Key bindings, for scrolling up and down through pages. This are separate bindings, because GNU readline doesn't have them, but they are very useful for navigating through long multiline buffers, like in Vi, Emacs, etc... """ from __future__ import unicode_literals from prompt_toolkit.layout.utils import find_window_for_buffer_name from six.moves import range __all__ = ( 'scroll_forward', 'scroll_backward', 'scroll_half_page_up', 'scroll_half_page_down', 'scroll_one_line_up', 'scroll_one_line_down', ) def _current_window_for_event(event): """ Return the `Window` for the currently focussed Buffer. """ return find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) def scroll_forward(event, half=False): """ Scroll window down. """ w = _current_window_for_event(event) b = event.cli.current_buffer if w and w.render_info: info = w.render_info ui_content = info.ui_content # Height to scroll. scroll_height = info.window_height if half: scroll_height //= 2 # Calculate how many lines is equivalent to that vertical space. y = b.document.cursor_position_row + 1 height = 0 while y < ui_content.line_count: line_height = info.get_height_for_line(y) if height + line_height < scroll_height: height += line_height y += 1 else: break b.cursor_position = b.document.translate_row_col_to_index(y, 0) def scroll_backward(event, half=False): """ Scroll window up. """ w = _current_window_for_event(event) b = event.cli.current_buffer if w and w.render_info: info = w.render_info # Height to scroll. scroll_height = info.window_height if half: scroll_height //= 2 # Calculate how many lines is equivalent to that vertical space. y = max(0, b.document.cursor_position_row - 1) height = 0 while y > 0: line_height = info.get_height_for_line(y) if height + line_height < scroll_height: height += line_height y -= 1 else: break b.cursor_position = b.document.translate_row_col_to_index(y, 0) def scroll_half_page_down(event): """ Same as ControlF, but only scroll half a page. """ scroll_forward(event, half=True) def scroll_half_page_up(event): """ Same as ControlB, but only scroll half a page. """ scroll_backward(event, half=True) def scroll_one_line_down(event): """ scroll_offset += 1 """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.current_buffer if w: # When the cursor is at the top, move to the next line. (Otherwise, only scroll.) if w.render_info: info = w.render_info if w.vertical_scroll < info.content_height - info.window_height: if info.cursor_position.y <= info.configured_scroll_offsets.top: b.cursor_position += b.document.get_cursor_down_position() w.vertical_scroll += 1 def scroll_one_line_up(event): """ scroll_offset -= 1 """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.current_buffer if w: # When the cursor is at the bottom, move to the previous line. (Otherwise, only scroll.) if w.render_info: info = w.render_info if w.vertical_scroll > 0: first_line_height = info.get_height_for_line(info.first_visible_line()) cursor_up = info.cursor_position.y - (info.window_height - 1 - first_line_height - info.configured_scroll_offsets.bottom) # Move cursor up, as many steps as the height of the first line. # TODO: not entirely correct yet, in case of line wrapping and many long lines. for _ in range(max(0, cursor_up)): b.cursor_position += b.document.get_cursor_up_position() # Scroll window w.vertical_scroll -= 1 def scroll_page_down(event): """ Scroll page down. (Prefer the cursor at the top of the page, after scrolling.) """ w = _current_window_for_event(event) b = event.cli.current_buffer if w and w.render_info: # Scroll down one page. line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1) w.vertical_scroll = line_index b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) def scroll_page_up(event): """ Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.) """ w = _current_window_for_event(event) b = event.cli.current_buffer if w and w.render_info: # Put cursor at the first visible line. (But make sure that the cursor # moves at least one line up.) line_index = max(0, min(w.render_info.first_visible_line(), b.document.cursor_position_row - 1)) b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) # Set the scroll offset. We can safely set it to zero; the Window will # make sure that it scrolls at least until the cursor becomes visible. w.vertical_scroll = 0 prompt_toolkit-1.0.15/prompt_toolkit/key_binding/bindings/__init__.py0000664000175000017500000000000013136130420027551 0ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/key_binding/bindings/vi.py0000664000175000017500000020434113136130420026446 0ustar jonathanjonathan00000000000000# pylint: disable=function-redefined from __future__ import unicode_literals from prompt_toolkit.buffer import ClipboardData, indent, unindent, reshape_text from prompt_toolkit.document import Document from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYSTEM_BUFFER from prompt_toolkit.filters import Filter, Condition, HasArg, Always, IsReadOnly from prompt_toolkit.filters.cli import ViNavigationMode, ViInsertMode, ViInsertMultipleMode, ViReplaceMode, ViSelectionMode, ViWaitingForTextObjectMode, ViDigraphMode, ViMode from prompt_toolkit.key_binding.digraphs import DIGRAPHS from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode from prompt_toolkit.keys import Keys from prompt_toolkit.layout.utils import find_window_for_buffer_name from prompt_toolkit.selection import SelectionType, SelectionState, PasteMode from .scroll import scroll_forward, scroll_backward, scroll_half_page_up, scroll_half_page_down, scroll_one_line_up, scroll_one_line_down, scroll_page_up, scroll_page_down from .named_commands import get_by_name from ..registry import Registry, ConditionalRegistry, BaseRegistry import prompt_toolkit.filters as filters from six.moves import range import codecs import six import string try: from itertools import accumulate except ImportError: # < Python 3.2 def accumulate(iterable): " Super simpel 'accumulate' implementation. " total = 0 for item in iterable: total += item yield total __all__ = ( 'load_vi_bindings', 'load_vi_search_bindings', 'load_vi_system_bindings', 'load_extra_vi_page_navigation_bindings', ) if six.PY2: ascii_lowercase = string.ascii_lowercase.decode('ascii') else: ascii_lowercase = string.ascii_lowercase vi_register_names = ascii_lowercase + '0123456789' class TextObjectType(object): EXCLUSIVE = 'EXCLUSIVE' INCLUSIVE = 'INCLUSIVE' LINEWISE = 'LINEWISE' BLOCK = 'BLOCK' class TextObject(object): """ Return struct for functions wrapped in ``text_object``. Both `start` and `end` are relative to the current cursor position. """ def __init__(self, start, end=0, type=TextObjectType.EXCLUSIVE): self.start = start self.end = end self.type = type @property def selection_type(self): if self.type == TextObjectType.LINEWISE: return SelectionType.LINES if self.type == TextObjectType.BLOCK: return SelectionType.BLOCK else: return SelectionType.CHARACTERS def sorted(self): """ Return a (start, end) tuple where start <= end. """ if self.start < self.end: return self.start, self.end else: return self.end, self.start def operator_range(self, document): """ Return a (start, end) tuple with start <= end that indicates the range operators should operate on. `buffer` is used to get start and end of line positions. """ start, end = self.sorted() doc = document if (self.type == TextObjectType.EXCLUSIVE and doc.translate_index_to_position(end + doc.cursor_position)[1] == 0): # If the motion is exclusive and the end of motion is on the first # column, the end position becomes end of previous line. end -= 1 if self.type == TextObjectType.INCLUSIVE: end += 1 if self.type == TextObjectType.LINEWISE: # Select whole lines row, col = doc.translate_index_to_position(start + doc.cursor_position) start = doc.translate_row_col_to_index(row, 0) - doc.cursor_position row, col = doc.translate_index_to_position(end + doc.cursor_position) end = doc.translate_row_col_to_index(row, len(doc.lines[row])) - doc.cursor_position return start, end def get_line_numbers(self, buffer): """ Return a (start_line, end_line) pair. """ # Get absolute cursor positions from the text object. from_, to = self.operator_range(buffer.document) from_ += buffer.cursor_position to += buffer.cursor_position # Take the start of the lines. from_, _ = buffer.document.translate_index_to_position(from_) to, _ = buffer.document.translate_index_to_position(to) return from_, to def cut(self, buffer): """ Turn text object into `ClipboardData` instance. """ from_, to = self.operator_range(buffer.document) from_ += buffer.cursor_position to += buffer.cursor_position to -= 1 # SelectionState does not include the end position, `operator_range` does. document = Document(buffer.text, to, SelectionState( original_cursor_position=from_, type=self.selection_type)) new_document, clipboard_data = document.cut_selection() return new_document, clipboard_data def create_text_object_decorator(registry): """ Create a decorator that can be used to register Vi text object implementations. """ assert isinstance(registry, BaseRegistry) operator_given = ViWaitingForTextObjectMode() navigation_mode = ViNavigationMode() selection_mode = ViSelectionMode() def text_object_decorator(*keys, **kw): """ Register a text object function. Usage:: @text_object('w', filter=..., no_move_handler=False) def handler(event): # Return a text object for this key. return TextObject(...) :param no_move_handler: Disable the move handler in navigation mode. (It's still active in selection mode.) """ filter = kw.pop('filter', Always()) no_move_handler = kw.pop('no_move_handler', False) no_selection_handler = kw.pop('no_selection_handler', False) eager = kw.pop('eager', False) assert not kw def decorator(text_object_func): assert callable(text_object_func) @registry.add_binding(*keys, filter=operator_given & filter, eager=eager) def _(event): # Arguments are multiplied. vi_state = event.cli.vi_state event._arg = (vi_state.operator_arg or 1) * (event.arg or 1) # Call the text object handler. text_obj = text_object_func(event) if text_obj is not None: assert isinstance(text_obj, TextObject) # Call the operator function with the text object. vi_state.operator_func(event, text_obj) # Clear operator. event.cli.vi_state.operator_func = None event.cli.vi_state.operator_arg = None # Register a move operation. (Doesn't need an operator.) if not no_move_handler: @registry.add_binding(*keys, filter=~operator_given & filter & navigation_mode, eager=eager) def _(event): " Move handler for navigation mode. " text_object = text_object_func(event) event.current_buffer.cursor_position += text_object.start # Register a move selection operation. if not no_selection_handler: @registry.add_binding(*keys, filter=~operator_given & filter & selection_mode, eager=eager) def _(event): " Move handler for selection mode. " text_object = text_object_func(event) buff = event.current_buffer # When the text object has both a start and end position, like 'i(' or 'iw', # Turn this into a selection, otherwise the cursor. if text_object.end: # Take selection positions from text object. start, end = text_object.operator_range(buff.document) start += buff.cursor_position end += buff.cursor_position buff.selection_state.original_cursor_position = start buff.cursor_position = end # Take selection type from text object. if text_object.type == TextObjectType.LINEWISE: buff.selection_state.type = SelectionType.LINES else: buff.selection_state.type = SelectionType.CHARACTERS else: event.current_buffer.cursor_position += text_object.start # Make it possible to chain @text_object decorators. return text_object_func return decorator return text_object_decorator def create_operator_decorator(registry): """ Create a decorator that can be used for registering Vi operators. """ assert isinstance(registry, BaseRegistry) operator_given = ViWaitingForTextObjectMode() navigation_mode = ViNavigationMode() selection_mode = ViSelectionMode() def operator_decorator(*keys, **kw): """ Register a Vi operator. Usage:: @operator('d', filter=...) def handler(cli, text_object): # Do something with the text object here. """ filter = kw.pop('filter', Always()) eager = kw.pop('eager', False) assert not kw def decorator(operator_func): @registry.add_binding(*keys, filter=~operator_given & filter & navigation_mode, eager=eager) def _(event): """ Handle operator in navigation mode. """ # When this key binding is matched, only set the operator # function in the ViState. We should execute it after a text # object has been received. event.cli.vi_state.operator_func = operator_func event.cli.vi_state.operator_arg = event.arg @registry.add_binding(*keys, filter=~operator_given & filter & selection_mode, eager=eager) def _(event): """ Handle operator in selection mode. """ buff = event.current_buffer selection_state = buff.selection_state # Create text object from selection. if selection_state.type == SelectionType.LINES: text_obj_type = TextObjectType.LINEWISE elif selection_state.type == SelectionType.BLOCK: text_obj_type = TextObjectType.BLOCK else: text_obj_type = TextObjectType.INCLUSIVE text_object = TextObject( selection_state.original_cursor_position - buff.cursor_position, type=text_obj_type) # Execute operator. operator_func(event, text_object) # Quit selection mode. buff.selection_state = None return operator_func return decorator return operator_decorator def load_vi_bindings(get_search_state=None): """ Vi extensions. # Overview of Readline Vi commands: # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf :param get_search_state: None or a callable that takes a CommandLineInterface and returns a SearchState. """ # Note: Some key bindings have the "~IsReadOnly()" filter added. This # prevents the handler to be executed when the focus is on a # read-only buffer. # This is however only required for those that change the ViState to # INSERT mode. The `Buffer` class itself throws the # `EditReadOnlyBuffer` exception for any text operations which is # handled correctly. There is no need to add "~IsReadOnly" to all key # bindings that do text manipulation. registry = ConditionalRegistry(Registry(), ViMode()) handle = registry.add_binding # Default get_search_state. if get_search_state is None: def get_search_state(cli): return cli.search_state # (Note: Always take the navigation bindings in read-only mode, even when # ViState says different.) navigation_mode = ViNavigationMode() insert_mode = ViInsertMode() insert_multiple_mode = ViInsertMultipleMode() replace_mode = ViReplaceMode() selection_mode = ViSelectionMode() operator_given = ViWaitingForTextObjectMode() digraph_mode = ViDigraphMode() vi_transform_functions = [ # Rot 13 transformation (('g', '?'), Always(), lambda string: codecs.encode(string, 'rot_13')), # To lowercase (('g', 'u'), Always(), lambda string: string.lower()), # To uppercase. (('g', 'U'), Always(), lambda string: string.upper()), # Swap case. (('g', '~'), Always(), lambda string: string.swapcase()), (('~', ), Condition(lambda cli: cli.vi_state.tilde_operator), lambda string: string.swapcase()), ] # Insert a character literally (quoted insert). handle(Keys.ControlV, filter=insert_mode)(get_by_name('quoted-insert')) @handle(Keys.Escape) def _(event): """ Escape goes to vi navigation mode. """ buffer = event.current_buffer vi_state = event.cli.vi_state if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE): buffer.cursor_position += buffer.document.get_cursor_left_position() vi_state.reset(InputMode.NAVIGATION) if bool(buffer.selection_state): buffer.exit_selection() @handle('k', filter=selection_mode) def _(event): """ Arrow up in selection mode. """ event.current_buffer.cursor_up(count=event.arg) @handle('j', filter=selection_mode) def _(event): """ Arrow down in selection mode. """ event.current_buffer.cursor_down(count=event.arg) @handle(Keys.Up, filter=navigation_mode) @handle(Keys.ControlP, filter=navigation_mode) def _(event): """ Arrow up and ControlP in navigation mode go up. """ event.current_buffer.auto_up(count=event.arg) @handle('k', filter=navigation_mode) def _(event): """ Go up, but if we enter a new history entry, move to the start of the line. """ event.current_buffer.auto_up( count=event.arg, go_to_start_of_line_if_history_changes=True) @handle(Keys.Down, filter=navigation_mode) @handle(Keys.ControlN, filter=navigation_mode) def _(event): """ Arrow down and Control-N in navigation mode. """ event.current_buffer.auto_down(count=event.arg) @handle('j', filter=navigation_mode) def _(event): """ Go down, but if we enter a new history entry, go to the start of the line. """ event.current_buffer.auto_down( count=event.arg, go_to_start_of_line_if_history_changes=True) @handle(Keys.ControlH, filter=navigation_mode) @handle(Keys.Backspace, filter=navigation_mode) def _(event): """ In navigation-mode, move cursor. """ event.current_buffer.cursor_position += \ event.current_buffer.document.get_cursor_left_position(count=event.arg) @handle(Keys.ControlN, filter=insert_mode) def _(event): b = event.current_buffer if b.complete_state: b.complete_next() else: event.cli.start_completion(select_first=True) @handle(Keys.ControlP, filter=insert_mode) def _(event): """ Control-P: To previous completion. """ b = event.current_buffer if b.complete_state: b.complete_previous() else: event.cli.start_completion(select_last=True) @handle(Keys.ControlY, filter=insert_mode) def _(event): """ Accept current completion. """ event.current_buffer.complete_state = None @handle(Keys.ControlE, filter=insert_mode) def _(event): """ Cancel completion. Go back to originally typed text. """ event.current_buffer.cancel_completion() @handle(Keys.ControlJ, filter=navigation_mode) # XXX: only if the selected buffer has a return handler. def _(event): """ In navigation mode, pressing enter will always return the input. """ b = event.current_buffer if b.accept_action.is_returnable: b.accept_action.validate_and_handle(event.cli, b) # ** In navigation mode ** # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html @handle(Keys.Insert, filter=navigation_mode) def _(event): " Presing the Insert key. " event.cli.vi_state.input_mode = InputMode.INSERT @handle('a', filter=navigation_mode & ~IsReadOnly()) # ~IsReadOnly, because we want to stay in navigation mode for # read-only buffers. def _(event): event.current_buffer.cursor_position += event.current_buffer.document.get_cursor_right_position() event.cli.vi_state.input_mode = InputMode.INSERT @handle('A', filter=navigation_mode & ~IsReadOnly()) def _(event): event.current_buffer.cursor_position += event.current_buffer.document.get_end_of_line_position() event.cli.vi_state.input_mode = InputMode.INSERT @handle('C', filter=navigation_mode & ~IsReadOnly()) def _(event): """ # Change to end of line. # Same as 'c$' (which is implemented elsewhere.) """ buffer = event.current_buffer deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) event.cli.clipboard.set_text(deleted) event.cli.vi_state.input_mode = InputMode.INSERT @handle('c', 'c', filter=navigation_mode & ~IsReadOnly()) @handle('S', filter=navigation_mode & ~IsReadOnly()) def _(event): # TODO: implement 'arg' """ Change current line """ buffer = event.current_buffer # We copy the whole line. data = ClipboardData(buffer.document.current_line, SelectionType.LINES) event.cli.clipboard.set_data(data) # But we delete after the whitespace buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) buffer.delete(count=buffer.document.get_end_of_line_position()) event.cli.vi_state.input_mode = InputMode.INSERT @handle('D', filter=navigation_mode) def _(event): buffer = event.current_buffer deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) event.cli.clipboard.set_text(deleted) @handle('d', 'd', filter=navigation_mode) def _(event): """ Delete line. (Or the following 'n' lines.) """ buffer = event.current_buffer # Split string in before/deleted/after text. lines = buffer.document.lines before = '\n'.join(lines[:buffer.document.cursor_position_row]) deleted = '\n'.join(lines[buffer.document.cursor_position_row: buffer.document.cursor_position_row + event.arg]) after = '\n'.join(lines[buffer.document.cursor_position_row + event.arg:]) # Set new text. if before and after: before = before + '\n' # Set text and cursor position. buffer.document = Document( text=before + after, # Cursor At the start of the first 'after' line, after the leading whitespace. cursor_position = len(before) + len(after) - len(after.lstrip(' '))) # Set clipboard data event.cli.clipboard.set_data(ClipboardData(deleted, SelectionType.LINES)) @handle('x', filter=selection_mode) def _(event): """ Cut selection. ('x' is not an operator.) """ clipboard_data = event.current_buffer.cut_selection() event.cli.clipboard.set_data(clipboard_data) @handle('i', filter=navigation_mode & ~IsReadOnly()) def _(event): event.cli.vi_state.input_mode = InputMode.INSERT @handle('I', filter=navigation_mode & ~IsReadOnly()) def _(event): event.cli.vi_state.input_mode = InputMode.INSERT event.current_buffer.cursor_position += \ event.current_buffer.document.get_start_of_line_position(after_whitespace=True) @Condition def in_block_selection(cli): buff = cli.current_buffer return buff.selection_state and buff.selection_state.type == SelectionType.BLOCK @handle('I', filter=in_block_selection & ~IsReadOnly()) def go_to_block_selection(event, after=False): " Insert in block selection mode. " buff = event.current_buffer # Store all cursor positions. positions = [] if after: def get_pos(from_to): return from_to[1] + 1 else: def get_pos(from_to): return from_to[0] for i, from_to in enumerate(buff.document.selection_ranges()): positions.append(get_pos(from_to)) if i == 0: buff.cursor_position = get_pos(from_to) buff.multiple_cursor_positions = positions # Go to 'INSERT_MULTIPLE' mode. event.cli.vi_state.input_mode = InputMode.INSERT_MULTIPLE buff.exit_selection() @handle('A', filter=in_block_selection & ~IsReadOnly()) def _(event): go_to_block_selection(event, after=True) @handle('J', filter=navigation_mode & ~IsReadOnly()) def _(event): " Join lines. " for i in range(event.arg): event.current_buffer.join_next_line() @handle('g', 'J', filter=navigation_mode & ~IsReadOnly()) def _(event): " Join lines without space. " for i in range(event.arg): event.current_buffer.join_next_line(separator='') @handle('J', filter=selection_mode & ~IsReadOnly()) def _(event): " Join selected lines. " event.current_buffer.join_selected_lines() @handle('g', 'J', filter=selection_mode & ~IsReadOnly()) def _(event): " Join selected lines without space. " event.current_buffer.join_selected_lines(separator='') @handle('p', filter=navigation_mode) def _(event): """ Paste after """ event.current_buffer.paste_clipboard_data( event.cli.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.VI_AFTER) @handle('P', filter=navigation_mode) def _(event): """ Paste before """ event.current_buffer.paste_clipboard_data( event.cli.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.VI_BEFORE) @handle('"', Keys.Any, 'p', filter=navigation_mode) def _(event): " Paste from named register. " c = event.key_sequence[1].data if c in vi_register_names: data = event.cli.vi_state.named_registers.get(c) if data: event.current_buffer.paste_clipboard_data( data, count=event.arg, paste_mode=PasteMode.VI_AFTER) @handle('"', Keys.Any, 'P', filter=navigation_mode) def _(event): " Paste (before) from named register. " c = event.key_sequence[1].data if c in vi_register_names: data = event.cli.vi_state.named_registers.get(c) if data: event.current_buffer.paste_clipboard_data( data, count=event.arg, paste_mode=PasteMode.VI_BEFORE) @handle('r', Keys.Any, filter=navigation_mode) def _(event): """ Replace single character under cursor """ event.current_buffer.insert_text(event.data * event.arg, overwrite=True) event.current_buffer.cursor_position -= 1 @handle('R', filter=navigation_mode) def _(event): """ Go to 'replace'-mode. """ event.cli.vi_state.input_mode = InputMode.REPLACE @handle('s', filter=navigation_mode & ~IsReadOnly()) def _(event): """ Substitute with new text (Delete character(s) and go to insert mode.) """ text = event.current_buffer.delete(count=event.arg) event.cli.clipboard.set_text(text) event.cli.vi_state.input_mode = InputMode.INSERT @handle('u', filter=navigation_mode, save_before=(lambda e: False)) def _(event): for i in range(event.arg): event.current_buffer.undo() @handle('V', filter=navigation_mode) def _(event): """ Start lines selection. """ event.current_buffer.start_selection(selection_type=SelectionType.LINES) @handle(Keys.ControlV, filter=navigation_mode) def _(event): " Enter block selection mode. " event.current_buffer.start_selection(selection_type=SelectionType.BLOCK) @handle('V', filter=selection_mode) def _(event): """ Exit line selection mode, or go from non line selection mode to line selection mode. """ selection_state = event.current_buffer.selection_state if selection_state.type != SelectionType.LINES: selection_state.type = SelectionType.LINES else: event.current_buffer.exit_selection() @handle('v', filter=navigation_mode) def _(event): " Enter character selection mode. " event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS) @handle('v', filter=selection_mode) def _(event): """ Exit character selection mode, or go from non-character-selection mode to character selection mode. """ selection_state = event.current_buffer.selection_state if selection_state.type != SelectionType.CHARACTERS: selection_state.type = SelectionType.CHARACTERS else: event.current_buffer.exit_selection() @handle(Keys.ControlV, filter=selection_mode) def _(event): """ Exit block selection mode, or go from non block selection mode to block selection mode. """ selection_state = event.current_buffer.selection_state if selection_state.type != SelectionType.BLOCK: selection_state.type = SelectionType.BLOCK else: event.current_buffer.exit_selection() @handle('a', 'w', filter=selection_mode) @handle('a', 'W', filter=selection_mode) def _(event): """ Switch from visual linewise mode to visual characterwise mode. """ buffer = event.current_buffer if buffer.selection_state and buffer.selection_state.type == SelectionType.LINES: buffer.selection_state.type = SelectionType.CHARACTERS @handle('x', filter=navigation_mode) def _(event): """ Delete character. """ text = event.current_buffer.delete(count=event.arg) event.cli.clipboard.set_text(text) @handle('X', filter=navigation_mode) def _(event): text = event.current_buffer.delete_before_cursor() event.cli.clipboard.set_text(text) @handle('y', 'y', filter=navigation_mode) @handle('Y', filter=navigation_mode) def _(event): """ Yank the whole line. """ text = '\n'.join(event.current_buffer.document.lines_from_current[:event.arg]) event.cli.clipboard.set_data(ClipboardData(text, SelectionType.LINES)) @handle('+', filter=navigation_mode) def _(event): """ Move to first non whitespace of next line """ buffer = event.current_buffer buffer.cursor_position += buffer.document.get_cursor_down_position(count=event.arg) buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) @handle('-', filter=navigation_mode) def _(event): """ Move to first non whitespace of previous line """ buffer = event.current_buffer buffer.cursor_position += buffer.document.get_cursor_up_position(count=event.arg) buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) @handle('>', '>', filter=navigation_mode) def _(event): """ Indent lines. """ buffer = event.current_buffer current_row = buffer.document.cursor_position_row indent(buffer, current_row, current_row + event.arg) @handle('<', '<', filter=navigation_mode) def _(event): """ Unindent lines. """ current_row = event.current_buffer.document.cursor_position_row unindent(event.current_buffer, current_row, current_row + event.arg) @handle('O', filter=navigation_mode & ~IsReadOnly()) def _(event): """ Open line above and enter insertion mode """ event.current_buffer.insert_line_above( copy_margin=not event.cli.in_paste_mode) event.cli.vi_state.input_mode = InputMode.INSERT @handle('o', filter=navigation_mode & ~IsReadOnly()) def _(event): """ Open line below and enter insertion mode """ event.current_buffer.insert_line_below( copy_margin=not event.cli.in_paste_mode) event.cli.vi_state.input_mode = InputMode.INSERT @handle('~', filter=navigation_mode) def _(event): """ Reverse case of current character and move cursor forward. """ buffer = event.current_buffer c = buffer.document.current_char if c is not None and c != '\n': buffer.insert_text(c.swapcase(), overwrite=True) @handle('g', 'u', 'u', filter=navigation_mode & ~IsReadOnly()) def _(event): " Lowercase current line. " buff = event.current_buffer buff.transform_current_line(lambda s: s.lower()) @handle('g', 'U', 'U', filter=navigation_mode & ~IsReadOnly()) def _(event): " Uppercase current line. " buff = event.current_buffer buff.transform_current_line(lambda s: s.upper()) @handle('g', '~', '~', filter=navigation_mode & ~IsReadOnly()) def _(event): " Swap case of the current line. " buff = event.current_buffer buff.transform_current_line(lambda s: s.swapcase()) @handle('#', filter=navigation_mode) def _(event): """ Go to previous occurence of this word. """ b = event.cli.current_buffer search_state = get_search_state(event.cli) search_state.text = b.document.get_word_under_cursor() search_state.direction = IncrementalSearchDirection.BACKWARD b.apply_search(search_state, count=event.arg, include_current_position=False) @handle('*', filter=navigation_mode) def _(event): """ Go to next occurence of this word. """ b = event.cli.current_buffer search_state = get_search_state(event.cli) search_state.text = b.document.get_word_under_cursor() search_state.direction = IncrementalSearchDirection.FORWARD b.apply_search(search_state, count=event.arg, include_current_position=False) @handle('(', filter=navigation_mode) def _(event): # TODO: go to begin of sentence. # XXX: should become text_object. pass @handle(')', filter=navigation_mode) def _(event): # TODO: go to end of sentence. # XXX: should become text_object. pass operator = create_operator_decorator(registry) text_object = create_text_object_decorator(registry) @text_object(Keys.Any, filter=operator_given) def _(event): """ Unknown key binding while waiting for a text object. """ event.cli.output.bell() # # *** Operators *** # def create_delete_and_change_operators(delete_only, with_register=False): """ Delete and change operators. :param delete_only: Create an operator that deletes, but doesn't go to insert mode. :param with_register: Copy the deleted text to this named register instead of the clipboard. """ if with_register: handler_keys = ('"', Keys.Any, 'cd'[delete_only]) else: handler_keys = 'cd'[delete_only] @operator(*handler_keys, filter=~IsReadOnly()) def delete_or_change_operator(event, text_object): clipboard_data = None buff = event.current_buffer if text_object: new_document, clipboard_data = text_object.cut(buff) buff.document = new_document # Set deleted/changed text to clipboard or named register. if clipboard_data and clipboard_data.text: if with_register: reg_name = event.key_sequence[1].data if reg_name in vi_register_names: event.cli.vi_state.named_registers[reg_name] = clipboard_data else: event.cli.clipboard.set_data(clipboard_data) # Only go back to insert mode in case of 'change'. if not delete_only: event.cli.vi_state.input_mode = InputMode.INSERT create_delete_and_change_operators(False, False) create_delete_and_change_operators(False, True) create_delete_and_change_operators(True, False) create_delete_and_change_operators(True, True) def create_transform_handler(filter, transform_func, *a): @operator(*a, filter=filter & ~IsReadOnly()) def _(event, text_object): """ Apply transformation (uppercase, lowercase, rot13, swap case). """ buff = event.current_buffer start, end = text_object.operator_range(buff.document) if start < end: # Transform. buff.transform_region( buff.cursor_position + start, buff.cursor_position + end, transform_func) # Move cursor buff.cursor_position += (text_object.end or text_object.start) for k, f, func in vi_transform_functions: create_transform_handler(f, func, *k) @operator('y') def yank_handler(event, text_object): """ Yank operator. (Copy text.) """ _, clipboard_data = text_object.cut(event.current_buffer) if clipboard_data.text: event.cli.clipboard.set_data(clipboard_data) @operator('"', Keys.Any, 'y') def _(event, text_object): " Yank selection to named register. " c = event.key_sequence[1].data if c in vi_register_names: _, clipboard_data = text_object.cut(event.current_buffer) event.cli.vi_state.named_registers[c] = clipboard_data @operator('>') def _(event, text_object): """ Indent. """ buff = event.current_buffer from_, to = text_object.get_line_numbers(buff) indent(buff, from_, to + 1, count=event.arg) @operator('<') def _(event, text_object): """ Unindent. """ buff = event.current_buffer from_, to = text_object.get_line_numbers(buff) unindent(buff, from_, to + 1, count=event.arg) @operator('g', 'q') def _(event, text_object): """ Reshape text. """ buff = event.current_buffer from_, to = text_object.get_line_numbers(buff) reshape_text(buff, from_, to) # # *** Text objects *** # @text_object('b') def _(event): """ Move one word or token left. """ return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg) or 0) @text_object('B') def _(event): """ Move one non-blank word left """ return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg, WORD=True) or 0) @text_object('$') def key_dollar(event): """ 'c$', 'd$' and '$': Delete/change/move until end of line. """ return TextObject(event.current_buffer.document.get_end_of_line_position()) @text_object('w') def _(event): """ 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. """ return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg) or event.current_buffer.document.get_end_of_document_position()) @text_object('W') def _(event): """ 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. """ return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg, WORD=True) or event.current_buffer.document.get_end_of_document_position()) @text_object('e') def _(event): """ End of 'word': 'ce', 'de', 'e' """ end = event.current_buffer.document.find_next_word_ending(count=event.arg) return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) @text_object('E') def _(event): """ End of 'WORD': 'cE', 'dE', 'E' """ end = event.current_buffer.document.find_next_word_ending(count=event.arg, WORD=True) return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) @text_object('i', 'w', no_move_handler=True) def _(event): """ Inner 'word': ciw and diw """ start, end = event.current_buffer.document.find_boundaries_of_current_word() return TextObject(start, end) @text_object('a', 'w', no_move_handler=True) def _(event): """ A 'word': caw and daw """ start, end = event.current_buffer.document.find_boundaries_of_current_word(include_trailing_whitespace=True) return TextObject(start, end) @text_object('i', 'W', no_move_handler=True) def _(event): """ Inner 'WORD': ciW and diW """ start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True) return TextObject(start, end) @text_object('a', 'W', no_move_handler=True) def _(event): """ A 'WORD': caw and daw """ start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True, include_trailing_whitespace=True) return TextObject(start, end) @text_object('a', 'p', no_move_handler=True) def _(event): """ Auto paragraph. """ start = event.current_buffer.document.start_of_paragraph() end = event.current_buffer.document.end_of_paragraph(count=event.arg) return TextObject(start, end) @text_object('^') def key_circumflex(event): """ 'c^', 'd^' and '^': Soft start of line, after whitespace. """ return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=True)) @text_object('0') def key_zero(event): """ 'c0', 'd0': Hard start of line, before whitespace. (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.) """ return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=False)) def create_ci_ca_handles(ci_start, ci_end, inner, key=None): # TODO: 'dat', 'dit', (tags (like xml) """ Delete/Change string between this start and stop character. But keep these characters. This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations. """ def handler(event): if ci_start == ci_end: # Quotes start = event.current_buffer.document.find_backwards(ci_start, in_current_line=False) end = event.current_buffer.document.find(ci_end, in_current_line=False) else: # Brackets start = event.current_buffer.document.find_enclosing_bracket_left(ci_start, ci_end) end = event.current_buffer.document.find_enclosing_bracket_right(ci_start, ci_end) if start is not None and end is not None: offset = 0 if inner else 1 return TextObject(start + 1 - offset, end + offset) else: # Nothing found. return TextObject(0) if key is None: text_object('ai'[inner], ci_start, no_move_handler=True)(handler) text_object('ai'[inner], ci_end, no_move_handler=True)(handler) else: text_object('ai'[inner], key, no_move_handler=True)(handler) for inner in (False, True): for ci_start, ci_end in [('"', '"'), ("'", "'"), ("`", "`"), ('[', ']'), ('<', '>'), ('{', '}'), ('(', ')')]: create_ci_ca_handles(ci_start, ci_end, inner) create_ci_ca_handles('(', ')', inner, 'b') # 'dab', 'dib' create_ci_ca_handles('{', '}', inner, 'B') # 'daB', 'diB' @text_object('{') def _(event): """ Move to previous blank-line separated section. Implements '{', 'c{', 'd{', 'y{' """ index = event.current_buffer.document.start_of_paragraph( count=event.arg, before=True) return TextObject(index) @text_object('}') def _(event): """ Move to next blank-line separated section. Implements '}', 'c}', 'd}', 'y}' """ index = event.current_buffer.document.end_of_paragraph(count=event.arg, after=True) return TextObject(index) @text_object('f', Keys.Any) def _(event): """ Go to next occurance of character. Typing 'fx' will move the cursor to the next occurance of character. 'x'. """ event.cli.vi_state.last_character_find = CharacterFind(event.data, False) match = event.current_buffer.document.find( event.data, in_current_line=True, count=event.arg) if match: return TextObject(match, type=TextObjectType.INCLUSIVE) else: return TextObject(0) @text_object('F', Keys.Any) def _(event): """ Go to previous occurance of character. Typing 'Fx' will move the cursor to the previous occurance of character. 'x'. """ event.cli.vi_state.last_character_find = CharacterFind(event.data, True) return TextObject(event.current_buffer.document.find_backwards( event.data, in_current_line=True, count=event.arg) or 0) @text_object('t', Keys.Any) def _(event): """ Move right to the next occurance of c, then one char backward. """ event.cli.vi_state.last_character_find = CharacterFind(event.data, False) match = event.current_buffer.document.find( event.data, in_current_line=True, count=event.arg) if match: return TextObject(match - 1, type=TextObjectType.INCLUSIVE) else: return TextObject(0) @text_object('T', Keys.Any) def _(event): """ Move left to the previous occurance of c, then one char forward. """ event.cli.vi_state.last_character_find = CharacterFind(event.data, True) match = event.current_buffer.document.find_backwards( event.data, in_current_line=True, count=event.arg) return TextObject(match + 1 if match else 0) def repeat(reverse): """ Create ',' and ';' commands. """ @text_object(',' if reverse else ';') def _(event): # Repeat the last 'f'/'F'/'t'/'T' command. pos = 0 vi_state = event.cli.vi_state type = TextObjectType.EXCLUSIVE if vi_state.last_character_find: char = vi_state.last_character_find.character backwards = vi_state.last_character_find.backwards if reverse: backwards = not backwards if backwards: pos = event.current_buffer.document.find_backwards(char, in_current_line=True, count=event.arg) else: pos = event.current_buffer.document.find(char, in_current_line=True, count=event.arg) type = TextObjectType.INCLUSIVE if pos: return TextObject(pos, type=type) else: return TextObject(0) repeat(True) repeat(False) @text_object('h') @text_object(Keys.Left) def _(event): """ Implements 'ch', 'dh', 'h': Cursor left. """ return TextObject(event.current_buffer.document.get_cursor_left_position(count=event.arg)) @text_object('j', no_move_handler=True, no_selection_handler=True) # Note: We also need `no_selection_handler`, because we in # selection mode, we prefer the other 'j' binding that keeps # `buffer.preferred_column`. def _(event): """ Implements 'cj', 'dj', 'j', ... Cursor up. """ return TextObject(event.current_buffer.document.get_cursor_down_position(count=event.arg), type=TextObjectType.LINEWISE) @text_object('k', no_move_handler=True, no_selection_handler=True) def _(event): """ Implements 'ck', 'dk', 'k', ... Cursor up. """ return TextObject(event.current_buffer.document.get_cursor_up_position(count=event.arg), type=TextObjectType.LINEWISE) @text_object('l') @text_object(' ') @text_object(Keys.Right) def _(event): """ Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. """ return TextObject(event.current_buffer.document.get_cursor_right_position(count=event.arg)) @text_object('H') def _(event): """ Moves to the start of the visible region. (Below the scroll offset.) Implements 'cH', 'dH', 'H'. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.current_buffer if w and w.render_info: # When we find a Window that has BufferControl showing this window, # move to the start of the visible area. pos = (b.document.translate_row_col_to_index( w.render_info.first_visible_line(after_scroll_offset=True), 0) - b.cursor_position) else: # Otherwise, move to the start of the input. pos = -len(b.document.text_before_cursor) return TextObject(pos, type=TextObjectType.LINEWISE) @text_object('M') def _(event): """ Moves cursor to the vertical center of the visible region. Implements 'cM', 'dM', 'M'. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.current_buffer if w and w.render_info: # When we find a Window that has BufferControl showing this window, # move to the center of the visible area. pos = (b.document.translate_row_col_to_index( w.render_info.center_visible_line(), 0) - b.cursor_position) else: # Otherwise, move to the start of the input. pos = -len(b.document.text_before_cursor) return TextObject(pos, type=TextObjectType.LINEWISE) @text_object('L') def _(event): """ Moves to the end of the visible region. (Above the scroll offset.) """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.current_buffer if w and w.render_info: # When we find a Window that has BufferControl showing this window, # move to the end of the visible area. pos = (b.document.translate_row_col_to_index( w.render_info.last_visible_line(before_scroll_offset=True), 0) - b.cursor_position) else: # Otherwise, move to the end of the input. pos = len(b.document.text_after_cursor) return TextObject(pos, type=TextObjectType.LINEWISE) @text_object('n', no_move_handler=True) def _(event): " Search next. " buff = event.current_buffer cursor_position = buff.get_search_position( get_search_state(event.cli), include_current_position=False, count=event.arg) return TextObject(cursor_position - buff.cursor_position) @handle('n', filter=navigation_mode) def _(event): " Search next in navigation mode. (This goes through the history.) " event.current_buffer.apply_search( get_search_state(event.cli), include_current_position=False, count=event.arg) @text_object('N', no_move_handler=True) def _(event): " Search previous. " buff = event.current_buffer cursor_position = buff.get_search_position( ~get_search_state(event.cli), include_current_position=False, count=event.arg) return TextObject(cursor_position - buff.cursor_position) @handle('N', filter=navigation_mode) def _(event): " Search previous in navigation mode. (This goes through the history.) " event.current_buffer.apply_search( ~get_search_state(event.cli), include_current_position=False, count=event.arg) @handle('z', '+', filter=navigation_mode|selection_mode) @handle('z', 't', filter=navigation_mode|selection_mode) @handle('z', Keys.ControlJ, filter=navigation_mode|selection_mode) def _(event): """ Scrolls the window to makes the current line the first line in the visible region. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.current_buffer w.vertical_scroll = b.document.cursor_position_row @handle('z', '-', filter=navigation_mode|selection_mode) @handle('z', 'b', filter=navigation_mode|selection_mode) def _(event): """ Scrolls the window to makes the current line the last line in the visible region. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) # We can safely set the scroll offset to zero; the Window will meke # sure that it scrolls at least enough to make the cursor visible # again. w.vertical_scroll = 0 @handle('z', 'z', filter=navigation_mode|selection_mode) def _(event): """ Center Window vertically around cursor. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.current_buffer if w and w.render_info: info = w.render_info # Calculate the offset that we need in order to position the row # containing the cursor in the center. scroll_height = info.window_height // 2 y = max(0, b.document.cursor_position_row - 1) height = 0 while y > 0: line_height = info.get_height_for_line(y) if height + line_height < scroll_height: height += line_height y -= 1 else: break w.vertical_scroll = y @text_object('%') def _(event): """ Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.) If an 'arg' has been given, go this this % position in the file. """ buffer = event.current_buffer if event._arg: # If 'arg' has been given, the meaning of % is to go to the 'x%' # row in the file. if 0 < event.arg <= 100: absolute_index = buffer.document.translate_row_col_to_index( int((event.arg * buffer.document.line_count - 1) / 100), 0) return TextObject(absolute_index - buffer.document.cursor_position, type=TextObjectType.LINEWISE) else: return TextObject(0) # Do nothing. else: # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s). match = buffer.document.find_matching_bracket_position() if match: return TextObject(match, type=TextObjectType.INCLUSIVE) else: return TextObject(0) @text_object('|') def _(event): # Move to the n-th column (you may specify the argument n by typing # it on number keys, for example, 20|). return TextObject(event.current_buffer.document.get_column_cursor_position(event.arg - 1)) @text_object('g', 'g') def _(event): """ Implements 'gg', 'cgg', 'ygg' """ d = event.current_buffer.document if event._arg: # Move to the given line. return TextObject(d.translate_row_col_to_index(event.arg - 1, 0) - d.cursor_position, type=TextObjectType.LINEWISE) else: # Move to the top of the input. return TextObject(d.get_start_of_document_position(), type=TextObjectType.LINEWISE) @text_object('g', '_') def _(event): """ Go to last non-blank of line. 'g_', 'cg_', 'yg_', etc.. """ return TextObject( event.current_buffer.document.last_non_blank_of_current_line_position(), type=TextObjectType.INCLUSIVE) @text_object('g', 'e') def _(event): """ Go to last character of previous word. 'ge', 'cge', 'yge', etc.. """ prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg) return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE) @text_object('g', 'E') def _(event): """ Go to last character of previous WORD. 'gE', 'cgE', 'ygE', etc.. """ prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg, WORD=True) return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE) @text_object('g', 'm') def _(event): """ Like g0, but half a screenwidth to the right. (Or as much as possible.) """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) buff = event.current_buffer if w and w.render_info: width = w.render_info.window_width start = buff.document.get_start_of_line_position(after_whitespace=False) start += int(min(width / 2, len(buff.document.current_line))) return TextObject(start, type=TextObjectType.INCLUSIVE) return TextObject(0) @text_object('G') def _(event): """ Go to the end of the document. (If no arg has been given.) """ buf = event.current_buffer return TextObject(buf.document.translate_row_col_to_index(buf.document.line_count - 1, 0) - buf.cursor_position, type=TextObjectType.LINEWISE) # # *** Other *** # @handle('G', filter=HasArg()) def _(event): """ If an argument is given, move to this line in the history. (for example, 15G) """ event.current_buffer.go_to_history(event.arg - 1) for n in '123456789': @handle(n, filter=navigation_mode|selection_mode|operator_given) def _(event): """ Always handle numberics in navigation mode as arg. """ event.append_to_arg_count(event.data) @handle('0', filter=(navigation_mode|selection_mode|operator_given) & HasArg()) def _(event): " Zero when an argument was already give. " event.append_to_arg_count(event.data) @handle(Keys.Any, filter=replace_mode) def _(event): """ Insert data at cursor position. """ event.current_buffer.insert_text(event.data, overwrite=True) @handle(Keys.Any, filter=insert_multiple_mode, save_before=(lambda e: not e.is_repeat)) def _(event): """ Insert data at multiple cursor positions at once. (Usually a result of pressing 'I' or 'A' in block-selection mode.) """ buff = event.current_buffer original_text = buff.text # Construct new text. text = [] p = 0 for p2 in buff.multiple_cursor_positions: text.append(original_text[p:p2]) text.append(event.data) p = p2 text.append(original_text[p:]) # Shift all cursor positions. new_cursor_positions = [ p + i + 1 for i, p in enumerate(buff.multiple_cursor_positions)] # Set result. buff.text = ''.join(text) buff.multiple_cursor_positions = new_cursor_positions buff.cursor_position += 1 @handle(Keys.Backspace, filter=insert_multiple_mode) def _(event): " Backspace, using multiple cursors. " buff = event.current_buffer original_text = buff.text # Construct new text. deleted_something = False text = [] p = 0 for p2 in buff.multiple_cursor_positions: if p2 > 0 and original_text[p2 - 1] != '\n': # Don't delete across lines. text.append(original_text[p:p2 - 1]) deleted_something = True else: text.append(original_text[p:p2]) p = p2 text.append(original_text[p:]) if deleted_something: # Shift all cursor positions. lengths = [len(part) for part in text[:-1]] new_cursor_positions = list(accumulate(lengths)) # Set result. buff.text = ''.join(text) buff.multiple_cursor_positions = new_cursor_positions buff.cursor_position -= 1 else: event.cli.output.bell() @handle(Keys.Delete, filter=insert_multiple_mode) def _(event): " Delete, using multiple cursors. " buff = event.current_buffer original_text = buff.text # Construct new text. deleted_something = False text = [] new_cursor_positions = [] p = 0 for p2 in buff.multiple_cursor_positions: text.append(original_text[p:p2]) if p2 >= len(original_text) or original_text[p2] == '\n': # Don't delete across lines. p = p2 else: p = p2 + 1 deleted_something = True text.append(original_text[p:]) if deleted_something: # Shift all cursor positions. lengths = [len(part) for part in text[:-1]] new_cursor_positions = list(accumulate(lengths)) # Set result. buff.text = ''.join(text) buff.multiple_cursor_positions = new_cursor_positions else: event.cli.output.bell() @handle(Keys.ControlX, Keys.ControlL, filter=insert_mode) def _(event): """ Pressing the ControlX - ControlL sequence in Vi mode does line completion based on the other lines in the document and the history. """ event.current_buffer.start_history_lines_completion() @handle(Keys.ControlX, Keys.ControlF, filter=insert_mode) def _(event): """ Complete file names. """ # TODO pass @handle(Keys.ControlK, filter=insert_mode|replace_mode) def _(event): " Go into digraph mode. " event.cli.vi_state.waiting_for_digraph = True @Condition def digraph_symbol_1_given(cli): return cli.vi_state.digraph_symbol1 is not None @handle(Keys.Any, filter=digraph_mode & ~digraph_symbol_1_given) def _(event): event.cli.vi_state.digraph_symbol1 = event.data @handle(Keys.Any, filter=digraph_mode & digraph_symbol_1_given) def _(event): " Insert digraph. " try: # Lookup. code = (event.cli.vi_state.digraph_symbol1, event.data) if code not in DIGRAPHS: code = code[::-1] # Try reversing. symbol = DIGRAPHS[code] except KeyError: # Unkown digraph. event.cli.output.bell() else: # Insert digraph. overwrite = event.cli.vi_state.input_mode == InputMode.REPLACE event.current_buffer.insert_text( six.unichr(symbol), overwrite=overwrite) event.cli.vi_state.waiting_for_digraph = False finally: event.cli.vi_state.waiting_for_digraph = False event.cli.vi_state.digraph_symbol1 = None return registry def load_vi_open_in_editor_bindings(): """ Pressing 'v' in navigation mode will open the buffer in an external editor. """ registry = Registry() navigation_mode = ViNavigationMode() registry.add_binding('v', filter=navigation_mode)( get_by_name('edit-and-execute-command')) return registry def load_vi_system_bindings(): registry = ConditionalRegistry(Registry(), ViMode()) handle = registry.add_binding has_focus = filters.HasFocus(SYSTEM_BUFFER) navigation_mode = ViNavigationMode() @handle('!', filter=~has_focus & navigation_mode) def _(event): """ '!' opens the system prompt. """ event.cli.push_focus(SYSTEM_BUFFER) event.cli.vi_state.input_mode = InputMode.INSERT @handle(Keys.Escape, filter=has_focus) @handle(Keys.ControlC, filter=has_focus) def _(event): """ Cancel system prompt. """ event.cli.vi_state.input_mode = InputMode.NAVIGATION event.cli.buffers[SYSTEM_BUFFER].reset() event.cli.pop_focus() @handle(Keys.ControlJ, filter=has_focus) def _(event): """ Run system command. """ event.cli.vi_state.input_mode = InputMode.NAVIGATION system_buffer = event.cli.buffers[SYSTEM_BUFFER] event.cli.run_system_command(system_buffer.text) system_buffer.reset(append_to_history=True) # Focus previous buffer again. event.cli.pop_focus() return registry def load_vi_search_bindings(get_search_state=None, search_buffer_name=SEARCH_BUFFER): assert get_search_state is None or callable(get_search_state) if not get_search_state: def get_search_state(cli): return cli.search_state registry = ConditionalRegistry(Registry(), ViMode()) handle = registry.add_binding has_focus = filters.HasFocus(search_buffer_name) navigation_mode = ViNavigationMode() selection_mode = ViSelectionMode() reverse_vi_search_direction = Condition( lambda cli: cli.application.reverse_vi_search_direction(cli)) @handle('/', filter=(navigation_mode|selection_mode)&~reverse_vi_search_direction) @handle('?', filter=(navigation_mode|selection_mode)&reverse_vi_search_direction) @handle(Keys.ControlS, filter=~has_focus) def _(event): """ Vi-style forward search. """ # Set the ViState. get_search_state(event.cli).direction = IncrementalSearchDirection.FORWARD event.cli.vi_state.input_mode = InputMode.INSERT # Focus search buffer. event.cli.push_focus(search_buffer_name) @handle('?', filter=(navigation_mode|selection_mode)&~reverse_vi_search_direction) @handle('/', filter=(navigation_mode|selection_mode)&reverse_vi_search_direction) @handle(Keys.ControlR, filter=~has_focus) def _(event): """ Vi-style backward search. """ # Set the ViState. get_search_state(event.cli).direction = IncrementalSearchDirection.BACKWARD # Focus search buffer. event.cli.push_focus(search_buffer_name) event.cli.vi_state.input_mode = InputMode.INSERT @handle(Keys.ControlJ, filter=has_focus) @handle(Keys.Escape, filter=has_focus) def _(event): """ Apply the search. (At the / or ? prompt.) """ input_buffer = event.cli.buffers.previous(event.cli) search_buffer = event.cli.buffers[search_buffer_name] # Update search state. if search_buffer.text: get_search_state(event.cli).text = search_buffer.text # Apply search. input_buffer.apply_search(get_search_state(event.cli)) # Add query to history of search line. search_buffer.append_to_history() search_buffer.reset() # Focus previous document again. event.cli.vi_state.input_mode = InputMode.NAVIGATION event.cli.pop_focus() def incremental_search(cli, direction, count=1): " Apply search, but keep search buffer focussed. " # Update search_state. search_state = get_search_state(cli) direction_changed = search_state.direction != direction search_state.text = cli.buffers[search_buffer_name].text search_state.direction = direction # Apply search to current buffer. if not direction_changed: input_buffer = cli.buffers.previous(cli) input_buffer.apply_search(search_state, include_current_position=False, count=count) @handle(Keys.ControlR, filter=has_focus) def _(event): incremental_search(event.cli, IncrementalSearchDirection.BACKWARD, count=event.arg) @handle(Keys.ControlS, filter=has_focus) def _(event): incremental_search(event.cli, IncrementalSearchDirection.FORWARD, count=event.arg) def search_buffer_is_empty(cli): """ Returns True when the search buffer is empty. """ return cli.buffers[search_buffer_name].text == '' @handle(Keys.ControlC, filter=has_focus) @handle(Keys.ControlH, filter=has_focus & Condition(search_buffer_is_empty)) @handle(Keys.Backspace, filter=has_focus & Condition(search_buffer_is_empty)) def _(event): """ Cancel search. """ event.cli.vi_state.input_mode = InputMode.NAVIGATION event.cli.pop_focus() event.cli.buffers[search_buffer_name].reset() return registry def load_extra_vi_page_navigation_bindings(): """ Key bindings, for scrolling up and down through pages. This are separate bindings, because GNU readline doesn't have them. """ registry = ConditionalRegistry(Registry(), ViMode()) handle = registry.add_binding handle(Keys.ControlF)(scroll_forward) handle(Keys.ControlB)(scroll_backward) handle(Keys.ControlD)(scroll_half_page_down) handle(Keys.ControlU)(scroll_half_page_up) handle(Keys.ControlE)(scroll_one_line_down) handle(Keys.ControlY)(scroll_one_line_up) handle(Keys.PageDown)(scroll_page_down) handle(Keys.PageUp)(scroll_page_up) return registry class ViStateFilter(Filter): " Deprecated! " def __init__(self, get_vi_state, mode): self.get_vi_state = get_vi_state self.mode = mode def __call__(self, cli): return self.get_vi_state(cli).input_mode == self.mode prompt_toolkit-1.0.15/prompt_toolkit/key_binding/bindings/named_commands.py0000664000175000017500000003706213136130420031001 0ustar jonathanjonathan00000000000000""" Key bindings which are also known by GNU readline by the given names. See: http://www.delorie.com/gnu/docs/readline/rlman_13.html """ from __future__ import unicode_literals from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER from prompt_toolkit.selection import PasteMode from six.moves import range import six from .completion import generate_completions, display_completions_like_readline from prompt_toolkit.document import Document from prompt_toolkit.enums import EditingMode from prompt_toolkit.key_binding.input_processor import KeyPress from prompt_toolkit.keys import Keys __all__ = ( 'get_by_name', ) # Registry that maps the Readline command names to their handlers. _readline_commands = {} def register(name): """ Store handler in the `_readline_commands` dictionary. """ assert isinstance(name, six.text_type) def decorator(handler): assert callable(handler) _readline_commands[name] = handler return handler return decorator def get_by_name(name): """ Return the handler for the (Readline) command with the given name. """ try: return _readline_commands[name] except KeyError: raise KeyError('Unknown readline command: %r' % name) # # Commands for moving # See: http://www.delorie.com/gnu/docs/readline/rlman_14.html # @register('beginning-of-line') def beginning_of_line(event): " Move to the start of the current line. " buff = event.current_buffer buff.cursor_position += buff.document.get_start_of_line_position(after_whitespace=False) @register('end-of-line') def end_of_line(event): " Move to the end of the line. " buff = event.current_buffer buff.cursor_position += buff.document.get_end_of_line_position() @register('forward-char') def forward_char(event): " Move forward a character. " buff = event.current_buffer buff.cursor_position += buff.document.get_cursor_right_position(count=event.arg) @register('backward-char') def backward_char(event): " Move back a character. " buff = event.current_buffer buff.cursor_position += buff.document.get_cursor_left_position(count=event.arg) @register('forward-word') def forward_word(event): """ Move forward to the end of the next word. Words are composed of letters and digits. """ buff = event.current_buffer pos = buff.document.find_next_word_ending(count=event.arg) if pos: buff.cursor_position += pos @register('backward-word') def backward_word(event): """ Move back to the start of the current or previous word. Words are composed of letters and digits. """ buff = event.current_buffer pos = buff.document.find_previous_word_beginning(count=event.arg) if pos: buff.cursor_position += pos @register('clear-screen') def clear_screen(event): """ Clear the screen and redraw everything at the top of the screen. """ event.cli.renderer.clear() @register('redraw-current-line') def redraw_current_line(event): """ Refresh the current line. (Readline defines this command, but prompt-toolkit doesn't have it.) """ pass # # Commands for manipulating the history. # See: http://www.delorie.com/gnu/docs/readline/rlman_15.html # @register('accept-line') def accept_line(event): " Accept the line regardless of where the cursor is. " b = event.current_buffer b.accept_action.validate_and_handle(event.cli, b) @register('previous-history') def previous_history(event): " Move `back` through the history list, fetching the previous command. " event.current_buffer.history_backward(count=event.arg) @register('next-history') def next_history(event): " Move `forward` through the history list, fetching the next command. " event.current_buffer.history_forward(count=event.arg) @register('beginning-of-history') def beginning_of_history(event): " Move to the first line in the history. " event.current_buffer.go_to_history(0) @register('end-of-history') def end_of_history(event): """ Move to the end of the input history, i.e., the line currently being entered. """ event.current_buffer.history_forward(count=10**100) buff = event.current_buffer buff.go_to_history(len(buff._working_lines) - 1) @register('reverse-search-history') def reverse_search_history(event): """ Search backward starting at the current line and moving `up` through the history as necessary. This is an incremental search. """ event.cli.current_search_state.direction = IncrementalSearchDirection.BACKWARD event.cli.push_focus(SEARCH_BUFFER) # # Commands for changing text # @register('end-of-file') def end_of_file(event): """ Exit. """ event.cli.exit() @register('delete-char') def delete_char(event): " Delete character before the cursor. " deleted = event.current_buffer.delete(count=event.arg) if not deleted: event.cli.output.bell() @register('backward-delete-char') def backward_delete_char(event): " Delete the character behind the cursor. " if event.arg < 0: # When a negative argument has been given, this should delete in front # of the cursor. deleted = event.current_buffer.delete(count=-event.arg) else: deleted = event.current_buffer.delete_before_cursor(count=event.arg) if not deleted: event.cli.output.bell() @register('self-insert') def self_insert(event): " Insert yourself. " event.current_buffer.insert_text(event.data * event.arg) @register('transpose-chars') def transpose_chars(event): """ Emulate Emacs transpose-char behavior: at the beginning of the buffer, do nothing. At the end of a line or buffer, swap the characters before the cursor. Otherwise, move the cursor right, and then swap the characters before the cursor. """ b = event.current_buffer p = b.cursor_position if p == 0: return elif p == len(b.text) or b.text[p] == '\n': b.swap_characters_before_cursor() else: b.cursor_position += b.document.get_cursor_right_position() b.swap_characters_before_cursor() @register('uppercase-word') def uppercase_word(event): """ Uppercase the current (or following) word. """ buff = event.current_buffer for i in range(event.arg): pos = buff.document.find_next_word_ending() words = buff.document.text_after_cursor[:pos] buff.insert_text(words.upper(), overwrite=True) @register('downcase-word') def downcase_word(event): """ Lowercase the current (or following) word. """ buff = event.current_buffer for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!! pos = buff.document.find_next_word_ending() words = buff.document.text_after_cursor[:pos] buff.insert_text(words.lower(), overwrite=True) @register('capitalize-word') def capitalize_word(event): """ Capitalize the current (or following) word. """ buff = event.current_buffer for i in range(event.arg): pos = buff.document.find_next_word_ending() words = buff.document.text_after_cursor[:pos] buff.insert_text(words.title(), overwrite=True) @register('quoted-insert') def quoted_insert(event): """ Add the next character typed to the line verbatim. This is how to insert key sequences like C-q, for example. """ event.cli.quoted_insert = True # # Killing and yanking. # @register('kill-line') def kill_line(event): """ Kill the text from the cursor to the end of the line. If we are at the end of the line, this should remove the newline. (That way, it is possible to delete multiple lines by executing this command multiple times.) """ buff = event.current_buffer if event.arg < 0: deleted = buff.delete_before_cursor(count=-buff.document.get_start_of_line_position()) else: if buff.document.current_char == '\n': deleted = buff.delete(1) else: deleted = buff.delete(count=buff.document.get_end_of_line_position()) event.cli.clipboard.set_text(deleted) @register('kill-word') def kill_word(event): """ Kill from point to the end of the current word, or if between words, to the end of the next word. Word boundaries are the same as forward-word. """ buff = event.current_buffer pos = buff.document.find_next_word_ending(count=event.arg) if pos: deleted = buff.delete(count=pos) event.cli.clipboard.set_text(deleted) @register('unix-word-rubout') def unix_word_rubout(event, WORD=True): """ Kill the word behind point, using whitespace as a word boundary. Usually bound to ControlW. """ buff = event.current_buffer pos = buff.document.find_start_of_previous_word(count=event.arg, WORD=WORD) if pos is None: # Nothing found? delete until the start of the document. (The # input starts with whitespace and no words were found before the # cursor.) pos = - buff.cursor_position if pos: deleted = buff.delete_before_cursor(count=-pos) # If the previous key press was also Control-W, concatenate deleted # text. if event.is_repeat: deleted += event.cli.clipboard.get_data().text event.cli.clipboard.set_text(deleted) else: # Nothing to delete. Bell. event.cli.output.bell() @register('backward-kill-word') def backward_kill_word(event): """ Kills the word before point, using "not a letter nor a digit" as a word boundary. Usually bound to M-Del or M-Backspace. """ unix_word_rubout(event, WORD=False) @register('delete-horizontal-space') def delete_horizontal_space(event): " Delete all spaces and tabs around point. " buff = event.current_buffer text_before_cursor = buff.document.text_before_cursor text_after_cursor = buff.document.text_after_cursor delete_before = len(text_before_cursor) - len(text_before_cursor.rstrip('\t ')) delete_after = len(text_after_cursor) - len(text_after_cursor.lstrip('\t ')) buff.delete_before_cursor(count=delete_before) buff.delete(count=delete_after) @register('unix-line-discard') def unix_line_discard(event): """ Kill backward from the cursor to the beginning of the current line. """ buff = event.current_buffer if buff.document.cursor_position_col == 0 and buff.document.cursor_position > 0: buff.delete_before_cursor(count=1) else: deleted = buff.delete_before_cursor(count=-buff.document.get_start_of_line_position()) event.cli.clipboard.set_text(deleted) @register('yank') def yank(event): """ Paste before cursor. """ event.current_buffer.paste_clipboard_data( event.cli.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.EMACS) @register('yank-nth-arg') def yank_nth_arg(event): """ Insert the first argument of the previous command. With an argument, insert the nth word from the previous command (start counting at 0). """ n = (event.arg if event.arg_present else None) event.current_buffer.yank_nth_arg(n) @register('yank-last-arg') def yank_last_arg(event): """ Like `yank_nth_arg`, but if no argument has been given, yank the last word of each line. """ n = (event.arg if event.arg_present else None) event.current_buffer.yank_last_arg(n) @register('yank-pop') def yank_pop(event): """ Rotate the kill ring, and yank the new top. Only works following yank or yank-pop. """ buff = event.current_buffer doc_before_paste = buff.document_before_paste clipboard = event.cli.clipboard if doc_before_paste is not None: buff.document = doc_before_paste clipboard.rotate() buff.paste_clipboard_data( clipboard.get_data(), paste_mode=PasteMode.EMACS) # # Completion. # @register('complete') def complete(event): " Attempt to perform completion. " display_completions_like_readline(event) @register('menu-complete') def menu_complete(event): """ Generate completions, or go to the next completion. (This is the default way of completing input in prompt_toolkit.) """ generate_completions(event) @register('menu-complete-backward') def menu_complete_backward(event): " Move backward through the list of possible completions. " event.current_buffer.complete_previous() # # Keyboard macros. # @register('start-kbd-macro') def start_kbd_macro(event): """ Begin saving the characters typed into the current keyboard macro. """ event.cli.input_processor.start_macro() @register('end-kbd-macro') def start_kbd_macro(event): """ Stop saving the characters typed into the current keyboard macro and save the definition. """ event.cli.input_processor.end_macro() @register('call-last-kbd-macro') def start_kbd_macro(event): """ Re-execute the last keyboard macro defined, by making the characters in the macro appear as if typed at the keyboard. """ event.cli.input_processor.call_macro() @register('print-last-kbd-macro') def print_last_kbd_macro(event): " Print the last keboard macro. " # TODO: Make the format suitable for the inputrc file. def print_macro(): for k in event.cli.input_processor.macro: print(k) event.cli.run_in_terminal(print_macro) # # Miscellaneous Commands. # @register('undo') def undo(event): " Incremental undo. " event.current_buffer.undo() @register('insert-comment') def insert_comment(event): """ Without numeric argument, comment all lines. With numeric argument, uncomment all lines. In any case accept the input. """ buff = event.current_buffer # Transform all lines. if event.arg != 1: def change(line): return line[1:] if line.startswith('#') else line else: def change(line): return '#' + line buff.document = Document( text='\n'.join(map(change, buff.text.splitlines())), cursor_position=0) # Accept input. buff.accept_action.validate_and_handle(event.cli, buff) @register('vi-editing-mode') def vi_editing_mode(event): " Switch to Vi editing mode. " event.cli.editing_mode = EditingMode.VI @register('emacs-editing-mode') def emacs_editing_mode(event): " Switch to Emacs editing mode. " event.cli.editing_mode = EditingMode.EMACS @register('prefix-meta') def prefix_meta(event): """ Metafy the next character typed. This is for keyboards without a meta key. Sometimes people also want to bind other keys to Meta, e.g. 'jj':: registry.add_key_binding('j', 'j', filter=ViInsertMode())(prefix_meta) """ event.cli.input_processor.feed(KeyPress(Keys.Escape)) @register('operate-and-get-next') def operate_and_get_next(event): """ Accept the current line for execution and fetch the next line relative to the current line from the history for editing. """ buff = event.current_buffer new_index = buff.working_index + 1 # Accept the current input. (This will also redraw the interface in the # 'done' state.) buff.accept_action.validate_and_handle(event.cli, buff) # Set the new index at the start of the next run. def set_working_index(): if new_index < len(buff._working_lines): buff.working_index = new_index event.cli.pre_run_callables.append(set_working_index) @register('edit-and-execute-command') def edit_and_execute(event): """ Invoke an editor on the current command line, and accept the result. """ buff = event.current_buffer buff.open_in_editor(event.cli) buff.accept_action.validate_and_handle(event.cli, buff) prompt_toolkit-1.0.15/prompt_toolkit/key_binding/bindings/completion.py0000664000175000017500000001301113136130420030171 0ustar jonathanjonathan00000000000000""" Key binding handlers for displaying completions. """ from __future__ import unicode_literals from prompt_toolkit.completion import CompleteEvent, get_common_complete_suffix from prompt_toolkit.utils import get_cwidth from prompt_toolkit.keys import Keys from prompt_toolkit.key_binding.registry import Registry import math __all__ = ( 'generate_completions', 'display_completions_like_readline', ) def generate_completions(event): r""" Tab-completion: where the first tab completes the common suffix and the second tab lists all the completions. """ b = event.current_buffer # When already navigating through completions, select the next one. if b.complete_state: b.complete_next() else: event.cli.start_completion(insert_common_part=True, select_first=False) def display_completions_like_readline(event): """ Key binding handler for readline-style tab completion. This is meant to be as similar as possible to the way how readline displays completions. Generate the completions immediately (blocking) and display them above the prompt in columns. Usage:: # Call this handler when 'Tab' has been pressed. registry.add_binding(Keys.ControlI)(display_completions_like_readline) """ # Request completions. b = event.current_buffer if b.completer is None: return complete_event = CompleteEvent(completion_requested=True) completions = list(b.completer.get_completions(b.document, complete_event)) # Calculate the common suffix. common_suffix = get_common_complete_suffix(b.document, completions) # One completion: insert it. if len(completions) == 1: b.delete_before_cursor(-completions[0].start_position) b.insert_text(completions[0].text) # Multiple completions with common part. elif common_suffix: b.insert_text(common_suffix) # Otherwise: display all completions. elif completions: _display_completions_like_readline(event.cli, completions) def _display_completions_like_readline(cli, completions): """ Display the list of completions in columns above the prompt. This will ask for a confirmation if there are too many completions to fit on a single page and provide a paginator to walk through them. """ from prompt_toolkit.shortcuts import create_confirm_application assert isinstance(completions, list) # Get terminal dimensions. term_size = cli.output.get_size() term_width = term_size.columns term_height = term_size.rows # Calculate amount of required columns/rows for displaying the # completions. (Keep in mind that completions are displayed # alphabetically column-wise.) max_compl_width = min(term_width, max(get_cwidth(c.text) for c in completions) + 1) column_count = max(1, term_width // max_compl_width) completions_per_page = column_count * (term_height - 1) page_count = int(math.ceil(len(completions) / float(completions_per_page))) # Note: math.ceil can return float on Python2. def display(page): # Display completions. page_completions = completions[page * completions_per_page: (page+1) * completions_per_page] page_row_count = int(math.ceil(len(page_completions) / float(column_count))) page_columns = [page_completions[i * page_row_count:(i+1) * page_row_count] for i in range(column_count)] result = [] for r in range(page_row_count): for c in range(column_count): try: result.append(page_columns[c][r].text.ljust(max_compl_width)) except IndexError: pass result.append('\n') cli.output.write(''.join(result)) cli.output.flush() # User interaction through an application generator function. def run(): if len(completions) > completions_per_page: # Ask confirmation if it doesn't fit on the screen. message = 'Display all {} possibilities? (y on n) '.format(len(completions)) confirm = yield create_confirm_application(message) if confirm: # Display pages. for page in range(page_count): display(page) if page != page_count - 1: # Display --MORE-- and go to the next page. show_more = yield _create_more_application() if not show_more: return else: cli.output.write('\n'); cli.output.flush() else: # Display all completions. display(0) cli.run_application_generator(run, render_cli_done=True) def _create_more_application(): """ Create an `Application` instance that displays the "--MORE--". """ from prompt_toolkit.shortcuts import create_prompt_application registry = Registry() @registry.add_binding(' ') @registry.add_binding('y') @registry.add_binding('Y') @registry.add_binding(Keys.ControlJ) @registry.add_binding(Keys.ControlI) # Tab. def _(event): event.cli.set_return_value(True) @registry.add_binding('n') @registry.add_binding('N') @registry.add_binding('q') @registry.add_binding('Q') @registry.add_binding(Keys.ControlC) def _(event): event.cli.set_return_value(False) return create_prompt_application( '--MORE--', key_bindings_registry=registry, erase_when_done=True) prompt_toolkit-1.0.15/prompt_toolkit/key_binding/input_processor.py0000664000175000017500000002722513136130420027475 0ustar jonathanjonathan00000000000000# *** encoding: utf-8 *** """ An :class:`~.InputProcessor` receives callbacks for the keystrokes parsed from the input in the :class:`~prompt_toolkit.inputstream.InputStream` instance. The `InputProcessor` will according to the implemented keybindings call the correct callbacks when new key presses are feed through `feed`. """ from __future__ import unicode_literals from prompt_toolkit.buffer import EditReadOnlyBuffer from prompt_toolkit.filters.cli import ViNavigationMode from prompt_toolkit.keys import Keys, Key from prompt_toolkit.utils import Event from .registry import BaseRegistry from collections import deque from six.moves import range import weakref import six __all__ = ( 'InputProcessor', 'KeyPress', ) class KeyPress(object): """ :param key: A `Keys` instance or text (one character). :param data: The received string on stdin. (Often vt100 escape codes.) """ def __init__(self, key, data=None): assert isinstance(key, (six.text_type, Key)) assert data is None or isinstance(data, six.text_type) if data is None: data = key.name if isinstance(key, Key) else key self.key = key self.data = data def __repr__(self): return '%s(key=%r, data=%r)' % ( self.__class__.__name__, self.key, self.data) def __eq__(self, other): return self.key == other.key and self.data == other.data class InputProcessor(object): """ Statemachine that receives :class:`KeyPress` instances and according to the key bindings in the given :class:`Registry`, calls the matching handlers. :: p = InputProcessor(registry) # Send keys into the processor. p.feed(KeyPress(Keys.ControlX, '\x18')) p.feed(KeyPress(Keys.ControlC, '\x03') # Process all the keys in the queue. p.process_keys() # Now the ControlX-ControlC callback will be called if this sequence is # registered in the registry. :param registry: `BaseRegistry` instance. :param cli_ref: weakref to `CommandLineInterface`. """ def __init__(self, registry, cli_ref): assert isinstance(registry, BaseRegistry) self._registry = registry self._cli_ref = cli_ref self.beforeKeyPress = Event(self) self.afterKeyPress = Event(self) # The queue of keys not yet send to our _process generator/state machine. self.input_queue = deque() # The key buffer that is matched in the generator state machine. # (This is at at most the amount of keys that make up for one key binding.) self.key_buffer = [] # Simple macro recording. (Like readline does.) self.record_macro = False self.macro = [] self.reset() def reset(self): self._previous_key_sequence = [] self._previous_handler = None self._process_coroutine = self._process() self._process_coroutine.send(None) #: Readline argument (for repetition of commands.) #: https://www.gnu.org/software/bash/manual/html_node/Readline-Arguments.html self.arg = None def start_macro(self): " Start recording macro. " self.record_macro = True self.macro = [] def end_macro(self): " End recording macro. " self.record_macro = False def call_macro(self): for k in self.macro: self.feed(k) def _get_matches(self, key_presses): """ For a list of :class:`KeyPress` instances. Give the matching handlers that would handle this. """ keys = tuple(k.key for k in key_presses) cli = self._cli_ref() # Try match, with mode flag return [b for b in self._registry.get_bindings_for_keys(keys) if b.filter(cli)] def _is_prefix_of_longer_match(self, key_presses): """ For a list of :class:`KeyPress` instances. Return True if there is any handler that is bound to a suffix of this keys. """ keys = tuple(k.key for k in key_presses) cli = self._cli_ref() # Get the filters for all the key bindings that have a longer match. # Note that we transform it into a `set`, because we don't care about # the actual bindings and executing it more than once doesn't make # sense. (Many key bindings share the same filter.) filters = set(b.filter for b in self._registry.get_bindings_starting_with_keys(keys)) # When any key binding is active, return True. return any(f(cli) for f in filters) def _process(self): """ Coroutine implementing the key match algorithm. Key strokes are sent into this generator, and it calls the appropriate handlers. """ buffer = self.key_buffer retry = False while True: if retry: retry = False else: buffer.append((yield)) # If we have some key presses, check for matches. if buffer: is_prefix_of_longer_match = self._is_prefix_of_longer_match(buffer) matches = self._get_matches(buffer) # When eager matches were found, give priority to them and also # ignore all the longer matches. eager_matches = [m for m in matches if m.eager(self._cli_ref())] if eager_matches: matches = eager_matches is_prefix_of_longer_match = False # Exact matches found, call handler. if not is_prefix_of_longer_match and matches: self._call_handler(matches[-1], key_sequence=buffer[:]) del buffer[:] # Keep reference. # No match found. elif not is_prefix_of_longer_match and not matches: retry = True found = False # Loop over the input, try longest match first and shift. for i in range(len(buffer), 0, -1): matches = self._get_matches(buffer[:i]) if matches: self._call_handler(matches[-1], key_sequence=buffer[:i]) del buffer[:i] found = True break if not found: del buffer[:1] def feed(self, key_press): """ Add a new :class:`KeyPress` to the input queue. (Don't forget to call `process_keys` in order to process the queue.) """ assert isinstance(key_press, KeyPress) self.input_queue.append(key_press) def process_keys(self): """ Process all the keys in the `input_queue`. (To be called after `feed`.) Note: because of the `feed`/`process_keys` separation, it is possible to call `feed` from inside a key binding. This function keeps looping until the queue is empty. """ while self.input_queue: key_press = self.input_queue.popleft() if key_press.key != Keys.CPRResponse: self.beforeKeyPress.fire() self._process_coroutine.send(key_press) if key_press.key != Keys.CPRResponse: self.afterKeyPress.fire() # Invalidate user interface. cli = self._cli_ref() if cli: cli.invalidate() def _call_handler(self, handler, key_sequence=None): was_recording = self.record_macro arg = self.arg self.arg = None event = KeyPressEvent( weakref.ref(self), arg=arg, key_sequence=key_sequence, previous_key_sequence=self._previous_key_sequence, is_repeat=(handler == self._previous_handler)) # Save the state of the current buffer. cli = event.cli # Can be `None` (In unit-tests only.) if handler.save_before(event) and cli: cli.current_buffer.save_to_undo_stack() # Call handler. try: handler.call(event) self._fix_vi_cursor_position(event) except EditReadOnlyBuffer: # When a key binding does an attempt to change a buffer which is # read-only, we can just silently ignore that. pass self._previous_key_sequence = key_sequence self._previous_handler = handler # Record the key sequence in our macro. (Only if we're in macro mode # before and after executing the key.) if self.record_macro and was_recording: self.macro.extend(key_sequence) def _fix_vi_cursor_position(self, event): """ After every command, make sure that if we are in Vi navigation mode, we never put the cursor after the last character of a line. (Unless it's an empty line.) """ cli = self._cli_ref() if cli: buff = cli.current_buffer preferred_column = buff.preferred_column if (ViNavigationMode()(event.cli) and buff.document.is_cursor_at_the_end_of_line and len(buff.document.current_line) > 0): buff.cursor_position -= 1 # Set the preferred_column for arrow up/down again. # (This was cleared after changing the cursor position.) buff.preferred_column = preferred_column class KeyPressEvent(object): """ Key press event, delivered to key bindings. :param input_processor_ref: Weak reference to the `InputProcessor`. :param arg: Repetition argument. :param key_sequence: List of `KeyPress` instances. :param previouskey_sequence: Previous list of `KeyPress` instances. :param is_repeat: True when the previous event was delivered to the same handler. """ def __init__(self, input_processor_ref, arg=None, key_sequence=None, previous_key_sequence=None, is_repeat=False): self._input_processor_ref = input_processor_ref self.key_sequence = key_sequence self.previous_key_sequence = previous_key_sequence #: True when the previous key sequence was handled by the same handler. self.is_repeat = is_repeat self._arg = arg def __repr__(self): return 'KeyPressEvent(arg=%r, key_sequence=%r, is_repeat=%r)' % ( self.arg, self.key_sequence, self.is_repeat) @property def data(self): return self.key_sequence[-1].data @property 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): """ Repetition argument. """ if self._arg == '-': return -1 result = int(self._arg or 1) # Don't exceed a million. if int(result) >= 1000000: result = 1 return result @property def arg_present(self): """ True if repetition argument was explicitly provided. """ return self._arg is not None def append_to_arg_count(self, data): """ Add digit to the input argument. :param data: the typed digit as string """ assert data in '-0123456789' current = self._arg if data == '-': assert current is None or current == '-' result = data elif current is None: result = data else: result = "%s%s" % (current, data) self.input_processor.arg = result prompt_toolkit-1.0.15/prompt_toolkit/key_binding/registry.py0000664000175000017500000002634213136130420026106 0ustar jonathanjonathan00000000000000""" Key bindings registry. A `Registry` object is a container that holds a list of key bindings. It has a very efficient internal data structure for checking which key bindings apply for a pressed key. Typical usage:: r = Registry() @r.add_binding(Keys.ControlX, Keys.ControlC, filter=INSERT) def handler(event): # Handle ControlX-ControlC key sequence. pass It is also possible to combine multiple registries. We do this in the default key bindings. There are some registries that contain Emacs bindings, while others contain the Vi bindings. They are merged together using a `MergedRegistry`. We also have a `ConditionalRegistry` object that can enable/disable a group of key bindings at once. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from prompt_toolkit.cache import SimpleCache from prompt_toolkit.filters import CLIFilter, to_cli_filter, Never from prompt_toolkit.keys import Key, Keys from six import text_type, with_metaclass __all__ = ( 'BaseRegistry', 'Registry', 'ConditionalRegistry', 'MergedRegistry', ) class _Binding(object): """ (Immutable binding class.) """ def __init__(self, keys, handler, filter=None, eager=None, save_before=None): assert isinstance(keys, tuple) assert callable(handler) assert isinstance(filter, CLIFilter) assert isinstance(eager, CLIFilter) assert callable(save_before) self.keys = keys self.handler = handler self.filter = filter self.eager = eager self.save_before = save_before def call(self, event): return self.handler(event) def __repr__(self): return '%s(keys=%r, handler=%r)' % ( self.__class__.__name__, self.keys, self.handler) class BaseRegistry(with_metaclass(ABCMeta, object)): """ Interface for a Registry. """ _version = 0 # For cache invalidation. @abstractmethod def get_bindings_for_keys(self, keys): pass @abstractmethod def get_bindings_starting_with_keys(self, keys): pass # `add_binding` and `remove_binding` don't have to be part of this # interface. class Registry(BaseRegistry): """ Key binding registry. """ def __init__(self): self.key_bindings = [] self._get_bindings_for_keys_cache = SimpleCache(maxsize=10000) self._get_bindings_starting_with_keys_cache = SimpleCache(maxsize=1000) self._version = 0 # For cache invalidation. def _clear_cache(self): self._version += 1 self._get_bindings_for_keys_cache.clear() self._get_bindings_starting_with_keys_cache.clear() def add_binding(self, *keys, **kwargs): """ Decorator for annotating key bindings. :param filter: :class:`~prompt_toolkit.filters.CLIFilter` to determine when this key binding is active. :param eager: :class:`~prompt_toolkit.filters.CLIFilter` or `bool`. When True, ignore potential longer matches when this key binding is hit. E.g. when there is an active eager key binding for Ctrl-X, execute the handler immediately and ignore the key binding for Ctrl-X Ctrl-E of which it is a prefix. :param save_before: Callable that takes an `Event` and returns True if we should save the current buffer, before handling the event. (That's the default.) """ filter = to_cli_filter(kwargs.pop('filter', True)) eager = to_cli_filter(kwargs.pop('eager', False)) save_before = kwargs.pop('save_before', lambda e: True) to_cli_filter(kwargs.pop('invalidate_ui', True)) # Deprecated! (ignored.) assert not kwargs assert keys assert all(isinstance(k, (Key, text_type)) for k in keys), \ 'Key bindings should consist of Key and string (unicode) instances.' assert callable(save_before) if isinstance(filter, Never): # When a filter is Never, it will always stay disabled, so in that case # don't bother putting it in the registry. It will slow down every key # press otherwise. def decorator(func): return func else: def decorator(func): self.key_bindings.append( _Binding(keys, func, filter=filter, eager=eager, save_before=save_before)) self._clear_cache() return func return decorator def remove_binding(self, function): """ Remove a key binding. This expects a function that was given to `add_binding` method as parameter. Raises `ValueError` when the given function was not registered before. """ assert callable(function) for b in self.key_bindings: if b.handler == function: self.key_bindings.remove(b) self._clear_cache() return # No key binding found for this function. Raise ValueError. raise ValueError('Binding not found: %r' % (function, )) def get_bindings_for_keys(self, keys): """ Return a list of key bindings that can handle this key. (This return also inactive bindings, so the `filter` still has to be called, for checking it.) :param keys: tuple of keys. """ def get(): result = [] for b in self.key_bindings: if len(keys) == len(b.keys): match = True any_count = 0 for i, j in zip(b.keys, keys): if i != j and i != Keys.Any: match = False break if i == Keys.Any: any_count += 1 if match: result.append((any_count, b)) # Place bindings that have more 'Any' occurences in them at the end. result = sorted(result, key=lambda item: -item[0]) return [item[1] for item in result] return self._get_bindings_for_keys_cache.get(keys, get) def get_bindings_starting_with_keys(self, keys): """ Return a list of key bindings that handle a key sequence starting with `keys`. (It does only return bindings for which the sequences are longer than `keys`. And like `get_bindings_for_keys`, it also includes inactive bindings.) :param keys: tuple of keys. """ def get(): result = [] for b in self.key_bindings: if len(keys) < len(b.keys): match = True for i, j in zip(b.keys, keys): if i != j and i != Keys.Any: match = False break if match: result.append(b) return result return self._get_bindings_starting_with_keys_cache.get(keys, get) class _AddRemoveMixin(BaseRegistry): """ Common part for ConditionalRegistry and MergedRegistry. """ def __init__(self): # `Registry` to be synchronized with all the others. self._registry2 = Registry() self._last_version = None # The 'extra' registry. Mostly for backwards compatibility. self._extra_registry = Registry() def _update_cache(self): raise NotImplementedError # For backwards, compatibility, we allow adding bindings to both # ConditionalRegistry and MergedRegistry. This is however not the # recommended way. Better is to create a new registry and merge them # together using MergedRegistry. def add_binding(self, *k, **kw): return self._extra_registry.add_binding(*k, **kw) def remove_binding(self, *k, **kw): return self._extra_registry.remove_binding(*k, **kw) # Proxy methods to self._registry2. @property def key_bindings(self): self._update_cache() return self._registry2.key_bindings @property def _version(self): self._update_cache() return self._last_version def get_bindings_for_keys(self, *a, **kw): self._update_cache() return self._registry2.get_bindings_for_keys(*a, **kw) def get_bindings_starting_with_keys(self, *a, **kw): self._update_cache() return self._registry2.get_bindings_starting_with_keys(*a, **kw) class ConditionalRegistry(_AddRemoveMixin): """ Wraps around a `Registry`. Disable/enable all the key bindings according to the given (additional) filter.:: @Condition def setting_is_true(cli): return True # or False registy = ConditionalRegistry(registry, setting_is_true) When new key bindings are added to this object. They are also enable/disabled according to the given `filter`. :param registries: List of `Registry` objects. :param filter: `CLIFilter` object. """ def __init__(self, registry=None, filter=True): registry = registry or Registry() assert isinstance(registry, BaseRegistry) _AddRemoveMixin.__init__(self) self.registry = registry self.filter = to_cli_filter(filter) def _update_cache(self): " If the original registry was changed. Update our copy version. " expected_version = (self.registry._version, self._extra_registry._version) if self._last_version != expected_version: registry2 = Registry() # Copy all bindings from `self.registry`, adding our condition. for reg in (self.registry, self._extra_registry): for b in reg.key_bindings: registry2.key_bindings.append( _Binding( keys=b.keys, handler=b.handler, filter=self.filter & b.filter, eager=b.eager, save_before=b.save_before)) self._registry2 = registry2 self._last_version = expected_version class MergedRegistry(_AddRemoveMixin): """ Merge multiple registries of key bindings into one. This class acts as a proxy to multiple `Registry` objects, but behaves as if this is just one bigger `Registry`. :param registries: List of `Registry` objects. """ def __init__(self, registries): assert all(isinstance(r, BaseRegistry) for r in registries) _AddRemoveMixin.__init__(self) self.registries = registries def _update_cache(self): """ If one of the original registries was changed. Update our merged version. """ expected_version = ( tuple(r._version for r in self.registries) + (self._extra_registry._version, )) if self._last_version != expected_version: registry2 = Registry() for reg in self.registries: registry2.key_bindings.extend(reg.key_bindings) # Copy all bindings from `self._extra_registry`. registry2.key_bindings.extend(self._extra_registry.key_bindings) self._registry2 = registry2 self._last_version = expected_version prompt_toolkit-1.0.15/prompt_toolkit/key_binding/__init__.py0000664000175000017500000000005013136130420025761 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals prompt_toolkit-1.0.15/prompt_toolkit/key_binding/manager.py0000664000175000017500000000721413136130420025645 0ustar jonathanjonathan00000000000000""" DEPRECATED: Use `prompt_toolkit.key_binding.defaults.load_key_bindings` instead. :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() app = Application(key_bindings_registry=manager.registry) """ from __future__ import unicode_literals from .defaults import load_key_bindings from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.key_binding.registry import Registry, ConditionalRegistry, MergedRegistry __all__ = ( 'KeyBindingManager', ) class KeyBindingManager(object): """ Utility for loading all key bindings into memory. :param registry: Optional `Registry` instance. :param enable_abort_and_exit_bindings: Filter to enable Ctrl-C and Ctrl-D. :param enable_system_bindings: Filter to enable the system bindings (meta-! prompt and Control-Z suspension.) :param enable_search: Filter to enable the search bindings. :param enable_open_in_editor: Filter to enable open-in-editor. :param enable_open_in_editor: Filter to enable open-in-editor. :param enable_extra_page_navigation: Filter for enabling extra page navigation. (Bindings for up/down scrolling through long pages, like in Emacs or Vi.) :param enable_auto_suggest_bindings: Filter to enable fish-style suggestions. :param enable_vi_mode: Deprecated! """ def __init__(self, registry=None, # XXX: not used anymore. enable_vi_mode=None, # (`enable_vi_mode` is deprecated.) enable_all=True, # get_search_state=None, enable_abort_and_exit_bindings=False, enable_system_bindings=False, enable_search=False, enable_open_in_editor=False, enable_extra_page_navigation=False, enable_auto_suggest_bindings=False): assert registry is None or isinstance(registry, Registry) assert get_search_state is None or callable(get_search_state) enable_all = to_cli_filter(enable_all) defaults = load_key_bindings( get_search_state=get_search_state, enable_abort_and_exit_bindings=enable_abort_and_exit_bindings, enable_system_bindings=enable_system_bindings, enable_search=enable_search, enable_open_in_editor=enable_open_in_editor, enable_extra_page_navigation=enable_extra_page_navigation, enable_auto_suggest_bindings=enable_auto_suggest_bindings) # Note, we wrap this whole thing again in a MergedRegistry, because we # don't want the `enable_all` settings to apply on items that were # added to the registry as a whole. self.registry = MergedRegistry([ ConditionalRegistry(defaults, enable_all) ]) @classmethod def for_prompt(cls, **kw): """ Create a ``KeyBindingManager`` with the defaults for an input prompt. This activates the key bindings for abort/exit (Ctrl-C/Ctrl-D), incremental search and auto suggestions. (Not for full screen applications.) """ kw.setdefault('enable_abort_and_exit_bindings', True) kw.setdefault('enable_search', True) kw.setdefault('enable_auto_suggest_bindings', True) return cls(**kw) def reset(self, cli): # For backwards compatibility. pass def get_vi_state(self, cli): # Deprecated! return cli.vi_state prompt_toolkit-1.0.15/prompt_toolkit/key_binding/vi_state.py0000664000175000017500000000330613136130420026047 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals __all__ = ( 'InputMode', 'CharacterFind', 'ViState', ) class InputMode(object): INSERT = 'vi-insert' INSERT_MULTIPLE = 'vi-insert-multiple' 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 # When an operator is given and we are waiting for text object, # -- e.g. in the case of 'dw', after the 'd' --, an operator callback # is set here. self.operator_func = None self.operator_arg = None #: Named registers. Maps register name (e.g. 'a') to #: :class:`ClipboardData` instances. self.named_registers = {} #: The Vi mode we're currently in to. self.input_mode = InputMode.INSERT #: Waiting for digraph. self.waiting_for_digraph = False self.digraph_symbol1 = None # (None or a symbol.) #: When true, make ~ act as an operator. self.tilde_operator = False def reset(self, mode=InputMode.INSERT): """ Reset state, go back to the given mode. INSERT by default. """ # Go back to insert mode. self.input_mode = mode self.waiting_for_digraph = False self.operator_func = None self.operator_arg = None prompt_toolkit-1.0.15/prompt_toolkit/reactive.py0000664000175000017500000000346113136130420023553 0ustar jonathanjonathan00000000000000""" Prompt_toolkit is designed a way that the amount of changing state is reduced to a minimum. Where possible, code is written in a pure functional way. In general, this results in code where the flow is very easy to follow: the value of a variable can be deducted from its first assignment. However, often, practicality and performance beat purity and some classes still have a changing state. In order to not having to care too much about transferring states between several components we use some reactive programming. Actually some kind of data binding. We introduce two types: - Filter: for binding a boolean state. They can be chained using & and | operators. Have a look in the ``filters`` module. Resolving the actual value of a filter happens by calling it. - Integer: for binding integer values. Reactive operations (like addition and substraction) are not suppported. Resolving the actual value happens by casting it to int, like ``int(integer)``. This way, it is possible to use normal integers as well for static values. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass class Integer(with_metaclass(ABCMeta, object)): """ Reactive integer -- anything that can be resolved to an ``int``. """ @abstractmethod def __int__(self): return 0 @classmethod def from_callable(cls, func): """ Create an Integer-like object that calls the given function when it is resolved to an int. """ return _IntegerFromCallable(func) Integer.register(int) class _IntegerFromCallable(Integer): def __init__(self, func=0): self.func = func def __repr__(self): return 'Integer.from_callable(%r)' % self.func def __int__(self): return int(self.func()) prompt_toolkit-1.0.15/prompt_toolkit/selection.py0000664000175000017500000000214013136130420023727 0ustar jonathanjonathan00000000000000""" Data structures for the selection. """ from __future__ import unicode_literals __all__ = ( 'SelectionType', 'PasteMode', 'SelectionState', ) class SelectionType(object): """ Type of selection. """ #: Characters. (Visual in Vi.) CHARACTERS = 'CHARACTERS' #: Whole lines. (Visual-Line in Vi.) LINES = 'LINES' #: A block selection. (Visual-Block in Vi.) BLOCK = 'BLOCK' class PasteMode(object): EMACS = 'EMACS' # Yank like emacs. VI_AFTER = 'VI_AFTER' # When pressing 'p' in Vi. VI_BEFORE = 'VI_BEFORE' # When pressing 'P' in Vi. class SelectionState(object): """ State of the current selection. :param original_cursor_position: int :param type: :class:`~.SelectionType` """ def __init__(self, original_cursor_position=0, type=SelectionType.CHARACTERS): self.original_cursor_position = original_cursor_position self.type = type def __repr__(self): return '%s(original_cursor_position=%r, type=%r)' % ( self.__class__.__name__, self.original_cursor_position, self.type) prompt_toolkit-1.0.15/prompt_toolkit/auto_suggest.py0000664000175000017500000000543213136130420024462 0ustar jonathanjonathan00000000000000""" `Fish-style `_ like auto-suggestion. While a user types input in a certain buffer, suggestions are generated (asynchronously.) Usually, they are displayed after the input. When the cursor presses the right arrow and the cursor is at the end of the input, the suggestion will be inserted. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from .filters import to_cli_filter __all__ = ( 'Suggestion', 'AutoSuggest', 'AutoSuggestFromHistory', 'ConditionalAutoSuggest', ) class Suggestion(object): """ Suggestion returned by an auto-suggest algorithm. :param text: The suggestion text. """ def __init__(self, text): self.text = text def __repr__(self): return 'Suggestion(%s)' % self.text class AutoSuggest(with_metaclass(ABCMeta, object)): """ Base class for auto suggestion implementations. """ @abstractmethod def get_suggestion(self, cli, buffer, document): """ Return `None` or a :class:`.Suggestion` instance. We receive both ``buffer`` and ``document``. The reason is that auto suggestions are retrieved asynchronously. (Like completions.) The buffer text could be changed in the meantime, but ``document`` contains the buffer document like it was at the start of the auto suggestion call. So, from here, don't access ``buffer.text``, but use ``document.text`` instead. :param buffer: The :class:`~prompt_toolkit.buffer.Buffer` instance. :param document: The :class:`~prompt_toolkit.document.Document` instance. """ class AutoSuggestFromHistory(AutoSuggest): """ Give suggestions based on the lines in the history. """ def get_suggestion(self, cli, buffer, document): history = buffer.history # Consider only the last line for the suggestion. text = document.text.rsplit('\n', 1)[-1] # Only create a suggestion when this is not an empty line. if text.strip(): # Find first matching line in history. for string in reversed(list(history)): for line in reversed(string.splitlines()): if line.startswith(text): return Suggestion(line[len(text):]) class ConditionalAutoSuggest(AutoSuggest): """ Auto suggest that can be turned on and of according to a certain condition. """ def __init__(self, auto_suggest, filter): assert isinstance(auto_suggest, AutoSuggest) self.auto_suggest = auto_suggest self.filter = to_cli_filter(filter) def get_suggestion(self, cli, buffer, document): if self.filter(cli): return self.auto_suggest.get_suggestion(cli, buffer, document) prompt_toolkit-1.0.15/prompt_toolkit/keys.py0000664000175000017500000000705613136130420022730 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals __all__ = ( 'Key', 'Keys', ) class Key(object): def __init__(self, name): #: Descriptive way of writing keys in configuration files. e.g. #: for ``Control-A``. self.name = name def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.name) class Keys(object): Escape = Key('') ControlA = Key('') ControlB = Key('') ControlC = Key('') ControlD = Key('') ControlE = Key('') ControlF = Key('') ControlG = Key('') ControlH = Key('') ControlI = Key('') # Tab ControlJ = Key('') # Enter ControlK = Key('') ControlL = Key('') ControlM = Key('') # Enter ControlN = Key('') ControlO = Key('') ControlP = Key('') ControlQ = Key('') ControlR = Key('') ControlS = Key('') ControlT = Key('') ControlU = Key('') ControlV = Key('') ControlW = Key('') ControlX = Key('') ControlY = Key('') ControlZ = Key('') ControlSpace = Key('') ControlBackslash = Key('') ControlSquareClose = Key('') ControlCircumflex = Key('') ControlUnderscore = Key('') ControlLeft = Key('') ControlRight = Key('') ControlUp = Key('') ControlDown = Key('') Up = Key('') Down = Key('') Right = Key('') Left = Key('') ShiftLeft = Key('') ShiftUp = Key('') ShiftDown = Key('') ShiftRight = Key('') Home = Key('') End = Key('') Delete = Key('') ShiftDelete = Key('') ControlDelete = Key('') PageUp = Key('') PageDown = Key('') BackTab = Key('') # shift + tab Insert = Key('') Backspace = Key('') # Aliases. Tab = ControlI Enter = ControlJ # XXX: Actually Enter equals ControlM, not ControlJ, # However, in prompt_toolkit, we made the mistake of translating # \r into \n during the input, so everyone is now handling the # enter key by binding ControlJ. # From now on, it's better to bind `Keys.Enter` everywhere, # because that's future compatible, and will still work when we # stop replacing \r by \n. F1 = Key('') F2 = Key('') F3 = Key('') F4 = Key('') F5 = Key('') F6 = Key('') F7 = Key('') F8 = Key('') F9 = Key('') F10 = Key('') F11 = Key('') F12 = Key('') F13 = Key('') F14 = Key('') F15 = Key('') F16 = Key('') F17 = Key('') F18 = Key('') F19 = Key('') F20 = Key('') F21 = Key('') F22 = Key('') F23 = Key('') F24 = Key('') # Matches any key. Any = Key('') # Special CPRResponse = Key('') Vt100MouseEvent = Key('') WindowsMouseEvent = Key('') BracketedPaste = Key('') # Key which is ignored. (The key binding for this key should not do # anything.) Ignore = Key('') prompt_toolkit-1.0.15/prompt_toolkit/input.py0000664000175000017500000000621613136130420023111 0ustar jonathanjonathan00000000000000""" Abstraction of CLI Input. """ from __future__ import unicode_literals from .utils import DummyContext, is_windows from abc import ABCMeta, abstractmethod from six import with_metaclass import io import os import sys if is_windows(): from .terminal.win32_input import raw_mode, cooked_mode else: from .terminal.vt100_input import raw_mode, cooked_mode __all__ = ( 'Input', 'StdinInput', 'PipeInput', ) class Input(with_metaclass(ABCMeta, object)): """ Abstraction for any input. An instance of this class can be given to the constructor of a :class:`~prompt_toolkit.interface.CommandLineInterface` and will also be passed to the :class:`~prompt_toolkit.eventloop.base.EventLoop`. """ @abstractmethod def fileno(self): """ Fileno for putting this in an event loop. """ @abstractmethod def read(self): """ Return text from the input. """ @abstractmethod def raw_mode(self): """ Context manager that turns the input into raw mode. """ @abstractmethod def cooked_mode(self): """ Context manager that turns the input into cooked mode. """ class StdinInput(Input): """ Simple wrapper around stdin. """ def __init__(self, stdin=None): self.stdin = stdin or sys.stdin # The input object should be a TTY. assert self.stdin.isatty() # Test whether the given input object has a file descriptor. # (Idle reports stdin to be a TTY, but fileno() is not implemented.) try: # This should not raise, but can return 0. self.stdin.fileno() except io.UnsupportedOperation: if 'idlelib.run' in sys.modules: raise io.UnsupportedOperation( 'Stdin is not a terminal. Running from Idle is not supported.') else: raise io.UnsupportedOperation('Stdin is not a terminal.') def __repr__(self): return 'StdinInput(stdin=%r)' % (self.stdin,) def raw_mode(self): return raw_mode(self.stdin.fileno()) def cooked_mode(self): return cooked_mode(self.stdin.fileno()) def fileno(self): return self.stdin.fileno() def read(self): return self.stdin.read() class PipeInput(Input): """ Input that is send through a pipe. This is useful if we want to send the input programatically into the interface, but still use the eventloop. Usage:: input = PipeInput() input.send('inputdata') """ def __init__(self): self._r, self._w = os.pipe() def fileno(self): return self._r def read(self): return os.read(self._r) def send_text(self, data): " Send text to the input. " os.write(self._w, data.encode('utf-8')) # Deprecated alias for `send_text`. send = send_text def raw_mode(self): return DummyContext() def cooked_mode(self): return DummyContext() def close(self): " Close pipe fds. " os.close(self._r) os.close(self._w) self._r = None self._w = None prompt_toolkit-1.0.15/prompt_toolkit/clipboard/0000775000175000017500000000000013136335632023346 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/clipboard/__init__.py0000664000175000017500000000036513136130420025447 0ustar jonathanjonathan00000000000000from .base import Clipboard, ClipboardData from .in_memory import InMemoryClipboard # We are not importing `PyperclipClipboard` here, because it would require the # `pyperclip` module to be present. #from .pyperclip import PyperclipClipboard prompt_toolkit-1.0.15/prompt_toolkit/clipboard/in_memory.py0000664000175000017500000000177713136130420025716 0ustar jonathanjonathan00000000000000from .base import Clipboard, ClipboardData from collections import deque __all__ = ( 'InMemoryClipboard', ) class InMemoryClipboard(Clipboard): """ Default clipboard implementation. Just keep the data in memory. This implements a kill-ring, for Emacs mode. """ def __init__(self, data=None, max_size=60): assert data is None or isinstance(data, ClipboardData) assert max_size >= 1 self.max_size = max_size self._ring = deque() if data is not None: self.set_data(data) def set_data(self, data): assert isinstance(data, ClipboardData) self._ring.appendleft(data) while len(self._ring) > self.max_size: self._ring.pop() def get_data(self): if self._ring: return self._ring[0] else: return ClipboardData() def rotate(self): if self._ring: # Add the very first item at the end. self._ring.append(self._ring.popleft()) prompt_toolkit-1.0.15/prompt_toolkit/clipboard/pyperclip.py0000664000175000017500000000216113136130420025713 0ustar jonathanjonathan00000000000000from __future__ import absolute_import, unicode_literals import pyperclip from prompt_toolkit.selection import SelectionType from .base import Clipboard, ClipboardData __all__ = ( 'PyperclipClipboard', ) class PyperclipClipboard(Clipboard): """ Clipboard that synchronizes with the Windows/Mac/Linux system clipboard, using the pyperclip module. """ def __init__(self): self._data = None def set_data(self, data): assert isinstance(data, ClipboardData) self._data = data pyperclip.copy(data.text) def get_data(self): text = pyperclip.paste() # When the clipboard data is equal to what we copied last time, reuse # the `ClipboardData` instance. That way we're sure to keep the same # `SelectionType`. if self._data and self._data.text == text: return self._data # Pyperclip returned something else. Create a new `ClipboardData` # instance. else: return ClipboardData( text=text, type=SelectionType.LINES if '\n' in text else SelectionType.LINES) prompt_toolkit-1.0.15/prompt_toolkit/clipboard/base.py0000664000175000017500000000272213136130420024621 0ustar jonathanjonathan00000000000000""" Clipboard for command line interface. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass import six from prompt_toolkit.selection import SelectionType __all__ = ( 'Clipboard', 'ClipboardData', ) class ClipboardData(object): """ Text on the clipboard. :param text: string :param type: :class:`~prompt_toolkit.selection.SelectionType` """ def __init__(self, text='', type=SelectionType.CHARACTERS): assert isinstance(text, six.string_types) assert type in (SelectionType.CHARACTERS, SelectionType.LINES, SelectionType.BLOCK) self.text = text self.type = type class Clipboard(with_metaclass(ABCMeta, object)): """ Abstract baseclass for clipboards. (An implementation can be in memory, it can share the X11 or Windows keyboard, or can be persistent.) """ @abstractmethod def set_data(self, data): """ Set data to the clipboard. :param data: :class:`~.ClipboardData` instance. """ def set_text(self, text): # Not abstract. """ Shortcut for setting plain text on clipboard. """ assert isinstance(text, six.string_types) self.set_data(ClipboardData(text)) def rotate(self): """ For Emacs mode, rotate the kill ring. """ @abstractmethod def get_data(self): """ Return clipboard data. """ prompt_toolkit-1.0.15/prompt_toolkit/filters/0000775000175000017500000000000013136335632023057 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/filters/types.py0000664000175000017500000000275213136130420024567 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from six import with_metaclass from collections import defaultdict import weakref __all__ = ( 'CLIFilter', 'SimpleFilter', ) # Cache for _FilterTypeMeta. (Don't test the same __instancecheck__ twice as # long as the object lives. -- We do this a lot and calling 'test_args' is # expensive.) _instance_check_cache = defaultdict(weakref.WeakKeyDictionary) class _FilterTypeMeta(type): def __instancecheck__(cls, instance): cache = _instance_check_cache[tuple(cls.arguments_list)] def get(): " The actual test. " if not hasattr(instance, 'test_args'): return False return instance.test_args(*cls.arguments_list) try: return cache[instance] except KeyError: result = get() cache[instance] = result return result class _FilterType(with_metaclass(_FilterTypeMeta)): def __new__(cls): raise NotImplementedError('This class should not be initiated.') class CLIFilter(_FilterType): """ Abstract base class for filters that accept a :class:`~prompt_toolkit.interface.CommandLineInterface` argument. It cannot be instantiated, it's only to be used for instance assertions, e.g.:: isinstance(my_filter, CliFilter) """ arguments_list = ['cli'] class SimpleFilter(_FilterType): """ Abstract base class for filters that don't accept any arguments. """ arguments_list = [] prompt_toolkit-1.0.15/prompt_toolkit/filters/cli.py0000664000175000017500000002201013136130420024157 0ustar jonathanjonathan00000000000000""" Filters that accept a `CommandLineInterface` as argument. """ from __future__ import unicode_literals from .base import Filter from prompt_toolkit.enums import EditingMode from prompt_toolkit.key_binding.vi_state import InputMode as ViInputMode from prompt_toolkit.cache import memoized __all__ = ( 'HasArg', 'HasCompletions', 'HasFocus', 'InFocusStack', 'HasSearch', 'HasSelection', 'HasValidationError', 'IsAborting', 'IsDone', 'IsMultiline', 'IsReadOnly', 'IsReturning', 'RendererHeightIsKnown', 'InEditingMode', # Vi modes. 'ViMode', 'ViNavigationMode', 'ViInsertMode', 'ViInsertMultipleMode', 'ViReplaceMode', 'ViSelectionMode', 'ViWaitingForTextObjectMode', 'ViDigraphMode', # Emacs modes. 'EmacsMode', 'EmacsInsertMode', 'EmacsSelectionMode', ) @memoized() class HasFocus(Filter): """ Enable when this buffer has the focus. """ def __init__(self, buffer_name): self._buffer_name = buffer_name @property def buffer_name(self): " The given buffer name. (Read-only) " return self._buffer_name def __call__(self, cli): return cli.current_buffer_name == self.buffer_name def __repr__(self): return 'HasFocus(%r)' % self.buffer_name @memoized() class InFocusStack(Filter): """ Enable when this buffer appears on the focus stack. """ def __init__(self, buffer_name): self._buffer_name = buffer_name @property def buffer_name(self): " The given buffer name. (Read-only) " return self._buffer_name def __call__(self, cli): return self.buffer_name in cli.buffers.focus_stack def __repr__(self): return 'InFocusStack(%r)' % self.buffer_name @memoized() class HasSelection(Filter): """ Enable when the current buffer has a selection. """ def __call__(self, cli): return bool(cli.current_buffer.selection_state) def __repr__(self): return 'HasSelection()' @memoized() class HasCompletions(Filter): """ Enable when the current buffer has completions. """ def __call__(self, cli): return cli.current_buffer.complete_state is not None def __repr__(self): return 'HasCompletions()' @memoized() class IsMultiline(Filter): """ Enable in multiline mode. """ def __call__(self, cli): return cli.current_buffer.is_multiline() def __repr__(self): return 'IsMultiline()' @memoized() class IsReadOnly(Filter): """ True when the current buffer is read only. """ def __call__(self, cli): return cli.current_buffer.read_only() def __repr__(self): return 'IsReadOnly()' @memoized() class HasValidationError(Filter): """ Current buffer has validation error. """ def __call__(self, cli): return cli.current_buffer.validation_error is not None def __repr__(self): return 'HasValidationError()' @memoized() class HasArg(Filter): """ Enable when the input processor has an 'arg'. """ def __call__(self, cli): return cli.input_processor.arg is not None def __repr__(self): return 'HasArg()' @memoized() class HasSearch(Filter): """ Incremental search is active. """ def __call__(self, cli): return cli.is_searching def __repr__(self): return 'HasSearch()' @memoized() class IsReturning(Filter): """ When a return value has been set. """ def __call__(self, cli): return cli.is_returning def __repr__(self): return 'IsReturning()' @memoized() class IsAborting(Filter): """ True when aborting. (E.g. Control-C pressed.) """ def __call__(self, cli): return cli.is_aborting def __repr__(self): return 'IsAborting()' @memoized() class IsExiting(Filter): """ True when exiting. (E.g. Control-D pressed.) """ def __call__(self, cli): return cli.is_exiting def __repr__(self): return 'IsExiting()' @memoized() class IsDone(Filter): """ True when the CLI is returning, aborting or exiting. """ def __call__(self, cli): return cli.is_done def __repr__(self): return 'IsDone()' @memoized() class RendererHeightIsKnown(Filter): """ Only True when the renderer knows it's real height. (On VT100 terminals, we have to wait for a CPR response, before we can be sure of the available height between the cursor position and the bottom of the terminal. And usually it's nicer to wait with drawing bottom toolbars until we receive the height, in order to avoid flickering -- first drawing somewhere in the middle, and then again at the bottom.) """ def __call__(self, cli): return cli.renderer.height_is_known def __repr__(self): return 'RendererHeightIsKnown()' @memoized() class InEditingMode(Filter): """ Check whether a given editing mode is active. (Vi or Emacs.) """ def __init__(self, editing_mode): self._editing_mode = editing_mode @property def editing_mode(self): " The given editing mode. (Read-only) " return self._editing_mode def __call__(self, cli): return cli.editing_mode == self.editing_mode def __repr__(self): return 'InEditingMode(%r)' % (self.editing_mode, ) @memoized() class ViMode(Filter): def __call__(self, cli): return cli.editing_mode == EditingMode.VI def __repr__(self): return 'ViMode()' @memoized() class ViNavigationMode(Filter): """ Active when the set for Vi navigation key bindings are active. """ def __call__(self, cli): if (cli.editing_mode != EditingMode.VI or cli.vi_state.operator_func or cli.vi_state.waiting_for_digraph or cli.current_buffer.selection_state): return False return (cli.vi_state.input_mode == ViInputMode.NAVIGATION or cli.current_buffer.read_only()) def __repr__(self): return 'ViNavigationMode()' @memoized() class ViInsertMode(Filter): def __call__(self, cli): if (cli.editing_mode != EditingMode.VI or cli.vi_state.operator_func or cli.vi_state.waiting_for_digraph or cli.current_buffer.selection_state or cli.current_buffer.read_only()): return False return cli.vi_state.input_mode == ViInputMode.INSERT def __repr__(self): return 'ViInputMode()' @memoized() class ViInsertMultipleMode(Filter): def __call__(self, cli): if (cli.editing_mode != EditingMode.VI or cli.vi_state.operator_func or cli.vi_state.waiting_for_digraph or cli.current_buffer.selection_state or cli.current_buffer.read_only()): return False return cli.vi_state.input_mode == ViInputMode.INSERT_MULTIPLE def __repr__(self): return 'ViInsertMultipleMode()' @memoized() class ViReplaceMode(Filter): def __call__(self, cli): if (cli.editing_mode != EditingMode.VI or cli.vi_state.operator_func or cli.vi_state.waiting_for_digraph or cli.current_buffer.selection_state or cli.current_buffer.read_only()): return False return cli.vi_state.input_mode == ViInputMode.REPLACE def __repr__(self): return 'ViReplaceMode()' @memoized() class ViSelectionMode(Filter): def __call__(self, cli): if cli.editing_mode != EditingMode.VI: return False return bool(cli.current_buffer.selection_state) def __repr__(self): return 'ViSelectionMode()' @memoized() class ViWaitingForTextObjectMode(Filter): def __call__(self, cli): if cli.editing_mode != EditingMode.VI: return False return cli.vi_state.operator_func is not None def __repr__(self): return 'ViWaitingForTextObjectMode()' @memoized() class ViDigraphMode(Filter): def __call__(self, cli): if cli.editing_mode != EditingMode.VI: return False return cli.vi_state.waiting_for_digraph def __repr__(self): return 'ViDigraphMode()' @memoized() class EmacsMode(Filter): " When the Emacs bindings are active. " def __call__(self, cli): return cli.editing_mode == EditingMode.EMACS def __repr__(self): return 'EmacsMode()' @memoized() class EmacsInsertMode(Filter): def __call__(self, cli): if (cli.editing_mode != EditingMode.EMACS or cli.current_buffer.selection_state or cli.current_buffer.read_only()): return False return True def __repr__(self): return 'EmacsInsertMode()' @memoized() class EmacsSelectionMode(Filter): def __call__(self, cli): return (cli.editing_mode == EditingMode.EMACS and cli.current_buffer.selection_state) def __repr__(self): return 'EmacsSelectionMode()' prompt_toolkit-1.0.15/prompt_toolkit/filters/__init__.py0000664000175000017500000000323313136130420025155 0ustar jonathanjonathan00000000000000""" Filters decide whether something is active or not (they decide about a boolean state). This is used to enable/disable features, like key bindings, parts of the layout and other stuff. For instance, we could have a `HasSearch` filter attached to some part of the layout, in order to show that part of the user interface only while the user is searching. Filters are made to avoid having to attach callbacks to all event in order to propagate state. However, they are lazy, they don't automatically propagate the state of what they are observing. Only when a filter is called (it's actually a callable), it will calculate its value. So, its not really reactive programming, but it's made to fit for this framework. One class of filters observe a `CommandLineInterface` instance. However, they are not attached to such an instance. (We have to pass this instance to the filter when calling it.) The reason for this is to allow declarative programming: for key bindings, we can attach a filter to a key binding without knowing yet which `CommandLineInterface` instance it will observe in the end. Examples are `HasSearch` or `IsExiting`. Another class of filters doesn't take anything as input. And a third class of filters are universal, for instance `Always` and `Never`. It is impossible to mix the first and the second class, because that would mean mixing filters with a different signature. Filters can be chained using ``&`` and ``|`` operations, and inverted using the ``~`` operator, for instance:: filter = HasFocus('default') & ~ HasSelection() """ from __future__ import unicode_literals from .base import * from .cli import * from .types import * from .utils import * prompt_toolkit-1.0.15/prompt_toolkit/filters/base.py0000664000175000017500000001367713136130420024345 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from prompt_toolkit.utils import test_callable_args __all__ = ( 'Filter', 'Never', 'Always', 'Condition', ) class Filter(with_metaclass(ABCMeta, object)): """ Filter to activate/deactivate a feature, depending on a condition. The return value of ``__call__`` will tell if the feature should be active. """ @abstractmethod def __call__(self, *a, **kw): """ The actual call to evaluate the filter. """ return True def __and__(self, other): """ Chaining of filters using the & operator. """ return _and_cache[self, other] def __or__(self, other): """ Chaining of filters using the | operator. """ return _or_cache[self, other] def __invert__(self): """ Inverting of filters using the ~ operator. """ return _invert_cache[self] def __bool__(self): """ By purpose, we don't allow bool(...) operations directly on a filter, because because the meaning is ambigue. Executing a filter has to be done always by calling it. Providing defaults for `None` values should be done through an `is None` check instead of for instance ``filter1 or Always()``. """ raise TypeError __nonzero__ = __bool__ # For Python 2. def test_args(self, *args): """ Test whether this filter can be called with the following argument list. """ return test_callable_args(self.__call__, args) class _AndCache(dict): """ Cache for And operation between filters. (Filter classes are stateless, so we can reuse them.) Note: This could be a memory leak if we keep creating filters at runtime. If that is True, the filters should be weakreffed (not the tuple of filters), and tuples should be removed when one of these filters is removed. In practise however, there is a finite amount of filters. """ def __missing__(self, filters): a, b = filters assert isinstance(b, Filter), 'Expecting filter, got %r' % b if isinstance(b, Always) or isinstance(a, Never): return a elif isinstance(b, Never) or isinstance(a, Always): return b result = _AndList(filters) self[filters] = result return result class _OrCache(dict): """ Cache for Or operation between filters. """ def __missing__(self, filters): a, b = filters assert isinstance(b, Filter), 'Expecting filter, got %r' % b if isinstance(b, Always) or isinstance(a, Never): return b elif isinstance(b, Never) or isinstance(a, Always): return a result = _OrList(filters) self[filters] = result return result class _InvertCache(dict): """ Cache for inversion operator. """ def __missing__(self, filter): result = _Invert(filter) self[filter] = result return result _and_cache = _AndCache() _or_cache = _OrCache() _invert_cache = _InvertCache() class _AndList(Filter): """ Result of &-operation between several filters. """ def __init__(self, filters): all_filters = [] for f in filters: if isinstance(f, _AndList): # Turn nested _AndLists into one. all_filters.extend(f.filters) else: all_filters.append(f) self.filters = all_filters def test_args(self, *args): return all(f.test_args(*args) for f in self.filters) def __call__(self, *a, **kw): return all(f(*a, **kw) 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): all_filters = [] for f in filters: if isinstance(f, _OrList): # Turn nested _OrLists into one. all_filters.extend(f.filters) else: all_filters.append(f) self.filters = all_filters def test_args(self, *args): return all(f.test_args(*args) for f in self.filters) def __call__(self, *a, **kw): return any(f(*a, **kw) 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, *a, **kw): return not self.filter(*a, **kw) def __repr__(self): return '~%r' % self.filter def test_args(self, *args): return self.filter.test_args(*args) class Always(Filter): """ Always enable feature. """ def __call__(self, *a, **kw): return True def __invert__(self): return Never() class Never(Filter): """ Never enable feature. """ def __call__(self, *a, **kw): return False def __invert__(self): return Always() class Condition(Filter): """ Turn any callable (which takes a cli and returns a boolean) into a Filter. This can be used as a decorator:: @Condition def feature_is_active(cli): # `feature_is_active` becomes a Filter. return True :param func: Callable which takes either a :class:`~prompt_toolkit.interface.CommandLineInterface` or nothing and returns a boolean. (Depending on what it takes, this will become a :class:`.Filter` or :class:`~prompt_toolkit.filters.CLIFilter`.) """ def __init__(self, func): assert callable(func) self.func = func def __call__(self, *a, **kw): return self.func(*a, **kw) def __repr__(self): return 'Condition(%r)' % self.func def test_args(self, *a): return test_callable_args(self.func, a) prompt_toolkit-1.0.15/prompt_toolkit/filters/utils.py0000664000175000017500000000175213136130420024562 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .base import Always, Never from .types import SimpleFilter, CLIFilter __all__ = ( 'to_cli_filter', 'to_simple_filter', ) _always = Always() _never = Never() def to_simple_filter(bool_or_filter): """ Accept both booleans and CLIFilters as input and turn it into a SimpleFilter. """ if not isinstance(bool_or_filter, (bool, SimpleFilter)): raise TypeError('Expecting a bool or a SimpleFilter instance. Got %r' % bool_or_filter) return { True: _always, False: _never, }.get(bool_or_filter, bool_or_filter) def to_cli_filter(bool_or_filter): """ Accept both booleans and CLIFilters as input and turn it into a CLIFilter. """ if not isinstance(bool_or_filter, (bool, CLIFilter)): raise TypeError('Expecting a bool or a CLIFilter instance. Got %r' % bool_or_filter) return { True: _always, False: _never, }.get(bool_or_filter, bool_or_filter) prompt_toolkit-1.0.15/prompt_toolkit/terminal/0000775000175000017500000000000013136335632023222 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/terminal/win32_output.py0000664000175000017500000004623213136130420026151 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from ctypes import windll, byref, ArgumentError, c_char, c_long, c_ulong, c_uint, pointer from ctypes.wintypes import DWORD from prompt_toolkit.renderer import Output from prompt_toolkit.styles import ANSI_COLOR_NAMES from prompt_toolkit.win32_types import CONSOLE_SCREEN_BUFFER_INFO, STD_OUTPUT_HANDLE, STD_INPUT_HANDLE, COORD, SMALL_RECT import os import six __all__ = ( 'Win32Output', ) def _coord_byval(coord): """ Turns a COORD object into a c_long. This will cause it to be passed by value instead of by reference. (That is what I think at least.) When runing ``ptipython`` is run (only with IPython), we often got the following error:: Error in 'SetConsoleCursorPosition'. ArgumentError("argument 2: : wrong type",) argument 2: : wrong type It was solved by turning ``COORD`` parameters into a ``c_long`` like this. More info: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx """ return c_long(coord.Y * 0x10000 | coord.X & 0xFFFF) #: If True: write the output of the renderer also to the following file. This #: is very useful for debugging. (e.g.: to see that we don't write more bytes #: than required.) _DEBUG_RENDER_OUTPUT = False _DEBUG_RENDER_OUTPUT_FILENAME = r'prompt-toolkit-windows-output.log' class NoConsoleScreenBufferError(Exception): """ Raised when the application is not running inside a Windows Console, but the user tries to instantiate Win32Output. """ def __init__(self): # Are we running in 'xterm' on Windows, like git-bash for instance? xterm = 'xterm' in os.environ.get('TERM', '') if xterm: message = ('Found %s, while expecting a Windows console. ' 'Maybe try to run this program using "winpty" ' 'or run it in cmd.exe instead. Or otherwise, ' 'in case of Cygwin, use the Python executable ' 'that is compiled for Cygwin.' % os.environ['TERM']) else: message = 'No Windows console found. Are you running cmd.exe?' super(NoConsoleScreenBufferError, self).__init__(message) class Win32Output(Output): """ I/O abstraction for rendering to Windows consoles. (cmd.exe and similar.) """ def __init__(self, stdout, use_complete_width=False): self.use_complete_width = use_complete_width self._buffer = [] self.stdout = stdout self.hconsole = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) self._in_alternate_screen = False self.color_lookup_table = ColorLookupTable() # Remember the default console colors. info = self.get_win32_screen_buffer_info() self.default_attrs = info.wAttributes if info else 15 if _DEBUG_RENDER_OUTPUT: self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, 'ab') def fileno(self): " Return file descriptor. " return self.stdout.fileno() def encoding(self): " Return encoding used for stdout. " return self.stdout.encoding def write(self, data): self._buffer.append(data) def write_raw(self, data): " For win32, there is no difference between write and write_raw. " self.write(data) def get_size(self): from prompt_toolkit.layout.screen import Size info = self.get_win32_screen_buffer_info() # We take the width of the *visible* region as the size. Not the width # of the complete screen buffer. (Unless use_complete_width has been # set.) if self.use_complete_width: width = info.dwSize.X else: width = info.srWindow.Right - info.srWindow.Left height = info.srWindow.Bottom - info.srWindow.Top + 1 # We avoid the right margin, windows will wrap otherwise. maxwidth = info.dwSize.X - 1 width = min(maxwidth, width) # Create `Size` object. return Size(rows=height, columns=width) def _winapi(self, func, *a, **kw): """ Flush and call win API function. """ self.flush() if _DEBUG_RENDER_OUTPUT: self.LOG.write(('%r' % func.__name__).encode('utf-8') + b'\n') self.LOG.write(b' ' + ', '.join(['%r' % i for i in a]).encode('utf-8') + b'\n') self.LOG.write(b' ' + ', '.join(['%r' % type(i) for i in a]).encode('utf-8') + b'\n') self.LOG.flush() try: return func(*a, **kw) except ArgumentError as e: if _DEBUG_RENDER_OUTPUT: self.LOG.write((' Error in %r %r %s\n' % (func.__name__, e, e)).encode('utf-8')) def get_win32_screen_buffer_info(self): """ Return Screen buffer info. """ # NOTE: We don't call the `GetConsoleScreenBufferInfo` API through # `self._winapi`. Doing so causes Python to crash on certain 64bit # Python versions. (Reproduced with 64bit Python 2.7.6, on Windows # 10). It is not clear why. Possibly, it has to do with passing # these objects as an argument, or through *args. # The Python documentation contains the following - possibly related - warning: # ctypes does not support passing unions or structures with # bit-fields to functions by value. While this may work on 32-bit # x86, it's not guaranteed by the library to work in the general # case. Unions and structures with bit-fields should always be # passed to functions by pointer. # Also see: # - https://github.com/ipython/ipython/issues/10070 # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/406 # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/86 self.flush() sbinfo = CONSOLE_SCREEN_BUFFER_INFO() success = windll.kernel32.GetConsoleScreenBufferInfo(self.hconsole, byref(sbinfo)) # success = self._winapi(windll.kernel32.GetConsoleScreenBufferInfo, # self.hconsole, byref(sbinfo)) if success: return sbinfo else: raise NoConsoleScreenBufferError def set_title(self, title): """ Set terminal title. """ assert isinstance(title, six.text_type) self._winapi(windll.kernel32.SetConsoleTitleW, title) def clear_title(self): self._winapi(windll.kernel32.SetConsoleTitleW, '') def erase_screen(self): start = COORD(0, 0) sbinfo = self.get_win32_screen_buffer_info() length = sbinfo.dwSize.X * sbinfo.dwSize.Y self.cursor_goto(row=0, column=0) self._erase(start, length) def erase_down(self): sbinfo = self.get_win32_screen_buffer_info() size = sbinfo.dwSize start = sbinfo.dwCursorPosition length = ((size.X - size.X) + size.X * (size.Y - sbinfo.dwCursorPosition.Y)) self._erase(start, length) def erase_end_of_line(self): """ """ sbinfo = self.get_win32_screen_buffer_info() start = sbinfo.dwCursorPosition length = sbinfo.dwSize.X - sbinfo.dwCursorPosition.X self._erase(start, length) def _erase(self, start, length): chars_written = c_ulong() self._winapi(windll.kernel32.FillConsoleOutputCharacterA, self.hconsole, c_char(b' '), DWORD(length), _coord_byval(start), byref(chars_written)) # Reset attributes. sbinfo = self.get_win32_screen_buffer_info() self._winapi(windll.kernel32.FillConsoleOutputAttribute, self.hconsole, sbinfo.wAttributes, length, _coord_byval(start), byref(chars_written)) def reset_attributes(self): " Reset the console foreground/background color. " self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, self.default_attrs) def set_attributes(self, attrs): fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs # Start from the default attributes. attrs = self.default_attrs # Override the last four bits: foreground color. if fgcolor is not None: attrs = attrs & ~0xf attrs |= self.color_lookup_table.lookup_fg_color(fgcolor) # Override the next four bits: background color. if bgcolor is not None: attrs = attrs & ~0xf0 attrs |= self.color_lookup_table.lookup_bg_color(bgcolor) # Reverse: swap these four bits groups. if reverse: attrs = (attrs & ~0xff) | ((attrs & 0xf) << 4) | ((attrs & 0xf0) >> 4) self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, attrs) def disable_autowrap(self): # Not supported by Windows. pass def enable_autowrap(self): # Not supported by Windows. pass def cursor_goto(self, row=0, column=0): pos = COORD(x=column, y=row) self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)) def cursor_up(self, amount): sr = self.get_win32_screen_buffer_info().dwCursorPosition pos = COORD(sr.X, sr.Y - amount) self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)) def cursor_down(self, amount): self.cursor_up(-amount) def cursor_forward(self, amount): sr = self.get_win32_screen_buffer_info().dwCursorPosition # assert sr.X + amount >= 0, 'Negative cursor position: x=%r amount=%r' % (sr.X, amount) pos = COORD(max(0, sr.X + amount), sr.Y) self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)) def cursor_backward(self, amount): self.cursor_forward(-amount) def flush(self): """ Write to output stream and flush. """ if not self._buffer: # Only flush stdout buffer. (It could be that Python still has # something in its buffer. -- We want to be sure to print that in # the correct color.) self.stdout.flush() return data = ''.join(self._buffer) if _DEBUG_RENDER_OUTPUT: self.LOG.write(('%r' % data).encode('utf-8') + b'\n') self.LOG.flush() # Print characters one by one. This appears to be the best soluton # in oder to avoid traces of vertical lines when the completion # menu disappears. for b in data: written = DWORD() retval = windll.kernel32.WriteConsoleW(self.hconsole, b, 1, byref(written), None) assert retval != 0 self._buffer = [] def get_rows_below_cursor_position(self): info = self.get_win32_screen_buffer_info() return info.srWindow.Bottom - info.dwCursorPosition.Y + 1 def scroll_buffer_to_prompt(self): """ To be called before drawing the prompt. This should scroll the console to left, with the cursor at the bottom (if possible). """ # Get current window size info = self.get_win32_screen_buffer_info() sr = info.srWindow cursor_pos = info.dwCursorPosition result = SMALL_RECT() # Scroll to the left. result.Left = 0 result.Right = sr.Right - sr.Left # Scroll vertical win_height = sr.Bottom - sr.Top if 0 < sr.Bottom - cursor_pos.Y < win_height - 1: # no vertical scroll if cursor already on the screen result.Bottom = sr.Bottom else: result.Bottom = max(win_height, cursor_pos.Y) result.Top = result.Bottom - win_height # Scroll API self._winapi(windll.kernel32.SetConsoleWindowInfo, self.hconsole, True, byref(result)) def enter_alternate_screen(self): """ Go to alternate screen buffer. """ if not self._in_alternate_screen: GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 # Create a new console buffer and activate that one. handle = self._winapi(windll.kernel32.CreateConsoleScreenBuffer, GENERIC_READ|GENERIC_WRITE, DWORD(0), None, DWORD(1), None) self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, handle) self.hconsole = handle self._in_alternate_screen = True def quit_alternate_screen(self): """ Make stdout again the active buffer. """ if self._in_alternate_screen: stdout = self._winapi(windll.kernel32.GetStdHandle, STD_OUTPUT_HANDLE) self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, stdout) self._winapi(windll.kernel32.CloseHandle, self.hconsole) self.hconsole = stdout self._in_alternate_screen = False def enable_mouse_support(self): ENABLE_MOUSE_INPUT = 0x10 handle = windll.kernel32.GetStdHandle(STD_INPUT_HANDLE) original_mode = DWORD() self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value | ENABLE_MOUSE_INPUT) def disable_mouse_support(self): ENABLE_MOUSE_INPUT = 0x10 handle = windll.kernel32.GetStdHandle(STD_INPUT_HANDLE) original_mode = DWORD() self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value & ~ ENABLE_MOUSE_INPUT) def hide_cursor(self): pass def show_cursor(self): pass @classmethod def win32_refresh_window(cls): """ Call win32 API to refresh the whole Window. This is sometimes necessary when the application paints background for completion menus. When the menu disappears, it leaves traces due to a bug in the Windows Console. Sending a repaint request solves it. """ # Get console handle handle = windll.kernel32.GetConsoleWindow() RDW_INVALIDATE = 0x0001 windll.user32.RedrawWindow(handle, None, None, c_uint(RDW_INVALIDATE)) class FOREGROUND_COLOR: BLACK = 0x0000 BLUE = 0x0001 GREEN = 0x0002 CYAN = 0x0003 RED = 0x0004 MAGENTA = 0x0005 YELLOW = 0x0006 GRAY = 0x0007 INTENSITY = 0x0008 # Foreground color is intensified. class BACKROUND_COLOR: BLACK = 0x0000 BLUE = 0x0010 GREEN = 0x0020 CYAN = 0x0030 RED = 0x0040 MAGENTA = 0x0050 YELLOW = 0x0060 GRAY = 0x0070 INTENSITY = 0x0080 # Background color is intensified. def _create_ansi_color_dict(color_cls): " Create a table that maps the 16 named ansi colors to their Windows code. " return { 'ansidefault': color_cls.BLACK, 'ansiblack': color_cls.BLACK, 'ansidarkgray': color_cls.BLACK | color_cls.INTENSITY, 'ansilightgray': color_cls.GRAY, 'ansiwhite': color_cls.GRAY | color_cls.INTENSITY, # Low intensity. 'ansidarkred': color_cls.RED, 'ansidarkgreen': color_cls.GREEN, 'ansibrown': color_cls.YELLOW, 'ansidarkblue': color_cls.BLUE, 'ansipurple': color_cls.MAGENTA, 'ansiteal': color_cls.CYAN, # High intensity. 'ansired': color_cls.RED | color_cls.INTENSITY, 'ansigreen': color_cls.GREEN | color_cls.INTENSITY, 'ansiyellow': color_cls.YELLOW | color_cls.INTENSITY, 'ansiblue': color_cls.BLUE | color_cls.INTENSITY, 'ansifuchsia': color_cls.MAGENTA | color_cls.INTENSITY, 'ansiturquoise': color_cls.CYAN | color_cls.INTENSITY, } FG_ANSI_COLORS = _create_ansi_color_dict(FOREGROUND_COLOR) BG_ANSI_COLORS = _create_ansi_color_dict(BACKROUND_COLOR) assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) class ColorLookupTable(object): """ Inspired by pygments/formatters/terminal256.py """ def __init__(self): self._win32_colors = self._build_color_table() self.best_match = {} # Cache @staticmethod def _build_color_table(): """ Build an RGB-to-256 color conversion table """ FG = FOREGROUND_COLOR BG = BACKROUND_COLOR return [ (0x00, 0x00, 0x00, FG.BLACK, BG.BLACK), (0x00, 0x00, 0xaa, FG.BLUE, BG.BLUE), (0x00, 0xaa, 0x00, FG.GREEN, BG.GREEN), (0x00, 0xaa, 0xaa, FG.CYAN, BG.CYAN), (0xaa, 0x00, 0x00, FG.RED, BG.RED), (0xaa, 0x00, 0xaa, FG.MAGENTA, BG.MAGENTA), (0xaa, 0xaa, 0x00, FG.YELLOW, BG.YELLOW), (0x88, 0x88, 0x88, FG.GRAY, BG.GRAY), (0x44, 0x44, 0xff, FG.BLUE | FG.INTENSITY, BG.BLUE | BG.INTENSITY), (0x44, 0xff, 0x44, FG.GREEN | FG.INTENSITY, BG.GREEN | BG.INTENSITY), (0x44, 0xff, 0xff, FG.CYAN | FG.INTENSITY, BG.CYAN | BG.INTENSITY), (0xff, 0x44, 0x44, FG.RED | FG.INTENSITY, BG.RED | BG.INTENSITY), (0xff, 0x44, 0xff, FG.MAGENTA | FG.INTENSITY, BG.MAGENTA | BG.INTENSITY), (0xff, 0xff, 0x44, FG.YELLOW | FG.INTENSITY, BG.YELLOW | BG.INTENSITY), (0x44, 0x44, 0x44, FG.BLACK | FG.INTENSITY, BG.BLACK | BG.INTENSITY), (0xff, 0xff, 0xff, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY), ] def _closest_color(self, r, g, b): distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) fg_match = 0 bg_match = 0 for r_, g_, b_, fg_, bg_ in self._win32_colors: rd = r - r_ gd = g - g_ bd = b - b_ d = rd * rd + gd * gd + bd * bd if d < distance: fg_match = fg_ bg_match = bg_ distance = d return fg_match, bg_match def _color_indexes(self, color): indexes = self.best_match.get(color, None) if indexes is None: try: rgb = int(str(color), 16) except ValueError: rgb = 0 r = (rgb >> 16) & 0xff g = (rgb >> 8) & 0xff b = rgb & 0xff indexes = self._closest_color(r, g, b) self.best_match[color] = indexes return indexes def lookup_fg_color(self, fg_color): """ Return the color for use in the `windll.kernel32.SetConsoleTextAttribute` API call. :param fg_color: Foreground as text. E.g. 'ffffff' or 'red' """ # Foreground. if fg_color in FG_ANSI_COLORS: return FG_ANSI_COLORS[fg_color] else: return self._color_indexes(fg_color)[0] def lookup_bg_color(self, bg_color): """ Return the color for use in the `windll.kernel32.SetConsoleTextAttribute` API call. :param bg_color: Background as text. E.g. 'ffffff' or 'red' """ # Background. if bg_color in BG_ANSI_COLORS: return BG_ANSI_COLORS[bg_color] else: return self._color_indexes(bg_color)[1] prompt_toolkit-1.0.15/prompt_toolkit/terminal/vt100_output.py0000664000175000017500000004661313136130420026064 0ustar jonathanjonathan00000000000000""" Output for vt100 terminals. A lot of thanks, regarding outputting of colors, goes to the Pygments project: (We don't rely on Pygments anymore, because many things are very custom, and everything has been highly optimized.) http://pygments.org/ """ from __future__ import unicode_literals from prompt_toolkit.filters import to_simple_filter, Condition from prompt_toolkit.layout.screen import Size from prompt_toolkit.renderer import Output from prompt_toolkit.styles import ANSI_COLOR_NAMES from six.moves import range import array import errno import os import six __all__ = ( 'Vt100_Output', ) FG_ANSI_COLORS = { 'ansidefault': 39, # Low intensity. 'ansiblack': 30, 'ansidarkred': 31, 'ansidarkgreen': 32, 'ansibrown': 33, 'ansidarkblue': 34, 'ansipurple': 35, 'ansiteal': 36, 'ansilightgray': 37, # High intensity. 'ansidarkgray': 90, 'ansired': 91, 'ansigreen': 92, 'ansiyellow': 93, 'ansiblue': 94, 'ansifuchsia': 95, 'ansiturquoise': 96, 'ansiwhite': 97, } BG_ANSI_COLORS = { 'ansidefault': 49, # Low intensity. 'ansiblack': 40, 'ansidarkred': 41, 'ansidarkgreen': 42, 'ansibrown': 43, 'ansidarkblue': 44, 'ansipurple': 45, 'ansiteal': 46, 'ansilightgray': 47, # High intensity. 'ansidarkgray': 100, 'ansired': 101, 'ansigreen': 102, 'ansiyellow': 103, 'ansiblue': 104, 'ansifuchsia': 105, 'ansiturquoise': 106, 'ansiwhite': 107, } ANSI_COLORS_TO_RGB = { 'ansidefault': (0x00, 0x00, 0x00), # Don't use, 'default' doesn't really have a value. 'ansiblack': (0x00, 0x00, 0x00), 'ansidarkgray': (0x7f, 0x7f, 0x7f), 'ansiwhite': (0xff, 0xff, 0xff), 'ansilightgray': (0xe5, 0xe5, 0xe5), # Low intensity. 'ansidarkred': (0xcd, 0x00, 0x00), 'ansidarkgreen': (0x00, 0xcd, 0x00), 'ansibrown': (0xcd, 0xcd, 0x00), 'ansidarkblue': (0x00, 0x00, 0xcd), 'ansipurple': (0xcd, 0x00, 0xcd), 'ansiteal': (0x00, 0xcd, 0xcd), # High intensity. 'ansired': (0xff, 0x00, 0x00), 'ansigreen': (0x00, 0xff, 0x00), 'ansiyellow': (0xff, 0xff, 0x00), 'ansiblue': (0x00, 0x00, 0xff), 'ansifuchsia': (0xff, 0x00, 0xff), 'ansiturquoise': (0x00, 0xff, 0xff), } assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES) def _get_closest_ansi_color(r, g, b, exclude=()): """ Find closest ANSI color. Return it by name. :param r: Red (Between 0 and 255.) :param g: Green (Between 0 and 255.) :param b: Blue (Between 0 and 255.) :param exclude: A tuple of color names to exclude. (E.g. ``('ansired', )``.) """ assert isinstance(exclude, tuple) # When we have a bit of saturation, avoid the gray-like colors, otherwise, # too often the distance to the gray color is less. saturation = abs(r - g) + abs(g - b) + abs(b - r) # Between 0..510 if saturation > 30: exclude += ('ansilightgray', 'ansidarkgray', 'ansiwhite', 'ansiblack') # Take the closest color. # (Thanks to Pygments for this part.) distance = 257*257*3 # "infinity" (>distance from #000000 to #ffffff) match = 'ansidefault' for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items(): if name != 'ansidefault' and name not in exclude: d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 if d < distance: match = name distance = d return match class _16ColorCache(dict): """ Cache which maps (r, g, b) tuples to 16 ansi colors. :param bg: Cache for background colors, instead of foreground. """ def __init__(self, bg=False): assert isinstance(bg, bool) self.bg = bg def get_code(self, value, exclude=()): """ Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for a given (r,g,b) value. """ key = (value, exclude) if key not in self: self[key] = self._get(value, exclude) return self[key] def _get(self, value, exclude=()): r, g, b = value match = _get_closest_ansi_color(r, g, b, exclude=exclude) # Turn color name into code. if self.bg: code = BG_ANSI_COLORS[match] else: code = FG_ANSI_COLORS[match] self[value] = code return code, match class _256ColorCache(dict): """ Cach which maps (r, g, b) tuples to 256 colors. """ def __init__(self): # Build color table. colors = [] # colors 0..15: 16 basic colors colors.append((0x00, 0x00, 0x00)) # 0 colors.append((0xcd, 0x00, 0x00)) # 1 colors.append((0x00, 0xcd, 0x00)) # 2 colors.append((0xcd, 0xcd, 0x00)) # 3 colors.append((0x00, 0x00, 0xee)) # 4 colors.append((0xcd, 0x00, 0xcd)) # 5 colors.append((0x00, 0xcd, 0xcd)) # 6 colors.append((0xe5, 0xe5, 0xe5)) # 7 colors.append((0x7f, 0x7f, 0x7f)) # 8 colors.append((0xff, 0x00, 0x00)) # 9 colors.append((0x00, 0xff, 0x00)) # 10 colors.append((0xff, 0xff, 0x00)) # 11 colors.append((0x5c, 0x5c, 0xff)) # 12 colors.append((0xff, 0x00, 0xff)) # 13 colors.append((0x00, 0xff, 0xff)) # 14 colors.append((0xff, 0xff, 0xff)) # 15 # colors 16..232: the 6x6x6 color cube valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff) for i in range(217): r = valuerange[(i // 36) % 6] g = valuerange[(i // 6) % 6] b = valuerange[i % 6] colors.append((r, g, b)) # colors 233..253: grayscale for i in range(1, 22): v = 8 + i * 10 colors.append((v, v, v)) self.colors = colors def __missing__(self, value): r, g, b = value # Find closest color. # (Thanks to Pygments for this!) distance = 257*257*3 # "infinity" (>distance from #000000 to #ffffff) match = 0 for i, (r2, g2, b2) in enumerate(self.colors): d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 if d < distance: match = i distance = d # Turn color name into code. self[value] = match return match _16_fg_colors = _16ColorCache(bg=False) _16_bg_colors = _16ColorCache(bg=True) _256_colors = _256ColorCache() class _EscapeCodeCache(dict): """ Cache for VT100 escape codes. It maps (fgcolor, bgcolor, bold, underline, reverse) tuples to VT100 escape sequences. :param true_color: When True, use 24bit colors instead of 256 colors. """ def __init__(self, true_color=False, ansi_colors_only=False): assert isinstance(true_color, bool) self.true_color = true_color self.ansi_colors_only = to_simple_filter(ansi_colors_only) def __missing__(self, attrs): fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs parts = [] parts.extend(self._colors_to_code(fgcolor, bgcolor)) if bold: parts.append('1') if italic: parts.append('3') if blink: parts.append('5') if underline: parts.append('4') if reverse: parts.append('7') if parts: result = '\x1b[0;' + ';'.join(parts) + 'm' else: result = '\x1b[0m' self[attrs] = result return result def _color_name_to_rgb(self, color): " Turn 'ffffff', into (0xff, 0xff, 0xff). " try: rgb = int(color, 16) except ValueError: raise else: r = (rgb >> 16) & 0xff g = (rgb >> 8) & 0xff b = rgb & 0xff return r, g, b def _colors_to_code(self, fg_color, bg_color): " Return a tuple with the vt100 values that represent this color. " # When requesting ANSI colors only, and both fg/bg color were converted # to ANSI, ensure that the foreground and background color are not the # same. (Unless they were explicitely defined to be the same color.) fg_ansi = [()] def get(color, bg): table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS if color is None: return () # 16 ANSI colors. (Given by name.) elif color in table: return (table[color], ) # RGB colors. (Defined as 'ffffff'.) else: try: rgb = self._color_name_to_rgb(color) except ValueError: return () # When only 16 colors are supported, use that. if self.ansi_colors_only(): if bg: # Background. if fg_color != bg_color: exclude = (fg_ansi[0], ) else: exclude = () code, name = _16_bg_colors.get_code(rgb, exclude=exclude) return (code, ) else: # Foreground. code, name = _16_fg_colors.get_code(rgb) fg_ansi[0] = name return (code, ) # True colors. (Only when this feature is enabled.) elif self.true_color: r, g, b = rgb return (48 if bg else 38, 2, r, g, b) # 256 RGB colors. else: return (48 if bg else 38, 5, _256_colors[rgb]) result = [] result.extend(get(fg_color, False)) result.extend(get(bg_color, True)) return map(six.text_type, result) def _get_size(fileno): # Thanks to fabric (fabfile.org), and # http://sqizit.bartletts.id.au/2011/02/14/pseudo-terminals-in-python/ """ Get the size of this pseudo terminal. :param fileno: stdout.fileno() :returns: A (rows, cols) tuple. """ # Inline imports, because these modules are not available on Windows. # (This file is used by ConEmuOutput, which is used on Windows.) import fcntl import termios # Buffer for the C call buf = array.array(b'h' if six.PY2 else u'h', [0, 0, 0, 0]) # Do TIOCGWINSZ (Get) # Note: We should not pass 'True' as a fourth parameter to 'ioctl'. (True # is the default.) This causes segmentation faults on some systems. # See: https://github.com/jonathanslenders/python-prompt-toolkit/pull/364 fcntl.ioctl(fileno, termios.TIOCGWINSZ, buf) # Return rows, cols return buf[0], buf[1] class Vt100_Output(Output): """ :param get_size: A callable which returns the `Size` of the output terminal. :param stdout: Any object with has a `write` and `flush` method + an 'encoding' property. :param true_color: Use 24bit color instead of 256 colors. (Can be a :class:`SimpleFilter`.) When `ansi_colors_only` is set, only 16 colors are used. :param ansi_colors_only: Restrict to 16 ANSI colors only. :param term: The terminal environment variable. (xterm, xterm-256color, linux, ...) :param write_binary: Encode the output before writing it. If `True` (the default), the `stdout` object is supposed to expose an `encoding` attribute. """ def __init__(self, stdout, get_size, true_color=False, ansi_colors_only=None, term=None, write_binary=True): assert callable(get_size) assert term is None or isinstance(term, six.text_type) assert all(hasattr(stdout, a) for a in ('write', 'flush')) if write_binary: assert hasattr(stdout, 'encoding') self._buffer = [] self.stdout = stdout self.write_binary = write_binary self.get_size = get_size self.true_color = to_simple_filter(true_color) self.term = term or 'xterm' # ANSI colors only? if ansi_colors_only is None: # When not given, use the following default. ANSI_COLORS_ONLY = bool(os.environ.get( 'PROMPT_TOOLKIT_ANSI_COLORS_ONLY', False)) @Condition def ansi_colors_only(): return ANSI_COLORS_ONLY or term in ('linux', 'eterm-color') else: ansi_colors_only = to_simple_filter(ansi_colors_only) self.ansi_colors_only = ansi_colors_only # Cache for escape codes. self._escape_code_cache = _EscapeCodeCache(ansi_colors_only=ansi_colors_only) self._escape_code_cache_true_color = _EscapeCodeCache( true_color=True, ansi_colors_only=ansi_colors_only) @classmethod def from_pty(cls, stdout, true_color=False, ansi_colors_only=None, term=None): """ Create an Output class from a pseudo terminal. (This will take the dimensions by reading the pseudo terminal attributes.) """ assert stdout.isatty() def get_size(): rows, columns = _get_size(stdout.fileno()) # If terminal (incorrectly) reports its size as 0, pick a reasonable default. # See https://github.com/ipython/ipython/issues/10071 return Size(rows=(rows or 24), columns=(columns or 80)) return cls(stdout, get_size, true_color=true_color, ansi_colors_only=ansi_colors_only, term=term) def fileno(self): " Return file descriptor. " return self.stdout.fileno() def encoding(self): " Return encoding used for stdout. " return self.stdout.encoding def write_raw(self, data): """ Write raw data to output. """ self._buffer.append(data) def write(self, data): """ Write text to output. (Removes vt100 escape codes. -- used for safely writing text.) """ self._buffer.append(data.replace('\x1b', '?')) def set_title(self, title): """ Set terminal title. """ if self.term not in ('linux', 'eterm-color'): # Not supported by the Linux console. self.write_raw('\x1b]2;%s\x07' % title.replace('\x1b', '').replace('\x07', '')) def clear_title(self): self.set_title('') def erase_screen(self): """ Erases the screen with the background colour and moves the cursor to home. """ self.write_raw('\x1b[2J') def enter_alternate_screen(self): self.write_raw('\x1b[?1049h\x1b[H') def quit_alternate_screen(self): self.write_raw('\x1b[?1049l') def enable_mouse_support(self): self.write_raw('\x1b[?1000h') # Enable urxvt Mouse mode. (For terminals that understand this.) self.write_raw('\x1b[?1015h') # Also enable Xterm SGR mouse mode. (For terminals that understand this.) self.write_raw('\x1b[?1006h') # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr # extensions. def disable_mouse_support(self): self.write_raw('\x1b[?1000l') self.write_raw('\x1b[?1015l') self.write_raw('\x1b[?1006l') def erase_end_of_line(self): """ Erases from the current cursor position to the end of the current line. """ self.write_raw('\x1b[K') def erase_down(self): """ Erases the screen from the current line down to the bottom of the screen. """ self.write_raw('\x1b[J') def reset_attributes(self): self.write_raw('\x1b[0m') def set_attributes(self, attrs): """ Create new style and output. :param attrs: `Attrs` instance. """ if self.true_color() and not self.ansi_colors_only(): self.write_raw(self._escape_code_cache_true_color[attrs]) else: self.write_raw(self._escape_code_cache[attrs]) def disable_autowrap(self): self.write_raw('\x1b[?7l') def enable_autowrap(self): self.write_raw('\x1b[?7h') def enable_bracketed_paste(self): self.write_raw('\x1b[?2004h') def disable_bracketed_paste(self): self.write_raw('\x1b[?2004l') def cursor_goto(self, row=0, column=0): """ Move cursor position. """ self.write_raw('\x1b[%i;%iH' % (row, column)) def cursor_up(self, amount): if amount == 0: pass elif amount == 1: self.write_raw('\x1b[A') else: self.write_raw('\x1b[%iA' % amount) def cursor_down(self, amount): if amount == 0: pass elif amount == 1: # Note: Not the same as '\n', '\n' can cause the window content to # scroll. self.write_raw('\x1b[B') else: self.write_raw('\x1b[%iB' % amount) def cursor_forward(self, amount): if amount == 0: pass elif amount == 1: self.write_raw('\x1b[C') else: self.write_raw('\x1b[%iC' % amount) def cursor_backward(self, amount): if amount == 0: pass elif amount == 1: self.write_raw('\b') # '\x1b[D' else: self.write_raw('\x1b[%iD' % amount) def hide_cursor(self): self.write_raw('\x1b[?25l') def show_cursor(self): self.write_raw('\x1b[?12l\x1b[?25h') # Stop blinking cursor and show. def flush(self): """ Write to output stream and flush. """ if not self._buffer: return data = ''.join(self._buffer) try: # (We try to encode ourself, because that way we can replace # characters that don't exist in the character set, avoiding # UnicodeEncodeError crashes. E.g. u'\xb7' does not appear in 'ascii'.) # My Arch Linux installation of july 2015 reported 'ANSI_X3.4-1968' # for sys.stdout.encoding in xterm. if self.write_binary: if hasattr(self.stdout, 'buffer'): out = self.stdout.buffer # Py3. else: out = self.stdout out.write(data.encode(self.stdout.encoding or 'utf-8', 'replace')) else: self.stdout.write(data) self.stdout.flush() except IOError as e: if e.args and e.args[0] == errno.EINTR: # Interrupted system call. Can happpen in case of a window # resize signal. (Just ignore. The resize handler will render # again anyway.) pass elif e.args and e.args[0] == 0: # This can happen when there is a lot of output and the user # sends a KeyboardInterrupt by pressing Control-C. E.g. in # a Python REPL when we execute "while True: print('test')". # (The `ptpython` REPL uses this `Output` class instead of # `stdout` directly -- in order to be network transparent.) # So, just ignore. pass else: raise self._buffer = [] def ask_for_cpr(self): """ Asks for a cursor position report (CPR). """ self.write_raw('\x1b[6n') self.flush() def bell(self): " Sound bell. " self.write_raw('\a') self.flush() prompt_toolkit-1.0.15/prompt_toolkit/terminal/conemu_output.py0000664000175000017500000000256213136130420026473 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.renderer import Output from .win32_output import Win32Output from .vt100_output import Vt100_Output __all__ = ( 'ConEmuOutput', ) class ConEmuOutput(object): """ ConEmu (Windows) output abstraction. ConEmu is a Windows console application, but it also supports ANSI escape sequences. This output class is actually a proxy to both `Win32Output` and `Vt100_Output`. It uses `Win32Output` for console sizing and scrolling, but all cursor movements and scrolling happens through the `Vt100_Output`. This way, we can have 256 colors in ConEmu and Cmder. Rendering will be even a little faster as well. http://conemu.github.io/ http://gooseberrycreative.com/cmder/ """ def __init__(self, stdout): self.win32_output = Win32Output(stdout) self.vt100_output = Vt100_Output(stdout, lambda: None) def __getattr__(self, name): if name in ('get_size', 'get_rows_below_cursor_position', 'enable_mouse_support', 'disable_mouse_support', 'scroll_buffer_to_prompt', 'get_win32_screen_buffer_info', 'enable_bracketed_paste', 'disable_bracketed_paste'): return getattr(self.win32_output, name) else: return getattr(self.vt100_output, name) Output.register(ConEmuOutput) prompt_toolkit-1.0.15/prompt_toolkit/terminal/win32_input.py0000664000175000017500000003137413136130420025751 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from ctypes import windll, pointer from ctypes.wintypes import DWORD from six.moves import range from prompt_toolkit.key_binding.input_processor import KeyPress from prompt_toolkit.keys import Keys from prompt_toolkit.mouse_events import MouseEventType from prompt_toolkit.win32_types import EventTypes, KEY_EVENT_RECORD, MOUSE_EVENT_RECORD, INPUT_RECORD, STD_INPUT_HANDLE import msvcrt import os import sys import six __all__ = ( 'ConsoleInputReader', 'raw_mode', 'cooked_mode' ) class ConsoleInputReader(object): """ :param recognize_paste: When True, try to discover paste actions and turn the event into a BracketedPaste. """ # Keys with character data. mappings = { b'\x1b': Keys.Escape, b'\x00': Keys.ControlSpace, # Control-Space (Also for Ctrl-@) b'\x01': Keys.ControlA, # Control-A (home) b'\x02': Keys.ControlB, # Control-B (emacs cursor left) b'\x03': Keys.ControlC, # Control-C (interrupt) b'\x04': Keys.ControlD, # Control-D (exit) b'\x05': Keys.ControlE, # Contrel-E (end) b'\x06': Keys.ControlF, # Control-F (cursor forward) b'\x07': Keys.ControlG, # Control-G b'\x08': Keys.ControlH, # Control-H (8) (Identical to '\b') b'\x09': Keys.ControlI, # Control-I (9) (Identical to '\t') b'\x0a': Keys.ControlJ, # Control-J (10) (Identical to '\n') b'\x0b': Keys.ControlK, # Control-K (delete until end of line; vertical tab) b'\x0c': Keys.ControlL, # Control-L (clear; form feed) b'\x0d': Keys.ControlJ, # Control-J NOTE: Windows sends \r instead of # \n when pressing enter. We turn it into \n # to be compatible with other platforms. b'\x0e': Keys.ControlN, # Control-N (14) (history forward) b'\x0f': Keys.ControlO, # Control-O (15) b'\x10': Keys.ControlP, # Control-P (16) (history back) b'\x11': Keys.ControlQ, # Control-Q b'\x12': Keys.ControlR, # Control-R (18) (reverse search) b'\x13': Keys.ControlS, # Control-S (19) (forward search) b'\x14': Keys.ControlT, # Control-T b'\x15': Keys.ControlU, # Control-U b'\x16': Keys.ControlV, # Control-V b'\x17': Keys.ControlW, # Control-W b'\x18': Keys.ControlX, # Control-X b'\x19': Keys.ControlY, # Control-Y (25) b'\x1a': Keys.ControlZ, # Control-Z b'\x1c': Keys.ControlBackslash, # Both Control-\ and Ctrl-| b'\x1d': Keys.ControlSquareClose, # Control-] b'\x1e': Keys.ControlCircumflex, # Control-^ b'\x1f': Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hypen.) b'\x7f': Keys.Backspace, # (127) Backspace } # Keys that don't carry character data. keycodes = { # Home/End 33: Keys.PageUp, 34: Keys.PageDown, 35: Keys.End, 36: Keys.Home, # Arrows 37: Keys.Left, 38: Keys.Up, 39: Keys.Right, 40: Keys.Down, 45: Keys.Insert, 46: Keys.Delete, # F-keys. 112: Keys.F1, 113: Keys.F2, 114: Keys.F3, 115: Keys.F4, 116: Keys.F5, 117: Keys.F6, 118: Keys.F7, 119: Keys.F8, 120: Keys.F9, 121: Keys.F10, 122: Keys.F11, 123: Keys.F12, } LEFT_ALT_PRESSED = 0x0002 RIGHT_ALT_PRESSED = 0x0001 SHIFT_PRESSED = 0x0010 LEFT_CTRL_PRESSED = 0x0008 RIGHT_CTRL_PRESSED = 0x0004 def __init__(self, recognize_paste=True): self._fdcon = None self.recognize_paste = recognize_paste # When stdin is a tty, use that handle, otherwise, create a handle from # CONIN$. if sys.stdin.isatty(): self.handle = windll.kernel32.GetStdHandle(STD_INPUT_HANDLE) else: self._fdcon = os.open('CONIN$', os.O_RDWR | os.O_BINARY) self.handle = msvcrt.get_osfhandle(self._fdcon) def close(self): " Close fdcon. " if self._fdcon is not None: os.close(self._fdcon) def read(self): """ Return a list of `KeyPress` instances. It won't return anything when there was nothing to read. (This function doesn't block.) http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx """ max_count = 2048 # Max events to read at the same time. read = DWORD(0) arrtype = INPUT_RECORD * max_count input_records = arrtype() # Get next batch of input event. windll.kernel32.ReadConsoleInputW( self.handle, pointer(input_records), max_count, pointer(read)) # First, get all the keys from the input buffer, in order to determine # whether we should consider this a paste event or not. all_keys = list(self._get_keys(read, input_records)) if self.recognize_paste and self._is_paste(all_keys): gen = iter(all_keys) for k in gen: # Pasting: if the current key consists of text or \n, turn it # into a BracketedPaste. data = [] while k and (isinstance(k.key, six.text_type) or k.key == Keys.ControlJ): data.append(k.data) try: k = next(gen) except StopIteration: k = None if data: yield KeyPress(Keys.BracketedPaste, ''.join(data)) if k is not None: yield k else: for k in all_keys: yield k def _get_keys(self, read, input_records): """ Generator that yields `KeyPress` objects from the input records. """ for i in range(read.value): ir = input_records[i] # Get the right EventType from the EVENT_RECORD. # (For some reason the Windows console application 'cmder' # [http://gooseberrycreative.com/cmder/] can return '0' for # ir.EventType. -- Just ignore that.) if ir.EventType in EventTypes: ev = getattr(ir.Event, EventTypes[ir.EventType]) # Process if this is a key event. (We also have mouse, menu and # focus events.) if type(ev) == KEY_EVENT_RECORD and ev.KeyDown: for key_press in self._event_to_key_presses(ev): yield key_press elif type(ev) == MOUSE_EVENT_RECORD: for key_press in self._handle_mouse(ev): yield key_press @staticmethod def _is_paste(keys): """ Return `True` when we should consider this list of keys as a paste event. Pasted text on windows will be turned into a `Keys.BracketedPaste` event. (It's not 100% correct, but it is probably the best possible way to detect pasting of text and handle that correctly.) """ # Consider paste when it contains at least one newline and at least one # other character. text_count = 0 newline_count = 0 for k in keys: if isinstance(k.key, six.text_type): text_count += 1 if k.key == Keys.ControlJ: newline_count += 1 return newline_count >= 1 and text_count > 1 def _event_to_key_presses(self, ev): """ For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances. """ assert type(ev) == KEY_EVENT_RECORD and ev.KeyDown result = None u_char = ev.uChar.UnicodeChar ascii_char = u_char.encode('utf-8') # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be latin-1 # encoded. See also: # https://github.com/ipython/ipython/issues/10004 # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389 if u_char == '\x00': if ev.VirtualKeyCode in self.keycodes: result = KeyPress(self.keycodes[ev.VirtualKeyCode], '') else: if ascii_char in self.mappings: if self.mappings[ascii_char] == Keys.ControlJ: u_char = '\n' # Windows sends \n, turn into \r for unix compatibility. result = KeyPress(self.mappings[ascii_char], u_char) else: result = KeyPress(u_char, u_char) # Correctly handle Control-Arrow keys. if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result: if result.key == Keys.Left: result.key = Keys.ControlLeft if result.key == Keys.Right: result.key = Keys.ControlRight if result.key == Keys.Up: result.key = Keys.ControlUp if result.key == Keys.Down: result.key = Keys.ControlDown # Turn 'Tab' into 'BackTab' when shift was pressed. if ev.ControlKeyState & self.SHIFT_PRESSED and result: if result.key == Keys.Tab: result.key = Keys.BackTab # Turn 'Space' into 'ControlSpace' when control was pressed. if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and result.data == ' ': result = KeyPress(Keys.ControlSpace, ' ') # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot # detect this combination. But it's really practical on Windows.) if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and \ result.key == Keys.ControlJ: return [KeyPress(Keys.Escape, ''), result] # Return result. If alt was pressed, prefix the result with an # 'Escape' key, just like unix VT100 terminals do. # NOTE: Only replace the left alt with escape. The right alt key often # acts as altgr and is used in many non US keyboard layouts for # typing some special characters, like a backslash. We don't want # all backslashes to be prefixed with escape. (Esc-\ has a # meaning in E-macs, for instance.) if result: meta_pressed = ev.ControlKeyState & self.LEFT_ALT_PRESSED if meta_pressed: return [KeyPress(Keys.Escape, ''), result] else: return [result] else: return [] def _handle_mouse(self, ev): """ Handle mouse events. Return a list of KeyPress instances. """ FROM_LEFT_1ST_BUTTON_PRESSED = 0x1 result = [] # Check event type. if ev.ButtonState == FROM_LEFT_1ST_BUTTON_PRESSED: # On a key press, generate both the mouse down and up event. for event_type in [MouseEventType.MOUSE_DOWN, MouseEventType.MOUSE_UP]: data = ';'.join([ event_type, str(ev.MousePosition.X), str(ev.MousePosition.Y) ]) result.append(KeyPress(Keys.WindowsMouseEvent, data)) return result class raw_mode(object): """ :: with raw_mode(stdin): ''' the windows terminal is now in 'raw' mode. ''' The ``fileno`` attribute is ignored. This is to be compatble with the `raw_input` method of `.vt100_input`. """ def __init__(self, fileno=None): self.handle = windll.kernel32.GetStdHandle(STD_INPUT_HANDLE) def __enter__(self): # Remember original mode. original_mode = DWORD() windll.kernel32.GetConsoleMode(self.handle, pointer(original_mode)) self.original_mode = original_mode self._patch() def _patch(self): # Set raw ENABLE_ECHO_INPUT = 0x0004 ENABLE_LINE_INPUT = 0x0002 ENABLE_PROCESSED_INPUT = 0x0001 windll.kernel32.SetConsoleMode( self.handle, self.original_mode.value & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) def __exit__(self, *a, **kw): # Restore original mode windll.kernel32.SetConsoleMode(self.handle, self.original_mode) class cooked_mode(raw_mode): """ :: with cooked_mode(stdin): ''' the pseudo-terminal stdin is now used in raw mode ''' """ def _patch(self): # Set cooked. ENABLE_ECHO_INPUT = 0x0004 ENABLE_LINE_INPUT = 0x0002 ENABLE_PROCESSED_INPUT = 0x0001 windll.kernel32.SetConsoleMode( self.handle, self.original_mode.value | (ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) prompt_toolkit-1.0.15/prompt_toolkit/terminal/__init__.py0000664000175000017500000000000013136130420025305 0ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/terminal/vt100_input.py0000664000175000017500000004377413136130420025670 0ustar jonathanjonathan00000000000000""" Parser for VT100 input stream. """ from __future__ import unicode_literals import os import re import six import termios import tty from six.moves import range from ..keys import Keys from ..key_binding.input_processor import KeyPress __all__ = ( 'InputStream', 'raw_mode', 'cooked_mode', ) _DEBUG_RENDERER_INPUT = False _DEBUG_RENDERER_INPUT_FILENAME = 'prompt-toolkit-render-input.log' # Regex matching any CPR response # (Note that we use '\Z' instead of '$', because '$' could include a trailing # newline.) _cpr_response_re = re.compile('^' + re.escape('\x1b[') + r'\d+;\d+R\Z') # Mouse events: # Typical: "Esc[MaB*" Urxvt: "Esc[96;14;13M" and for Xterm SGR: "Esc[<64;85;12M" _mouse_event_re = re.compile('^' + re.escape('\x1b[') + r'( height. self._line_heights = {} def __getitem__(self, lineno): " Make it iterable (iterate line by line). " if lineno < self.line_count: return self.get_line(lineno) else: raise IndexError def get_height_for_line(self, lineno, width): """ Return the height that a given line would need if it is rendered in a space with the given width. """ try: return self._line_heights[lineno, width] except KeyError: text = token_list_to_text(self.get_line(lineno)) result = self.get_height_for_text(text, width) # Cache and return self._line_heights[lineno, width] = result return result @staticmethod def get_height_for_text(text, width): # Get text width for this line. line_width = get_cwidth(text) # Calculate height. try: quotient, remainder = divmod(line_width, width) except ZeroDivisionError: # Return something very big. # (This can happen, when the Window gets very small.) return 10 ** 10 else: if remainder: quotient += 1 # Like math.ceil. return max(1, quotient) class TokenListControl(UIControl): """ Control that displays a list of (Token, text) tuples. (It's mostly optimized for rather small widgets, like toolbars, menus, etc...) Mouse support: The list of tokens can also contain tuples of three items, looking like: (Token, text, handler). When mouse support is enabled and the user clicks on this token, then the given handler is called. That handler should accept two inputs: (CommandLineInterface, MouseEvent) and it should either handle the event or return `NotImplemented` in case we want the containing Window to handle this event. :param get_tokens: Callable that takes a `CommandLineInterface` instance and returns the list of (Token, text) tuples to be displayed right now. :param default_char: default :class:`.Char` (character and Token) to use for the background when there is more space available than `get_tokens` returns. :param get_default_char: Like `default_char`, but this is a callable that takes a :class:`prompt_toolkit.interface.CommandLineInterface` and returns a :class:`.Char` instance. :param has_focus: `bool` or `CLIFilter`, when this evaluates to `True`, this UI control will take the focus. The cursor will be shown in the upper left corner of this control, unless `get_token` returns a ``Token.SetCursorPosition`` token somewhere in the token list, then the cursor will be shown there. """ def __init__(self, get_tokens, default_char=None, get_default_char=None, align_right=False, align_center=False, has_focus=False): assert callable(get_tokens) assert default_char is None or isinstance(default_char, Char) assert get_default_char is None or callable(get_default_char) assert not (default_char and get_default_char) self.align_right = to_cli_filter(align_right) self.align_center = to_cli_filter(align_center) self._has_focus_filter = to_cli_filter(has_focus) self.get_tokens = get_tokens # Construct `get_default_char` callable. if default_char: get_default_char = lambda _: default_char elif not get_default_char: get_default_char = lambda _: Char(' ', Token.Transparent) self.get_default_char = get_default_char #: Cache for the content. self._content_cache = SimpleCache(maxsize=18) self._token_cache = SimpleCache(maxsize=1) # Only cache one token list. We don't need the previous item. # Render info for the mouse support. self._tokens = None def reset(self): self._tokens = None def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.get_tokens) def _get_tokens_cached(self, cli): """ Get tokens, but only retrieve tokens once during one render run. (This function is called several times during one rendering, because we also need those for calculating the dimensions.) """ return self._token_cache.get( cli.render_counter, lambda: self.get_tokens(cli)) def has_focus(self, cli): return self._has_focus_filter(cli) def preferred_width(self, cli, max_available_width): """ Return the preferred width for this control. That is the width of the longest line. """ text = token_list_to_text(self._get_tokens_cached(cli)) line_lengths = [get_cwidth(l) for l in text.split('\n')] return max(line_lengths) def preferred_height(self, cli, width, max_available_height, wrap_lines): content = self.create_content(cli, width, None) return content.line_count def create_content(self, cli, width, height): # Get tokens tokens_with_mouse_handlers = self._get_tokens_cached(cli) default_char = self.get_default_char(cli) # Wrap/align right/center parameters. right = self.align_right(cli) center = self.align_center(cli) def process_line(line): " Center or right align a single line. " used_width = token_list_width(line) padding = width - used_width if center: padding = int(padding / 2) return [(default_char.token, default_char.char * padding)] + line if right or center: token_lines_with_mouse_handlers = [] for line in split_lines(tokens_with_mouse_handlers): token_lines_with_mouse_handlers.append(process_line(line)) else: token_lines_with_mouse_handlers = list(split_lines(tokens_with_mouse_handlers)) # Strip mouse handlers from tokens. token_lines = [ [tuple(item[:2]) for item in line] for line in token_lines_with_mouse_handlers ] # Keep track of the tokens with mouse handler, for later use in # `mouse_handler`. self._tokens = tokens_with_mouse_handlers # If there is a `Token.SetCursorPosition` in the token list, set the # cursor position here. def get_cursor_position(): SetCursorPosition = Token.SetCursorPosition for y, line in enumerate(token_lines): x = 0 for token, text in line: if token == SetCursorPosition: return Point(x=x, y=y) x += len(text) return None # Create content, or take it from the cache. key = (default_char.char, default_char.token, tuple(tokens_with_mouse_handlers), width, right, center) def get_content(): return UIContent(get_line=lambda i: token_lines[i], line_count=len(token_lines), default_char=default_char, cursor_position=get_cursor_position()) return self._content_cache.get(key, get_content) @classmethod def static(cls, tokens): def get_static_tokens(cli): return tokens return cls(get_static_tokens) def mouse_handler(self, cli, mouse_event): """ Handle mouse events. (When the token list contained mouse handlers and the user clicked on on any of these, the matching handler is called. This handler can still return `NotImplemented` in case we want the `Window` to handle this particular event.) """ if self._tokens: # Read the generator. tokens_for_line = list(split_lines(self._tokens)) try: tokens = tokens_for_line[mouse_event.position.y] except IndexError: return NotImplemented else: # Find position in the token list. xpos = mouse_event.position.x # Find mouse handler for this character. count = 0 for item in tokens: count += len(item[1]) if count >= xpos: if len(item) >= 3: # Handler found. Call it. # (Handler can return NotImplemented, so return # that result.) handler = item[2] return handler(cli, mouse_event) else: break # Otherwise, don't handle here. return NotImplemented class FillControl(UIControl): """ Fill whole control with characters with this token. (Also helpful for debugging.) :param char: :class:`.Char` instance to use for filling. :param get_char: A callable that takes a CommandLineInterface and returns a :class:`.Char` object. """ def __init__(self, character=None, token=Token, char=None, get_char=None): # 'character' and 'token' parameters are deprecated. assert char is None or isinstance(char, Char) assert get_char is None or callable(get_char) assert not (char and get_char) self.char = char if character: # Passing (character=' ', token=token) is deprecated. self.character = character self.token = token self.get_char = lambda cli: Char(character, token) elif get_char: # When 'get_char' is given. self.get_char = get_char else: # When 'char' is given. self.char = self.char or Char() self.get_char = lambda cli: self.char self.char = char def __repr__(self): if self.char: return '%s(char=%r)' % (self.__class__.__name__, self.char) else: return '%s(get_char=%r)' % (self.__class__.__name__, self.get_char) def reset(self): pass def has_focus(self, cli): return False def create_content(self, cli, width, height): def get_line(i): return [] return UIContent( get_line=get_line, line_count=100 ** 100, # Something very big. default_char=self.get_char(cli)) _ProcessedLine = namedtuple('_ProcessedLine', 'tokens source_to_display display_to_source') class BufferControl(UIControl): """ Control for visualising the content of a `Buffer`. :param input_processors: list of :class:`~prompt_toolkit.layout.processors.Processor`. :param lexer: :class:`~prompt_toolkit.layout.lexers.Lexer` instance for syntax highlighting. :param preview_search: `bool` or `CLIFilter`: Show search while typing. :param get_search_state: Callable that takes a CommandLineInterface and returns the SearchState to be used. (If not CommandLineInterface.search_state.) :param buffer_name: String representing the name of the buffer to display. :param default_char: :class:`.Char` instance to use to fill the background. This is transparent by default. :param focus_on_click: Focus this buffer when it's click, but not yet focussed. """ def __init__(self, buffer_name=DEFAULT_BUFFER, input_processors=None, lexer=None, preview_search=False, search_buffer_name=SEARCH_BUFFER, get_search_state=None, menu_position=None, default_char=None, focus_on_click=False): assert input_processors is None or all(isinstance(i, Processor) for i in input_processors) assert menu_position is None or callable(menu_position) assert lexer is None or isinstance(lexer, Lexer) assert get_search_state is None or callable(get_search_state) assert default_char is None or isinstance(default_char, Char) self.preview_search = to_cli_filter(preview_search) self.get_search_state = get_search_state self.focus_on_click = to_cli_filter(focus_on_click) self.input_processors = input_processors or [] self.buffer_name = buffer_name self.menu_position = menu_position self.lexer = lexer or SimpleLexer() self.default_char = default_char or Char(token=Token.Transparent) self.search_buffer_name = search_buffer_name #: Cache for the lexer. #: Often, due to cursor movement, undo/redo and window resizing #: operations, it happens that a short time, the same document has to be #: lexed. This is a faily easy way to cache such an expensive operation. self._token_cache = SimpleCache(maxsize=8) self._xy_to_cursor_position = None self._last_click_timestamp = None self._last_get_processed_line = None def _buffer(self, cli): """ The buffer object that contains the 'main' content. """ return cli.buffers[self.buffer_name] def has_focus(self, cli): # This control gets the focussed if the actual `Buffer` instance has the # focus or when any of the `InputProcessor` classes tells us that it # wants the focus. (E.g. in case of a reverse-search, where the actual # search buffer may not be displayed, but the "reverse-i-search" text # should get the focus.) return cli.current_buffer_name == self.buffer_name or \ any(i.has_focus(cli) for i in self.input_processors) def preferred_width(self, cli, max_available_width): """ This should return the preferred width. Note: We don't specify a preferred width according to the content, because it would be too expensive. Calculating the preferred width can be done by calculating the longest line, but this would require applying all the processors to each line. This is unfeasible for a larger document, and doing it for small documents only would result in inconsistent behaviour. """ return None def preferred_height(self, cli, width, max_available_height, wrap_lines): # Calculate the content height, if it was drawn on a screen with the # given width. height = 0 content = self.create_content(cli, width, None) # When line wrapping is off, the height should be equal to the amount # of lines. if not wrap_lines: return content.line_count # When the number of lines exceeds the max_available_height, just # return max_available_height. No need to calculate anything. if content.line_count >= max_available_height: return max_available_height for i in range(content.line_count): height += content.get_height_for_line(i, width) if height >= max_available_height: return max_available_height return height def _get_tokens_for_line_func(self, cli, document): """ Create a function that returns the tokens for a given line. """ # Cache using `document.text`. def get_tokens_for_line(): return self.lexer.lex_document(cli, document) return self._token_cache.get(document.text, get_tokens_for_line) def _create_get_processed_line_func(self, cli, document): """ Create a function that takes a line number of the current document and returns a _ProcessedLine(processed_tokens, source_to_display, display_to_source) tuple. """ def transform(lineno, tokens): " Transform the tokens for a given line number. " source_to_display_functions = [] display_to_source_functions = [] # Get cursor position at this line. if document.cursor_position_row == lineno: cursor_column = document.cursor_position_col else: cursor_column = None def source_to_display(i): """ Translate x position from the buffer to the x position in the processed token list. """ for f in source_to_display_functions: i = f(i) return i # Apply each processor. for p in self.input_processors: transformation = p.apply_transformation( cli, document, lineno, source_to_display, tokens) tokens = transformation.tokens if cursor_column: cursor_column = transformation.source_to_display(cursor_column) display_to_source_functions.append(transformation.display_to_source) source_to_display_functions.append(transformation.source_to_display) def display_to_source(i): for f in reversed(display_to_source_functions): i = f(i) return i return _ProcessedLine(tokens, source_to_display, display_to_source) def create_func(): get_line = self._get_tokens_for_line_func(cli, document) cache = {} def get_processed_line(i): try: return cache[i] except KeyError: processed_line = transform(i, get_line(i)) cache[i] = processed_line return processed_line return get_processed_line return create_func() def create_content(self, cli, width, height): """ Create a UIContent. """ buffer = self._buffer(cli) # Get the document to be shown. If we are currently searching (the # search buffer has focus, and the preview_search filter is enabled), # then use the search document, which has possibly a different # text/cursor position.) def preview_now(): """ True when we should preview a search. """ return bool(self.preview_search(cli) and cli.buffers[self.search_buffer_name].text) if preview_now(): if self.get_search_state: ss = self.get_search_state(cli) else: ss = cli.search_state document = buffer.document_for_search(SearchState( text=cli.current_buffer.text, direction=ss.direction, ignore_case=ss.ignore_case)) else: document = buffer.document get_processed_line = self._create_get_processed_line_func(cli, document) self._last_get_processed_line = get_processed_line def translate_rowcol(row, col): " Return the content column for this coordinate. " return Point(y=row, x=get_processed_line(row).source_to_display(col)) def get_line(i): " Return the tokens for a given line number. " tokens = get_processed_line(i).tokens # Add a space at the end, because that is a possible cursor # position. (When inserting after the input.) We should do this on # all the lines, not just the line containing the cursor. (Because # otherwise, line wrapping/scrolling could change when moving the # cursor around.) tokens = tokens + [(self.default_char.token, ' ')] return tokens content = UIContent( get_line=get_line, line_count=document.line_count, cursor_position=translate_rowcol(document.cursor_position_row, document.cursor_position_col), default_char=self.default_char) # If there is an auto completion going on, use that start point for a # pop-up menu position. (But only when this buffer has the focus -- # there is only one place for a menu, determined by the focussed buffer.) if cli.current_buffer_name == self.buffer_name: menu_position = self.menu_position(cli) if self.menu_position else None if menu_position is not None: assert isinstance(menu_position, int) menu_row, menu_col = buffer.document.translate_index_to_position(menu_position) content.menu_position = translate_rowcol(menu_row, menu_col) elif buffer.complete_state: # Position for completion menu. # Note: We use 'min', because the original cursor position could be # behind the input string when the actual completion is for # some reason shorter than the text we had before. (A completion # can change and shorten the input.) menu_row, menu_col = buffer.document.translate_index_to_position( min(buffer.cursor_position, buffer.complete_state.original_document.cursor_position)) content.menu_position = translate_rowcol(menu_row, menu_col) else: content.menu_position = None return content def mouse_handler(self, cli, mouse_event): """ Mouse handler for this control. """ buffer = self._buffer(cli) position = mouse_event.position # Focus buffer when clicked. if self.has_focus(cli): if self._last_get_processed_line: processed_line = self._last_get_processed_line(position.y) # Translate coordinates back to the cursor position of the # original input. xpos = processed_line.display_to_source(position.x) index = buffer.document.translate_row_col_to_index(position.y, xpos) # Set the cursor position. if mouse_event.event_type == MouseEventType.MOUSE_DOWN: buffer.exit_selection() buffer.cursor_position = index elif mouse_event.event_type == MouseEventType.MOUSE_UP: # When the cursor was moved to another place, select the text. # (The >1 is actually a small but acceptable workaround for # selecting text in Vi navigation mode. In navigation mode, # the cursor can never be after the text, so the cursor # will be repositioned automatically.) if abs(buffer.cursor_position - index) > 1: buffer.start_selection(selection_type=SelectionType.CHARACTERS) buffer.cursor_position = index # Select word around cursor on double click. # Two MOUSE_UP events in a short timespan are considered a double click. double_click = self._last_click_timestamp and time.time() - self._last_click_timestamp < .3 self._last_click_timestamp = time.time() if double_click: start, end = buffer.document.find_boundaries_of_current_word() buffer.cursor_position += start buffer.start_selection(selection_type=SelectionType.CHARACTERS) buffer.cursor_position += end - start else: # Don't handle scroll events here. return NotImplemented # Not focussed, but focussing on click events. else: if self.focus_on_click(cli) and mouse_event.event_type == MouseEventType.MOUSE_UP: # Focus happens on mouseup. (If we did this on mousedown, the # up event will be received at the point where this widget is # focussed and be handled anyway.) cli.focus(self.buffer_name) else: return NotImplemented def move_cursor_down(self, cli): b = self._buffer(cli) b.cursor_position += b.document.get_cursor_down_position() def move_cursor_up(self, cli): b = self._buffer(cli) b.cursor_position += b.document.get_cursor_up_position() prompt_toolkit-1.0.15/prompt_toolkit/layout/containers.py0000664000175000017500000020431113136130420025430 0ustar jonathanjonathan00000000000000""" Container for the layout. (Containers can contain other containers or user interface controls.) """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from six.moves import range from .controls import UIControl, TokenListControl, UIContent from .dimension import LayoutDimension, sum_layout_dimensions, max_layout_dimensions from .margins import Margin from .screen import Point, WritePosition, _CHAR_CACHE from .utils import token_list_to_text, explode_tokens from prompt_toolkit.cache import SimpleCache from prompt_toolkit.filters import to_cli_filter, ViInsertMode, EmacsInsertMode from prompt_toolkit.mouse_events import MouseEvent, MouseEventType from prompt_toolkit.reactive import Integer from prompt_toolkit.token import Token from prompt_toolkit.utils import take_using_weights, get_cwidth __all__ = ( 'Container', 'HSplit', 'VSplit', 'FloatContainer', 'Float', 'Window', 'WindowRenderInfo', 'ConditionalContainer', 'ScrollOffsets', 'ColorColumn', ) Transparent = Token.Transparent class Container(with_metaclass(ABCMeta, object)): """ Base class for user interface layout. """ @abstractmethod def reset(self): """ Reset the state of this container and all the children. (E.g. reset scroll offsets, etc...) """ @abstractmethod def preferred_width(self, cli, max_available_width): """ Return a :class:`~prompt_toolkit.layout.dimension.LayoutDimension` that represents the desired width for this container. :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface`. """ @abstractmethod def preferred_height(self, cli, width, max_available_height): """ Return a :class:`~prompt_toolkit.layout.dimension.LayoutDimension` that represents the desired height for this container. :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface`. """ @abstractmethod def write_to_screen(self, cli, screen, mouse_handlers, write_position): """ Write the actual content to the screen. :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface`. :param screen: :class:`~prompt_toolkit.layout.screen.Screen` :param mouse_handlers: :class:`~prompt_toolkit.layout.mouse_handlers.MouseHandlers`. """ @abstractmethod def walk(self, cli): """ Walk through all the layout nodes (and their children) and yield them. """ def _window_too_small(): " Create a `Window` that displays the 'Window too small' text. " return Window(TokenListControl.static( [(Token.WindowTooSmall, ' Window too small... ')])) class HSplit(Container): """ Several layouts, one stacked above/under the other. :param children: List of child :class:`.Container` objects. :param window_too_small: A :class:`.Container` object that is displayed if there is not enough space for all the children. By default, this is a "Window too small" message. :param get_dimensions: (`None` or a callable that takes a `CommandLineInterface` and returns a list of `LayoutDimension` instances.) By default the dimensions are taken from the children and divided by the available space. However, when `get_dimensions` is specified, this is taken instead. :param report_dimensions_callback: When rendering, this function is called with the `CommandLineInterface` and the list of used dimensions. (As a list of integers.) """ def __init__(self, children, window_too_small=None, get_dimensions=None, report_dimensions_callback=None): assert all(isinstance(c, Container) for c in children) assert window_too_small is None or isinstance(window_too_small, Container) assert get_dimensions is None or callable(get_dimensions) assert report_dimensions_callback is None or callable(report_dimensions_callback) self.children = children self.window_too_small = window_too_small or _window_too_small() self.get_dimensions = get_dimensions self.report_dimensions_callback = report_dimensions_callback def preferred_width(self, cli, max_available_width): if self.children: dimensions = [c.preferred_width(cli, max_available_width) for c in self.children] return max_layout_dimensions(dimensions) else: return LayoutDimension(0) def preferred_height(self, cli, width, max_available_height): dimensions = [c.preferred_height(cli, width, max_available_height) for c in self.children] return sum_layout_dimensions(dimensions) def reset(self): for c in self.children: c.reset() def write_to_screen(self, cli, screen, mouse_handlers, write_position): """ Render the prompt to a `Screen` instance. :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class to which the output has to be written. """ sizes = self._divide_heigths(cli, write_position) if self.report_dimensions_callback: self.report_dimensions_callback(cli, sizes) if sizes is None: self.window_too_small.write_to_screen( cli, screen, mouse_handlers, write_position) else: # Draw child panes. ypos = write_position.ypos xpos = write_position.xpos width = write_position.width for s, c in zip(sizes, self.children): c.write_to_screen(cli, screen, mouse_handlers, WritePosition(xpos, ypos, width, s)) ypos += s def _divide_heigths(self, cli, write_position): """ Return the heights for all rows. Or None when there is not enough space. """ if not self.children: return [] # Calculate heights. given_dimensions = self.get_dimensions(cli) if self.get_dimensions else None def get_dimension_for_child(c, index): if given_dimensions and given_dimensions[index] is not None: return given_dimensions[index] else: return c.preferred_height(cli, write_position.width, write_position.extended_height) dimensions = [get_dimension_for_child(c, index) for index, c in enumerate(self.children)] # Sum dimensions sum_dimensions = sum_layout_dimensions(dimensions) # If there is not enough space for both. # Don't do anything. if sum_dimensions.min > write_position.extended_height: return # Find optimal sizes. (Start with minimal size, increase until we cover # the whole height.) sizes = [d.min for d in dimensions] child_generator = take_using_weights( items=list(range(len(dimensions))), weights=[d.weight for d in dimensions]) i = next(child_generator) while sum(sizes) < min(write_position.extended_height, sum_dimensions.preferred): # Increase until we meet at least the 'preferred' size. if sizes[i] < dimensions[i].preferred: sizes[i] += 1 i = next(child_generator) if not any([cli.is_returning, cli.is_exiting, cli.is_aborting]): while sum(sizes) < min(write_position.height, sum_dimensions.max): # Increase until we use all the available space. (or until "max") if sizes[i] < dimensions[i].max: sizes[i] += 1 i = next(child_generator) return sizes def walk(self, cli): """ Walk through children. """ yield self for c in self.children: for i in c.walk(cli): yield i class VSplit(Container): """ Several layouts, one stacked left/right of the other. :param children: List of child :class:`.Container` objects. :param window_too_small: A :class:`.Container` object that is displayed if there is not enough space for all the children. By default, this is a "Window too small" message. :param get_dimensions: (`None` or a callable that takes a `CommandLineInterface` and returns a list of `LayoutDimension` instances.) By default the dimensions are taken from the children and divided by the available space. However, when `get_dimensions` is specified, this is taken instead. :param report_dimensions_callback: When rendering, this function is called with the `CommandLineInterface` and the list of used dimensions. (As a list of integers.) """ def __init__(self, children, window_too_small=None, get_dimensions=None, report_dimensions_callback=None): assert all(isinstance(c, Container) for c in children) assert window_too_small is None or isinstance(window_too_small, Container) assert get_dimensions is None or callable(get_dimensions) assert report_dimensions_callback is None or callable(report_dimensions_callback) self.children = children self.window_too_small = window_too_small or _window_too_small() self.get_dimensions = get_dimensions self.report_dimensions_callback = report_dimensions_callback def preferred_width(self, cli, max_available_width): dimensions = [c.preferred_width(cli, max_available_width) for c in self.children] return sum_layout_dimensions(dimensions) def preferred_height(self, cli, width, max_available_height): sizes = self._divide_widths(cli, width) if sizes is None: return LayoutDimension() else: dimensions = [c.preferred_height(cli, s, max_available_height) for s, c in zip(sizes, self.children)] return max_layout_dimensions(dimensions) def reset(self): for c in self.children: c.reset() def _divide_widths(self, cli, width): """ Return the widths for all columns. Or None when there is not enough space. """ if not self.children: return [] # Calculate widths. given_dimensions = self.get_dimensions(cli) if self.get_dimensions else None def get_dimension_for_child(c, index): if given_dimensions and given_dimensions[index] is not None: return given_dimensions[index] else: return c.preferred_width(cli, width) dimensions = [get_dimension_for_child(c, index) for index, c in enumerate(self.children)] # Sum dimensions sum_dimensions = sum_layout_dimensions(dimensions) # If there is not enough space for both. # Don't do anything. if sum_dimensions.min > width: return # Find optimal sizes. (Start with minimal size, increase until we cover # the whole height.) sizes = [d.min for d in dimensions] child_generator = take_using_weights( items=list(range(len(dimensions))), weights=[d.weight for d in dimensions]) i = next(child_generator) while sum(sizes) < min(width, sum_dimensions.preferred): # Increase until we meet at least the 'preferred' size. if sizes[i] < dimensions[i].preferred: sizes[i] += 1 i = next(child_generator) while sum(sizes) < min(width, sum_dimensions.max): # Increase until we use all the available space. if sizes[i] < dimensions[i].max: sizes[i] += 1 i = next(child_generator) return sizes def write_to_screen(self, cli, screen, mouse_handlers, write_position): """ Render the prompt to a `Screen` instance. :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class to which the output has to be written. """ if not self.children: return sizes = self._divide_widths(cli, write_position.width) if self.report_dimensions_callback: self.report_dimensions_callback(cli, sizes) # If there is not enough space. if sizes is None: self.window_too_small.write_to_screen( cli, screen, mouse_handlers, write_position) return # Calculate heights, take the largest possible, but not larger than write_position.extended_height. heights = [child.preferred_height(cli, width, write_position.extended_height).preferred for width, child in zip(sizes, self.children)] height = max(write_position.height, min(write_position.extended_height, max(heights))) # Draw child panes. ypos = write_position.ypos xpos = write_position.xpos for s, c in zip(sizes, self.children): c.write_to_screen(cli, screen, mouse_handlers, WritePosition(xpos, ypos, s, height)) xpos += s def walk(self, cli): """ Walk through children. """ yield self for c in self.children: for i in c.walk(cli): yield i class FloatContainer(Container): """ Container which can contain another container for the background, as well as a list of floating containers on top of it. Example Usage:: FloatContainer(content=Window(...), floats=[ Float(xcursor=True, ycursor=True, layout=CompletionMenu(...)) ]) """ def __init__(self, content, floats): assert isinstance(content, Container) assert all(isinstance(f, Float) for f in floats) self.content = content self.floats = floats def reset(self): self.content.reset() for f in self.floats: f.content.reset() def preferred_width(self, cli, write_position): return self.content.preferred_width(cli, write_position) def preferred_height(self, cli, width, max_available_height): """ Return the preferred height of the float container. (We don't care about the height of the floats, they should always fit into the dimensions provided by the container.) """ return self.content.preferred_height(cli, width, max_available_height) def write_to_screen(self, cli, screen, mouse_handlers, write_position): self.content.write_to_screen(cli, screen, mouse_handlers, write_position) for fl in self.floats: # When a menu_position was given, use this instead of the cursor # position. (These cursor positions are absolute, translate again # relative to the write_position.) # Note: This should be inside the for-loop, because one float could # set the cursor position to be used for the next one. cursor_position = screen.menu_position or screen.cursor_position cursor_position = Point(x=cursor_position.x - write_position.xpos, y=cursor_position.y - write_position.ypos) fl_width = fl.get_width(cli) fl_height = fl.get_height(cli) # Left & width given. if fl.left is not None and fl_width is not None: xpos = fl.left width = fl_width # Left & right given -> calculate width. elif fl.left is not None and fl.right is not None: xpos = fl.left width = write_position.width - fl.left - fl.right # Width & right given -> calculate left. elif fl_width is not None and fl.right is not None: xpos = write_position.width - fl.right - fl_width width = fl_width elif fl.xcursor: width = fl_width if width is None: width = fl.content.preferred_width(cli, write_position.width).preferred width = min(write_position.width, width) xpos = cursor_position.x if xpos + width > write_position.width: xpos = max(0, write_position.width - width) # Only width given -> center horizontally. elif fl_width: xpos = int((write_position.width - fl_width) / 2) width = fl_width # Otherwise, take preferred width from float content. else: width = fl.content.preferred_width(cli, write_position.width).preferred if fl.left is not None: xpos = fl.left elif fl.right is not None: xpos = max(0, write_position.width - width - fl.right) else: # Center horizontally. xpos = max(0, int((write_position.width - width) / 2)) # Trim. width = min(width, write_position.width - xpos) # Top & height given. if fl.top is not None and fl_height is not None: ypos = fl.top height = fl_height # Top & bottom given -> calculate height. elif fl.top is not None and fl.bottom is not None: ypos = fl.top height = write_position.height - fl.top - fl.bottom # Height & bottom given -> calculate top. elif fl_height is not None and fl.bottom is not None: ypos = write_position.height - fl_height - fl.bottom height = fl_height # Near cursor elif fl.ycursor: ypos = cursor_position.y + 1 height = fl_height if height is None: height = fl.content.preferred_height( cli, width, write_position.extended_height).preferred # Reduce height if not enough space. (We can use the # extended_height when the content requires it.) if height > write_position.extended_height - ypos: if write_position.extended_height - ypos + 1 >= ypos: # When the space below the cursor is more than # the space above, just reduce the height. height = write_position.extended_height - ypos else: # Otherwise, fit the float above the cursor. height = min(height, cursor_position.y) ypos = cursor_position.y - height # Only height given -> center vertically. elif fl_width: ypos = int((write_position.height - fl_height) / 2) height = fl_height # Otherwise, take preferred height from content. else: height = fl.content.preferred_height( cli, width, write_position.extended_height).preferred if fl.top is not None: ypos = fl.top elif fl.bottom is not None: ypos = max(0, write_position.height - height - fl.bottom) else: # Center vertically. ypos = max(0, int((write_position.height - height) / 2)) # Trim. height = min(height, write_position.height - ypos) # Write float. # (xpos and ypos can be negative: a float can be partially visible.) if height > 0 and width > 0: wp = WritePosition(xpos=xpos + write_position.xpos, ypos=ypos + write_position.ypos, width=width, height=height) if not fl.hide_when_covering_content or self._area_is_empty(screen, wp): fl.content.write_to_screen(cli, screen, mouse_handlers, wp) def _area_is_empty(self, screen, write_position): """ Return True when the area below the write position is still empty. (For floats that should not hide content underneath.) """ wp = write_position Transparent = Token.Transparent for y in range(wp.ypos, wp.ypos + wp.height): if y in screen.data_buffer: row = screen.data_buffer[y] for x in range(wp.xpos, wp.xpos + wp.width): c = row[x] if c.char != ' ' or c.token != Transparent: return False return True def walk(self, cli): """ Walk through children. """ yield self for i in self.content.walk(cli): yield i for f in self.floats: for i in f.content.walk(cli): yield i class Float(object): """ Float for use in a :class:`.FloatContainer`. :param content: :class:`.Container` instance. :param hide_when_covering_content: Hide the float when it covers content underneath. """ def __init__(self, top=None, right=None, bottom=None, left=None, width=None, height=None, get_width=None, get_height=None, xcursor=False, ycursor=False, content=None, hide_when_covering_content=False): assert isinstance(content, Container) assert width is None or get_width is None assert height is None or get_height is None self.left = left self.right = right self.top = top self.bottom = bottom self._width = width self._height = height self._get_width = get_width self._get_height = get_height self.xcursor = xcursor self.ycursor = ycursor self.content = content self.hide_when_covering_content = hide_when_covering_content def get_width(self, cli): if self._width: return self._width if self._get_width: return self._get_width(cli) def get_height(self, cli): if self._height: return self._height if self._get_height: return self._get_height(cli) def __repr__(self): return 'Float(content=%r)' % self.content class WindowRenderInfo(object): """ Render information, for the last render time of this control. It stores mapping information between the input buffers (in case of a :class:`~prompt_toolkit.layout.controls.BufferControl`) and the actual render position on the output screen. (Could be used for implementation of the Vi 'H' and 'L' key bindings as well as implementing mouse support.) :param ui_content: The original :class:`.UIContent` instance that contains the whole input, without clipping. (ui_content) :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance. :param vertical_scroll: The vertical scroll of the :class:`.Window` instance. :param window_width: The width of the window that displays the content, without the margins. :param window_height: The height of the window that displays the content. :param configured_scroll_offsets: The scroll offsets as configured for the :class:`Window` instance. :param visible_line_to_row_col: Mapping that maps the row numbers on the displayed screen (starting from zero for the first visible line) to (row, col) tuples pointing to the row and column of the :class:`.UIContent`. :param rowcol_to_yx: Mapping that maps (row, column) tuples representing coordinates of the :class:`UIContent` to (y, x) absolute coordinates at the rendered screen. """ def __init__(self, ui_content, horizontal_scroll, vertical_scroll, window_width, window_height, configured_scroll_offsets, visible_line_to_row_col, rowcol_to_yx, x_offset, y_offset, wrap_lines): assert isinstance(ui_content, UIContent) assert isinstance(horizontal_scroll, int) assert isinstance(vertical_scroll, int) assert isinstance(window_width, int) assert isinstance(window_height, int) assert isinstance(configured_scroll_offsets, ScrollOffsets) assert isinstance(visible_line_to_row_col, dict) assert isinstance(rowcol_to_yx, dict) assert isinstance(x_offset, int) assert isinstance(y_offset, int) assert isinstance(wrap_lines, bool) self.ui_content = ui_content self.vertical_scroll = vertical_scroll self.window_width = window_width # Width without margins. self.window_height = window_height self.configured_scroll_offsets = configured_scroll_offsets self.visible_line_to_row_col = visible_line_to_row_col self.wrap_lines = wrap_lines self._rowcol_to_yx = rowcol_to_yx # row/col from input to absolute y/x # screen coordinates. self._x_offset = x_offset self._y_offset = y_offset @property def visible_line_to_input_line(self): return dict( (visible_line, rowcol[0]) for visible_line, rowcol in self.visible_line_to_row_col.items()) @property def cursor_position(self): """ Return the cursor position coordinates, relative to the left/top corner of the rendered screen. """ cpos = self.ui_content.cursor_position y, x = self._rowcol_to_yx[cpos.y, cpos.x] return Point(x=x - self._x_offset, y=y - self._y_offset) @property def applied_scroll_offsets(self): """ Return a :class:`.ScrollOffsets` instance that indicates the actual offset. This can be less than or equal to what's configured. E.g, when the cursor is completely at the top, the top offset will be zero rather than what's configured. """ if self.displayed_lines[0] == 0: top = 0 else: # Get row where the cursor is displayed. y = self.input_line_to_visible_line[self.ui_content.cursor_position.y] top = min(y, self.configured_scroll_offsets.top) return ScrollOffsets( top=top, bottom=min(self.ui_content.line_count - self.displayed_lines[-1] - 1, self.configured_scroll_offsets.bottom), # For left/right, it probably doesn't make sense to return something. # (We would have to calculate the widths of all the lines and keep # double width characters in mind.) left=0, right=0) @property def displayed_lines(self): """ List of all the visible rows. (Line numbers of the input buffer.) The last line may not be entirely visible. """ return sorted(row for row, col in self.visible_line_to_row_col.values()) @property def input_line_to_visible_line(self): """ Return the dictionary mapping the line numbers of the input buffer to the lines of the screen. When a line spans several rows at the screen, the first row appears in the dictionary. """ result = {} for k, v in self.visible_line_to_input_line.items(): if v in result: result[v] = min(result[v], k) else: result[v] = k return result def first_visible_line(self, after_scroll_offset=False): """ Return the line number (0 based) of the input document that corresponds with the first visible line. """ if after_scroll_offset: return self.displayed_lines[self.applied_scroll_offsets.top] else: return self.displayed_lines[0] def last_visible_line(self, before_scroll_offset=False): """ Like `first_visible_line`, but for the last visible line. """ if before_scroll_offset: return self.displayed_lines[-1 - self.applied_scroll_offsets.bottom] else: return self.displayed_lines[-1] def center_visible_line(self, before_scroll_offset=False, after_scroll_offset=False): """ Like `first_visible_line`, but for the center visible line. """ return (self.first_visible_line(after_scroll_offset) + (self.last_visible_line(before_scroll_offset) - self.first_visible_line(after_scroll_offset)) // 2 ) @property def content_height(self): """ The full height of the user control. """ return self.ui_content.line_count @property def full_height_visible(self): """ True when the full height is visible (There is no vertical scroll.) """ return self.vertical_scroll == 0 and self.last_visible_line() == self.content_height @property def top_visible(self): """ True when the top of the buffer is visible. """ return self.vertical_scroll == 0 @property def bottom_visible(self): """ True when the bottom of the buffer is visible. """ return self.last_visible_line() == self.content_height - 1 @property def vertical_scroll_percentage(self): """ Vertical scroll as a percentage. (0 means: the top is visible, 100 means: the bottom is visible.) """ if self.bottom_visible: return 100 else: return (100 * self.vertical_scroll // self.content_height) def get_height_for_line(self, lineno): """ Return the height of the given line. (The height that it would take, if this line became visible.) """ if self.wrap_lines: return self.ui_content.get_height_for_line(lineno, self.window_width) else: return 1 class ScrollOffsets(object): """ Scroll offsets for the :class:`.Window` class. Note that left/right offsets only make sense if line wrapping is disabled. """ def __init__(self, top=0, bottom=0, left=0, right=0): assert isinstance(top, Integer) assert isinstance(bottom, Integer) assert isinstance(left, Integer) assert isinstance(right, Integer) self._top = top self._bottom = bottom self._left = left self._right = right @property def top(self): return int(self._top) @property def bottom(self): return int(self._bottom) @property def left(self): return int(self._left) @property def right(self): return int(self._right) def __repr__(self): return 'ScrollOffsets(top=%r, bottom=%r, left=%r, right=%r)' % ( self.top, self.bottom, self.left, self.right) class ColorColumn(object): def __init__(self, position, token=Token.ColorColumn): self.position = position self.token = token _in_insert_mode = ViInsertMode() | EmacsInsertMode() class Window(Container): """ Container that holds a control. :param content: :class:`~prompt_toolkit.layout.controls.UIControl` instance. :param width: :class:`~prompt_toolkit.layout.dimension.LayoutDimension` instance. :param height: :class:`~prompt_toolkit.layout.dimension.LayoutDimension` instance. :param get_width: callable which takes a `CommandLineInterface` and returns a `LayoutDimension`. :param get_height: callable which takes a `CommandLineInterface` and returns a `LayoutDimension`. :param dont_extend_width: When `True`, don't take up more width then the preferred width reported by the control. :param dont_extend_height: When `True`, don't take up more width then the preferred height reported by the control. :param left_margins: A list of :class:`~prompt_toolkit.layout.margins.Margin` instance to be displayed on the left. For instance: :class:`~prompt_toolkit.layout.margins.NumberredMargin` can be one of them in order to show line numbers. :param right_margins: Like `left_margins`, but on the other side. :param scroll_offsets: :class:`.ScrollOffsets` instance, representing the preferred amount of lines/columns to be always visible before/after the cursor. When both top and bottom are a very high number, the cursor will be centered vertically most of the time. :param allow_scroll_beyond_bottom: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, allow scrolling so far, that the top part of the content is not visible anymore, while there is still empty space available at the bottom of the window. In the Vi editor for instance, this is possible. You will see tildes while the top part of the body is hidden. :param wrap_lines: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, don't scroll horizontally, but wrap lines instead. :param get_vertical_scroll: Callable that takes this window instance as input and returns a preferred vertical scroll. (When this is `None`, the scroll is only determined by the last and current cursor position.) :param get_horizontal_scroll: Callable that takes this window instance as input and returns a preferred vertical scroll. :param always_hide_cursor: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, never display the cursor, even when the user control specifies a cursor position. :param cursorline: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, display a cursorline. :param cursorcolumn: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, display a cursorcolumn. :param get_colorcolumns: A callable that takes a `CommandLineInterface` and returns a a list of :class:`.ColorColumn` instances that describe the columns to be highlighted. :param cursorline_token: The token to be used for highlighting the current line, if `cursorline` is True. :param cursorcolumn_token: The token to be used for highlighting the current line, if `cursorcolumn` is True. """ def __init__(self, content, width=None, height=None, get_width=None, get_height=None, dont_extend_width=False, dont_extend_height=False, left_margins=None, right_margins=None, scroll_offsets=None, allow_scroll_beyond_bottom=False, wrap_lines=False, get_vertical_scroll=None, get_horizontal_scroll=None, always_hide_cursor=False, cursorline=False, cursorcolumn=False, get_colorcolumns=None, cursorline_token=Token.CursorLine, cursorcolumn_token=Token.CursorColumn): assert isinstance(content, UIControl) assert width is None or isinstance(width, LayoutDimension) assert height is None or isinstance(height, LayoutDimension) assert get_width is None or callable(get_width) assert get_height is None or callable(get_height) assert width is None or get_width is None assert height is None or get_height is None assert scroll_offsets is None or isinstance(scroll_offsets, ScrollOffsets) assert left_margins is None or all(isinstance(m, Margin) for m in left_margins) assert right_margins is None or all(isinstance(m, Margin) for m in right_margins) assert get_vertical_scroll is None or callable(get_vertical_scroll) assert get_horizontal_scroll is None or callable(get_horizontal_scroll) assert get_colorcolumns is None or callable(get_colorcolumns) self.allow_scroll_beyond_bottom = to_cli_filter(allow_scroll_beyond_bottom) self.always_hide_cursor = to_cli_filter(always_hide_cursor) self.wrap_lines = to_cli_filter(wrap_lines) self.cursorline = to_cli_filter(cursorline) self.cursorcolumn = to_cli_filter(cursorcolumn) self.content = content self.dont_extend_width = dont_extend_width self.dont_extend_height = dont_extend_height self.left_margins = left_margins or [] self.right_margins = right_margins or [] self.scroll_offsets = scroll_offsets or ScrollOffsets() self.get_vertical_scroll = get_vertical_scroll self.get_horizontal_scroll = get_horizontal_scroll self._width = get_width or (lambda cli: width) self._height = get_height or (lambda cli: height) self.get_colorcolumns = get_colorcolumns or (lambda cli: []) self.cursorline_token = cursorline_token self.cursorcolumn_token = cursorcolumn_token # Cache for the screens generated by the margin. self._ui_content_cache = SimpleCache(maxsize=8) self._margin_width_cache = SimpleCache(maxsize=1) self.reset() def __repr__(self): return 'Window(content=%r)' % self.content def reset(self): self.content.reset() #: Scrolling position of the main content. self.vertical_scroll = 0 self.horizontal_scroll = 0 # Vertical scroll 2: this is the vertical offset that a line is # scrolled if a single line (the one that contains the cursor) consumes # all of the vertical space. self.vertical_scroll_2 = 0 #: Keep render information (mappings between buffer input and render #: output.) self.render_info = None def _get_margin_width(self, cli, margin): """ Return the width for this margin. (Calculate only once per render time.) """ # Margin.get_width, needs to have a UIContent instance. def get_ui_content(): return self._get_ui_content(cli, width=0, height=0) def get_width(): return margin.get_width(cli, get_ui_content) key = (margin, cli.render_counter) return self._margin_width_cache.get(key, get_width) def preferred_width(self, cli, max_available_width): # Calculate the width of the margin. total_margin_width = sum(self._get_margin_width(cli, m) for m in self.left_margins + self.right_margins) # Window of the content. (Can be `None`.) preferred_width = self.content.preferred_width( cli, max_available_width - total_margin_width) if preferred_width is not None: # Include width of the margins. preferred_width += total_margin_width # Merge. return self._merge_dimensions( dimension=self._width(cli), preferred=preferred_width, dont_extend=self.dont_extend_width) def preferred_height(self, cli, width, max_available_height): total_margin_width = sum(self._get_margin_width(cli, m) for m in self.left_margins + self.right_margins) wrap_lines = self.wrap_lines(cli) return self._merge_dimensions( dimension=self._height(cli), preferred=self.content.preferred_height( cli, width - total_margin_width, max_available_height, wrap_lines), dont_extend=self.dont_extend_height) @staticmethod def _merge_dimensions(dimension, preferred=None, dont_extend=False): """ Take the LayoutDimension from this `Window` class and the received preferred size from the `UIControl` and return a `LayoutDimension` to report to the parent container. """ dimension = dimension or LayoutDimension() # When a preferred dimension was explicitly given to the Window, # ignore the UIControl. if dimension.preferred_specified: preferred = dimension.preferred # When a 'preferred' dimension is given by the UIControl, make sure # that it stays within the bounds of the Window. if preferred is not None: if dimension.max: preferred = min(preferred, dimension.max) if dimension.min: preferred = max(preferred, dimension.min) # When a `dont_extend` flag has been given, use the preferred dimension # also as the max dimension. if dont_extend and preferred is not None: max_ = min(dimension.max, preferred) else: max_ = dimension.max return LayoutDimension( min=dimension.min, max=max_, preferred=preferred, weight=dimension.weight) def _get_ui_content(self, cli, width, height): """ Create a `UIContent` instance. """ def get_content(): return self.content.create_content(cli, width=width, height=height) key = (cli.render_counter, width, height) return self._ui_content_cache.get(key, get_content) def _get_digraph_char(self, cli): " Return `False`, or the Digraph symbol to be used. " if cli.quoted_insert: return '^' if cli.vi_state.waiting_for_digraph: if cli.vi_state.digraph_symbol1: return cli.vi_state.digraph_symbol1 return '?' return False def write_to_screen(self, cli, screen, mouse_handlers, write_position): """ Write window to screen. This renders the user control, the margins and copies everything over to the absolute position at the given screen. """ # Calculate margin sizes. left_margin_widths = [self._get_margin_width(cli, m) for m in self.left_margins] right_margin_widths = [self._get_margin_width(cli, m) for m in self.right_margins] total_margin_width = sum(left_margin_widths + right_margin_widths) # Render UserControl. ui_content = self.content.create_content( cli, write_position.width - total_margin_width, write_position.height) assert isinstance(ui_content, UIContent) # Scroll content. wrap_lines = self.wrap_lines(cli) scroll_func = self._scroll_when_linewrapping if wrap_lines else self._scroll_without_linewrapping scroll_func( ui_content, write_position.width - total_margin_width, write_position.height, cli) # Write body visible_line_to_row_col, rowcol_to_yx = self._copy_body( cli, ui_content, screen, write_position, sum(left_margin_widths), write_position.width - total_margin_width, self.vertical_scroll, self.horizontal_scroll, has_focus=self.content.has_focus(cli), wrap_lines=wrap_lines, highlight_lines=True, vertical_scroll_2=self.vertical_scroll_2, always_hide_cursor=self.always_hide_cursor(cli)) # Remember render info. (Set before generating the margins. They need this.) x_offset=write_position.xpos + sum(left_margin_widths) y_offset=write_position.ypos self.render_info = WindowRenderInfo( ui_content=ui_content, horizontal_scroll=self.horizontal_scroll, vertical_scroll=self.vertical_scroll, window_width=write_position.width - total_margin_width, window_height=write_position.height, configured_scroll_offsets=self.scroll_offsets, visible_line_to_row_col=visible_line_to_row_col, rowcol_to_yx=rowcol_to_yx, x_offset=x_offset, y_offset=y_offset, wrap_lines=wrap_lines) # Set mouse handlers. def mouse_handler(cli, mouse_event): """ Wrapper around the mouse_handler of the `UIControl` that turns screen coordinates into line coordinates. """ # Find row/col position first. yx_to_rowcol = dict((v, k) for k, v in rowcol_to_yx.items()) y = mouse_event.position.y x = mouse_event.position.x # If clicked below the content area, look for a position in the # last line instead. max_y = write_position.ypos + len(visible_line_to_row_col) - 1 y = min(max_y, y) while x >= 0: try: row, col = yx_to_rowcol[y, x] except KeyError: # Try again. (When clicking on the right side of double # width characters, or on the right side of the input.) x -= 1 else: # Found position, call handler of UIControl. result = self.content.mouse_handler( cli, MouseEvent(position=Point(x=col, y=row), event_type=mouse_event.event_type)) break else: # nobreak. # (No x/y coordinate found for the content. This happens in # case of a FillControl, that only specifies a background, but # doesn't have a content. Report (0,0) instead.) result = self.content.mouse_handler( cli, MouseEvent(position=Point(x=0, y=0), event_type=mouse_event.event_type)) # If it returns NotImplemented, handle it here. if result == NotImplemented: return self._mouse_handler(cli, mouse_event) return result mouse_handlers.set_mouse_handler_for_range( x_min=write_position.xpos + sum(left_margin_widths), x_max=write_position.xpos + write_position.width - total_margin_width, y_min=write_position.ypos, y_max=write_position.ypos + write_position.height, handler=mouse_handler) # Render and copy margins. move_x = 0 def render_margin(m, width): " Render margin. Return `Screen`. " # Retrieve margin tokens. tokens = m.create_margin(cli, self.render_info, width, write_position.height) # Turn it into a UIContent object. # already rendered those tokens using this size.) return TokenListControl.static(tokens).create_content( cli, width + 1, write_position.height) for m, width in zip(self.left_margins, left_margin_widths): # Create screen for margin. margin_screen = render_margin(m, width) # Copy and shift X. self._copy_margin(cli, margin_screen, screen, write_position, move_x, width) move_x += width move_x = write_position.width - sum(right_margin_widths) for m, width in zip(self.right_margins, right_margin_widths): # Create screen for margin. margin_screen = render_margin(m, width) # Copy and shift X. self._copy_margin(cli, margin_screen, screen, write_position, move_x, width) move_x += width def _copy_body(self, cli, ui_content, new_screen, write_position, move_x, width, vertical_scroll=0, horizontal_scroll=0, has_focus=False, wrap_lines=False, highlight_lines=False, vertical_scroll_2=0, always_hide_cursor=False): """ Copy the UIContent into the output screen. """ xpos = write_position.xpos + move_x ypos = write_position.ypos line_count = ui_content.line_count new_buffer = new_screen.data_buffer empty_char = _CHAR_CACHE['', Token] ZeroWidthEscape = Token.ZeroWidthEscape # Map visible line number to (row, col) of input. # 'col' will always be zero if line wrapping is off. visible_line_to_row_col = {} rowcol_to_yx = {} # Maps (row, col) from the input to (y, x) screen coordinates. # Fill background with default_char first. default_char = ui_content.default_char if default_char: for y in range(ypos, ypos + write_position.height): new_buffer_row = new_buffer[y] for x in range(xpos, xpos + width): new_buffer_row[x] = default_char # Copy content. def copy(): y = - vertical_scroll_2 lineno = vertical_scroll while y < write_position.height and lineno < line_count: # Take the next line and copy it in the real screen. line = ui_content.get_line(lineno) col = 0 x = -horizontal_scroll visible_line_to_row_col[y] = (lineno, horizontal_scroll) new_buffer_row = new_buffer[y + ypos] for token, text in line: # Remember raw VT escape sequences. (E.g. FinalTerm's # escape sequences.) if token == ZeroWidthEscape: new_screen.zero_width_escapes[y + ypos][x + xpos] += text continue for c in text: char = _CHAR_CACHE[c, token] char_width = char.width # Wrap when the line width is exceeded. if wrap_lines and x + char_width > width: visible_line_to_row_col[y + 1] = ( lineno, visible_line_to_row_col[y][1] + x) y += 1 x = -horizontal_scroll # This would be equal to zero. # (horizontal_scroll=0 when wrap_lines.) new_buffer_row = new_buffer[y + ypos] if y >= write_position.height: return y # Break out of all for loops. # Set character in screen and shift 'x'. if x >= 0 and y >= 0 and x < write_position.width: new_buffer_row[x + xpos] = char # When we print a multi width character, make sure # to erase the neighbous positions in the screen. # (The empty string if different from everything, # so next redraw this cell will repaint anyway.) if char_width > 1: for i in range(1, char_width): new_buffer_row[x + xpos + i] = empty_char # If this is a zero width characters, then it's # probably part of a decomposed unicode character. # See: https://en.wikipedia.org/wiki/Unicode_equivalence # Merge it in the previous cell. elif char_width == 0 and x - 1 >= 0: prev_char = new_buffer_row[x + xpos - 1] char2 = _CHAR_CACHE[prev_char.char + c, prev_char.token] new_buffer_row[x + xpos - 1] = char2 # Keep track of write position for each character. rowcol_to_yx[lineno, col] = (y + ypos, x + xpos) col += 1 x += char_width lineno += 1 y += 1 return y y = copy() def cursor_pos_to_screen_pos(row, col): " Translate row/col from UIContent to real Screen coordinates. " try: y, x = rowcol_to_yx[row, col] except KeyError: # Normally this should never happen. (It is a bug, if it happens.) # But to be sure, return (0, 0) return Point(y=0, x=0) # raise ValueError( # 'Invalid position. row=%r col=%r, vertical_scroll=%r, ' # 'horizontal_scroll=%r, height=%r' % # (row, col, vertical_scroll, horizontal_scroll, write_position.height)) else: return Point(y=y, x=x) # Set cursor and menu positions. if ui_content.cursor_position: screen_cursor_position = cursor_pos_to_screen_pos( ui_content.cursor_position.y, ui_content.cursor_position.x) if has_focus: new_screen.cursor_position = screen_cursor_position if always_hide_cursor: new_screen.show_cursor = False else: new_screen.show_cursor = ui_content.show_cursor self._highlight_digraph(cli, new_screen) if highlight_lines: self._highlight_cursorlines( cli, new_screen, screen_cursor_position, xpos, ypos, width, write_position.height) # Draw input characters from the input processor queue. if has_focus and ui_content.cursor_position: self._show_input_processor_key_buffer(cli, new_screen) # Set menu position. if not new_screen.menu_position and ui_content.menu_position: new_screen.menu_position = cursor_pos_to_screen_pos( ui_content.menu_position.y, ui_content.menu_position.x) # Update output screne height. new_screen.height = max(new_screen.height, ypos + write_position.height) return visible_line_to_row_col, rowcol_to_yx def _highlight_digraph(self, cli, new_screen): """ When we are in Vi digraph mode, put a question mark underneath the cursor. """ digraph_char = self._get_digraph_char(cli) if digraph_char: cpos = new_screen.cursor_position new_screen.data_buffer[cpos.y][cpos.x] = \ _CHAR_CACHE[digraph_char, Token.Digraph] def _show_input_processor_key_buffer(self, cli, new_screen): """ When the user is typing a key binding that consists of several keys, display the last pressed key if the user is in insert mode and the key is meaningful to be displayed. E.g. Some people want to bind 'jj' to escape in Vi insert mode. But the first 'j' needs to be displayed in order to get some feedback. """ key_buffer = cli.input_processor.key_buffer if key_buffer and _in_insert_mode(cli) and not cli.is_done: # The textual data for the given key. (Can be a VT100 escape # sequence.) data = key_buffer[-1].data # Display only if this is a 1 cell width character. if get_cwidth(data) == 1: cpos = new_screen.cursor_position new_screen.data_buffer[cpos.y][cpos.x] = \ _CHAR_CACHE[data, Token.PartialKeyBinding] def _highlight_cursorlines(self, cli, new_screen, cpos, x, y, width, height): """ Highlight cursor row/column. """ cursor_line_token = (':', ) + self.cursorline_token cursor_column_token = (':', ) + self.cursorcolumn_token data_buffer = new_screen.data_buffer # Highlight cursor line. if self.cursorline(cli): row = data_buffer[cpos.y] for x in range(x, x + width): original_char = row[x] row[x] = _CHAR_CACHE[ original_char.char, original_char.token + cursor_line_token] # Highlight cursor column. if self.cursorcolumn(cli): for y2 in range(y, y + height): row = data_buffer[y2] original_char = row[cpos.x] row[cpos.x] = _CHAR_CACHE[ original_char.char, original_char.token + cursor_column_token] # Highlight color columns for cc in self.get_colorcolumns(cli): assert isinstance(cc, ColorColumn) color_column_token = (':', ) + cc.token column = cc.position for y2 in range(y, y + height): row = data_buffer[y2] original_char = row[column] row[column] = _CHAR_CACHE[ original_char.char, original_char.token + color_column_token] def _copy_margin(self, cli, lazy_screen, new_screen, write_position, move_x, width): """ Copy characters from the margin screen to the real screen. """ xpos = write_position.xpos + move_x ypos = write_position.ypos margin_write_position = WritePosition(xpos, ypos, width, write_position.height) self._copy_body(cli, lazy_screen, new_screen, margin_write_position, 0, width) def _scroll_when_linewrapping(self, ui_content, width, height, cli): """ Scroll to make sure the cursor position is visible and that we maintain the requested scroll offset. Set `self.horizontal_scroll/vertical_scroll`. """ scroll_offsets_bottom = self.scroll_offsets.bottom scroll_offsets_top = self.scroll_offsets.top # We don't have horizontal scrolling. self.horizontal_scroll = 0 # If the current line consumes more than the whole window height, # then we have to scroll vertically inside this line. (We don't take # the scroll offsets into account for this.) # Also, ignore the scroll offsets in this case. Just set the vertical # scroll to this line. if ui_content.get_height_for_line(ui_content.cursor_position.y, width) > height - scroll_offsets_top: # Calculate the height of the text before the cursor, with the line # containing the cursor included, and the character belowe the # cursor included as well. line = explode_tokens(ui_content.get_line(ui_content.cursor_position.y)) text_before_cursor = token_list_to_text(line[:ui_content.cursor_position.x + 1]) text_before_height = UIContent.get_height_for_text(text_before_cursor, width) # Adjust scroll offset. self.vertical_scroll = ui_content.cursor_position.y self.vertical_scroll_2 = min(text_before_height - 1, self.vertical_scroll_2) self.vertical_scroll_2 = max(0, text_before_height - height, self.vertical_scroll_2) return else: self.vertical_scroll_2 = 0 # Current line doesn't consume the whole height. Take scroll offsets into account. def get_min_vertical_scroll(): # Make sure that the cursor line is not below the bottom. # (Calculate how many lines can be shown between the cursor and the .) used_height = 0 prev_lineno = ui_content.cursor_position.y for lineno in range(ui_content.cursor_position.y, -1, -1): used_height += ui_content.get_height_for_line(lineno, width) if used_height > height - scroll_offsets_bottom: return prev_lineno else: prev_lineno = lineno return 0 def get_max_vertical_scroll(): # Make sure that the cursor line is not above the top. prev_lineno = ui_content.cursor_position.y used_height = 0 for lineno in range(ui_content.cursor_position.y - 1, -1, -1): used_height += ui_content.get_height_for_line(lineno, width) if used_height > scroll_offsets_top: return prev_lineno else: prev_lineno = lineno return prev_lineno def get_topmost_visible(): """ Calculate the upper most line that can be visible, while the bottom is still visible. We should not allow scroll more than this if `allow_scroll_beyond_bottom` is false. """ prev_lineno = ui_content.line_count - 1 used_height = 0 for lineno in range(ui_content.line_count - 1, -1, -1): used_height += ui_content.get_height_for_line(lineno, width) if used_height > height: return prev_lineno else: prev_lineno = lineno return prev_lineno # Scroll vertically. (Make sure that the whole line which contains the # cursor is visible. topmost_visible = get_topmost_visible() # Note: the `min(topmost_visible, ...)` is to make sure that we # don't require scrolling up because of the bottom scroll offset, # when we are at the end of the document. self.vertical_scroll = max(self.vertical_scroll, min(topmost_visible, get_min_vertical_scroll())) self.vertical_scroll = min(self.vertical_scroll, get_max_vertical_scroll()) # Disallow scrolling beyond bottom? if not self.allow_scroll_beyond_bottom(cli): self.vertical_scroll = min(self.vertical_scroll, topmost_visible) def _scroll_without_linewrapping(self, ui_content, width, height, cli): """ Scroll to make sure the cursor position is visible and that we maintain the requested scroll offset. Set `self.horizontal_scroll/vertical_scroll`. """ cursor_position = ui_content.cursor_position or Point(0, 0) # Without line wrapping, we will never have to scroll vertically inside # a single line. self.vertical_scroll_2 = 0 if ui_content.line_count == 0: self.vertical_scroll = 0 self.horizontal_scroll = 0 return else: current_line_text = token_list_to_text(ui_content.get_line(cursor_position.y)) def do_scroll(current_scroll, scroll_offset_start, scroll_offset_end, cursor_pos, window_size, content_size): " Scrolling algorithm. Used for both horizontal and vertical scrolling. " # Calculate the scroll offset to apply. # This can obviously never be more than have the screen size. Also, when the # cursor appears at the top or bottom, we don't apply the offset. scroll_offset_start = int(min(scroll_offset_start, window_size / 2, cursor_pos)) scroll_offset_end = int(min(scroll_offset_end, window_size / 2, content_size - 1 - cursor_pos)) # Prevent negative scroll offsets. if current_scroll < 0: current_scroll = 0 # Scroll back if we scrolled to much and there's still space to show more of the document. if (not self.allow_scroll_beyond_bottom(cli) and current_scroll > content_size - window_size): current_scroll = max(0, content_size - window_size) # Scroll up if cursor is before visible part. if current_scroll > cursor_pos - scroll_offset_start: current_scroll = max(0, cursor_pos - scroll_offset_start) # Scroll down if cursor is after visible part. if current_scroll < (cursor_pos + 1) - window_size + scroll_offset_end: current_scroll = (cursor_pos + 1) - window_size + scroll_offset_end return current_scroll # When a preferred scroll is given, take that first into account. if self.get_vertical_scroll: self.vertical_scroll = self.get_vertical_scroll(self) assert isinstance(self.vertical_scroll, int) if self.get_horizontal_scroll: self.horizontal_scroll = self.get_horizontal_scroll(self) assert isinstance(self.horizontal_scroll, int) # Update horizontal/vertical scroll to make sure that the cursor # remains visible. offsets = self.scroll_offsets self.vertical_scroll = do_scroll( current_scroll=self.vertical_scroll, scroll_offset_start=offsets.top, scroll_offset_end=offsets.bottom, cursor_pos=ui_content.cursor_position.y, window_size=height, content_size=ui_content.line_count) self.horizontal_scroll = do_scroll( current_scroll=self.horizontal_scroll, scroll_offset_start=offsets.left, scroll_offset_end=offsets.right, cursor_pos=get_cwidth(current_line_text[:ui_content.cursor_position.x]), window_size=width, # We can only analyse the current line. Calculating the width off # all the lines is too expensive. content_size=max(get_cwidth(current_line_text), self.horizontal_scroll + width)) def _mouse_handler(self, cli, mouse_event): """ Mouse handler. Called when the UI control doesn't handle this particular event. """ if mouse_event.event_type == MouseEventType.SCROLL_DOWN: self._scroll_down(cli) elif mouse_event.event_type == MouseEventType.SCROLL_UP: self._scroll_up(cli) def _scroll_down(self, cli): " Scroll window down. " info = self.render_info if self.vertical_scroll < info.content_height - info.window_height: if info.cursor_position.y <= info.configured_scroll_offsets.top: self.content.move_cursor_down(cli) self.vertical_scroll += 1 def _scroll_up(self, cli): " Scroll window up. " info = self.render_info if info.vertical_scroll > 0: # TODO: not entirely correct yet in case of line wrapping and long lines. if info.cursor_position.y >= info.window_height - 1 - info.configured_scroll_offsets.bottom: self.content.move_cursor_up(cli) self.vertical_scroll -= 1 def walk(self, cli): # Only yield self. A window doesn't have children. yield self class ConditionalContainer(Container): """ Wrapper around any other container that can change the visibility. The received `filter` determines whether the given container should be displayed or not. :param content: :class:`.Container` instance. :param filter: :class:`~prompt_toolkit.filters.CLIFilter` instance. """ def __init__(self, content, filter): assert isinstance(content, Container) self.content = content self.filter = to_cli_filter(filter) def __repr__(self): return 'ConditionalContainer(%r, filter=%r)' % (self.content, self.filter) def reset(self): self.content.reset() def preferred_width(self, cli, max_available_width): if self.filter(cli): return self.content.preferred_width(cli, max_available_width) else: return LayoutDimension.exact(0) def preferred_height(self, cli, width, max_available_height): if self.filter(cli): return self.content.preferred_height(cli, width, max_available_height) else: return LayoutDimension.exact(0) def write_to_screen(self, cli, screen, mouse_handlers, write_position): if self.filter(cli): return self.content.write_to_screen(cli, screen, mouse_handlers, write_position) def walk(self, cli): return self.content.walk(cli) # Deprecated alias for 'Container'. Layout = Container prompt_toolkit-1.0.15/prompt_toolkit/layout/screen.py0000664000175000017500000001060113136130420024537 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.cache import FastDictCache from prompt_toolkit.token import Token from prompt_toolkit.utils import get_cwidth from collections import defaultdict, namedtuple __all__ = ( 'Point', 'Size', 'Screen', 'Char', ) Point = namedtuple('Point', 'y x') Size = namedtuple('Size', 'rows columns') class Char(object): """ Represent a single character in a :class:`.Screen`. This should be considered immutable. """ __slots__ = ('char', 'token', 'width') # If we end up having one of these special control sequences in the input string, # we should display them as follows: # Usually this happens after a "quoted insert". display_mappings = { '\x00': '^@', # Control space '\x01': '^A', '\x02': '^B', '\x03': '^C', '\x04': '^D', '\x05': '^E', '\x06': '^F', '\x07': '^G', '\x08': '^H', '\x09': '^I', '\x0a': '^J', '\x0b': '^K', '\x0c': '^L', '\x0d': '^M', '\x0e': '^N', '\x0f': '^O', '\x10': '^P', '\x11': '^Q', '\x12': '^R', '\x13': '^S', '\x14': '^T', '\x15': '^U', '\x16': '^V', '\x17': '^W', '\x18': '^X', '\x19': '^Y', '\x1a': '^Z', '\x1b': '^[', # Escape '\x1c': '^\\', '\x1d': '^]', '\x1f': '^_', '\x7f': '^?', # Backspace } def __init__(self, char=' ', token=Token): # If this character has to be displayed otherwise, take that one. char = self.display_mappings.get(char, char) self.char = char self.token = token # Calculate width. (We always need this, so better to store it directly # as a member for performance.) self.width = get_cwidth(char) def __eq__(self, other): return self.char == other.char and self.token == other.token def __ne__(self, other): # Not equal: We don't do `not char.__eq__` here, because of the # performance of calling yet another function. return self.char != other.char or self.token != other.token def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.char, self.token) _CHAR_CACHE = FastDictCache(Char, size=1000 * 1000) Transparent = Token.Transparent class Screen(object): """ Two dimentional buffer of :class:`.Char` instances. """ def __init__(self, default_char=None, initial_width=0, initial_height=0): if default_char is None: default_char = _CHAR_CACHE[' ', Transparent] self.data_buffer = defaultdict(lambda: defaultdict(lambda: default_char)) #: Escape sequences to be injected. self.zero_width_escapes = defaultdict(lambda: defaultdict(lambda: '')) #: Position of the cursor. self.cursor_position = Point(y=0, x=0) #: Visibility of the cursor. self.show_cursor = True #: (Optional) Where to position the menu. E.g. at the start of a completion. #: (We can't use the cursor position, because we don't want the #: completion menu to change its position when we browse through all the #: completions.) self.menu_position = None #: Currently used width/height of the screen. This will increase when #: data is written to the screen. self.width = initial_width or 0 self.height = initial_height or 0 def replace_all_tokens(self, token): """ For all the characters in the screen. Set the token to the given `token`. """ b = self.data_buffer for y, row in b.items(): for x, char in row.items(): b[y][x] = _CHAR_CACHE[char.char, token] class WritePosition(object): def __init__(self, xpos, ypos, width, height, extended_height=None): assert height >= 0 assert extended_height is None or extended_height >= 0 assert width >= 0 # xpos and ypos can be negative. (A float can be partially visible.) self.xpos = xpos self.ypos = ypos self.width = width self.height = height self.extended_height = extended_height or height def __repr__(self): return '%s(%r, %r, %r, %r, %r)' % ( self.__class__.__name__, self.xpos, self.ypos, self.width, self.height, self.extended_height) prompt_toolkit-1.0.15/prompt_toolkit/layout/processors.py0000664000175000017500000005235313136130420025474 0ustar jonathanjonathan00000000000000""" Processors are little transformation blocks that transform the token list from a buffer before the BufferControl will render it to the screen. They can insert tokens before or after, or highlight fragments by replacing the token types. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from six.moves import range from prompt_toolkit.cache import SimpleCache from prompt_toolkit.document import Document from prompt_toolkit.enums import SEARCH_BUFFER from prompt_toolkit.filters import to_cli_filter, ViInsertMultipleMode from prompt_toolkit.layout.utils import token_list_to_text from prompt_toolkit.reactive import Integer from prompt_toolkit.token import Token from .utils import token_list_len, explode_tokens import re __all__ = ( 'Processor', 'Transformation', 'HighlightSearchProcessor', 'HighlightSelectionProcessor', 'PasswordProcessor', 'HighlightMatchingBracketProcessor', 'DisplayMultipleCursors', 'BeforeInput', 'AfterInput', 'AppendAutoSuggestion', 'ConditionalProcessor', 'ShowLeadingWhiteSpaceProcessor', 'ShowTrailingWhiteSpaceProcessor', 'TabsProcessor', ) class Processor(with_metaclass(ABCMeta, object)): """ Manipulate the tokens for a given line in a :class:`~prompt_toolkit.layout.controls.BufferControl`. """ @abstractmethod def apply_transformation(self, cli, document, lineno, source_to_display, tokens): """ Apply transformation. Returns a :class:`.Transformation` instance. :param cli: :class:`.CommandLineInterface` instance. :param lineno: The number of the line to which we apply the processor. :param source_to_display: A function that returns the position in the `tokens` for any position in the source string. (This takes previous processors into account.) :param tokens: List of tokens that we can transform. (Received from the previous processor.) """ return Transformation(tokens) def has_focus(self, cli): """ Processors can override the focus. (Used for the reverse-i-search prefix in DefaultPrompt.) """ return False class Transformation(object): """ Transformation result, as returned by :meth:`.Processor.apply_transformation`. Important: Always make sure that the length of `document.text` is equal to the length of all the text in `tokens`! :param tokens: The transformed tokens. To be displayed, or to pass to the next processor. :param source_to_display: Cursor position transformation from original string to transformed string. :param display_to_source: Cursor position transformed from source string to original string. """ def __init__(self, tokens, source_to_display=None, display_to_source=None): self.tokens = tokens self.source_to_display = source_to_display or (lambda i: i) self.display_to_source = display_to_source or (lambda i: i) class HighlightSearchProcessor(Processor): """ Processor that highlights search matches in the document. Note that this doesn't support multiline search matches yet. :param preview_search: A Filter; when active it indicates that we take the search text in real time while the user is typing, instead of the last active search state. """ def __init__(self, preview_search=False, search_buffer_name=SEARCH_BUFFER, get_search_state=None): self.preview_search = to_cli_filter(preview_search) self.search_buffer_name = search_buffer_name self.get_search_state = get_search_state or (lambda cli: cli.search_state) def _get_search_text(self, cli): """ The text we are searching for. """ # When the search buffer has focus, take that text. if self.preview_search(cli) and cli.buffers[self.search_buffer_name].text: return cli.buffers[self.search_buffer_name].text # Otherwise, take the text of the last active search. else: return self.get_search_state(cli).text def apply_transformation(self, cli, document, lineno, source_to_display, tokens): search_text = self._get_search_text(cli) searchmatch_current_token = (':', ) + Token.SearchMatch.Current searchmatch_token = (':', ) + Token.SearchMatch if search_text and not cli.is_returning: # For each search match, replace the Token. line_text = token_list_to_text(tokens) tokens = explode_tokens(tokens) flags = re.IGNORECASE if cli.is_ignoring_case else 0 # Get cursor column. if document.cursor_position_row == lineno: cursor_column = source_to_display(document.cursor_position_col) else: cursor_column = None for match in re.finditer(re.escape(search_text), line_text, flags=flags): if cursor_column is not None: on_cursor = match.start() <= cursor_column < match.end() else: on_cursor = False for i in range(match.start(), match.end()): old_token, text = tokens[i] if on_cursor: tokens[i] = (old_token + searchmatch_current_token, tokens[i][1]) else: tokens[i] = (old_token + searchmatch_token, tokens[i][1]) return Transformation(tokens) class HighlightSelectionProcessor(Processor): """ Processor that highlights the selection in the document. """ def apply_transformation(self, cli, document, lineno, source_to_display, tokens): selected_token = (':', ) + Token.SelectedText # In case of selection, highlight all matches. selection_at_line = document.selection_range_at_line(lineno) if selection_at_line: from_, to = selection_at_line from_ = source_to_display(from_) to = source_to_display(to) tokens = explode_tokens(tokens) if from_ == 0 and to == 0 and len(tokens) == 0: # When this is an empty line, insert a space in order to # visualiase the selection. return Transformation([(Token.SelectedText, ' ')]) else: for i in range(from_, to + 1): if i < len(tokens): old_token, old_text = tokens[i] tokens[i] = (old_token + selected_token, old_text) return Transformation(tokens) class PasswordProcessor(Processor): """ Processor that turns masks the input. (For passwords.) :param char: (string) Character to be used. "*" by default. """ def __init__(self, char='*'): self.char = char def apply_transformation(self, cli, document, lineno, source_to_display, tokens): tokens = [(token, self.char * len(text)) for token, text in tokens] return Transformation(tokens) class HighlightMatchingBracketProcessor(Processor): """ When the cursor is on or right after a bracket, it highlights the matching bracket. :param max_cursor_distance: Only highlight matching brackets when the cursor is within this distance. (From inside a `Processor`, we can't know which lines will be visible on the screen. But we also don't want to scan the whole document for matching brackets on each key press, so we limit to this value.) """ _closing_braces = '])}>' def __init__(self, chars='[](){}<>', max_cursor_distance=1000): self.chars = chars self.max_cursor_distance = max_cursor_distance self._positions_cache = SimpleCache(maxsize=8) def _get_positions_to_highlight(self, document): """ Return a list of (row, col) tuples that need to be highlighted. """ # Try for the character under the cursor. if document.current_char and document.current_char in self.chars: pos = document.find_matching_bracket_position( start_pos=document.cursor_position - self.max_cursor_distance, end_pos=document.cursor_position + self.max_cursor_distance) # Try for the character before the cursor. elif (document.char_before_cursor and document.char_before_cursor in self._closing_braces and document.char_before_cursor in self.chars): document = Document(document.text, document.cursor_position - 1) pos = document.find_matching_bracket_position( start_pos=document.cursor_position - self.max_cursor_distance, end_pos=document.cursor_position + self.max_cursor_distance) else: pos = None # Return a list of (row, col) tuples that need to be highlighted. if pos: pos += document.cursor_position # pos is relative. row, col = document.translate_index_to_position(pos) return [(row, col), (document.cursor_position_row, document.cursor_position_col)] else: return [] def apply_transformation(self, cli, document, lineno, source_to_display, tokens): # Get the highlight positions. key = (cli.render_counter, document.text, document.cursor_position) positions = self._positions_cache.get( key, lambda: self._get_positions_to_highlight(document)) # Apply if positions were found at this line. if positions: for row, col in positions: if row == lineno: col = source_to_display(col) tokens = explode_tokens(tokens) token, text = tokens[col] if col == document.cursor_position_col: token += (':', ) + Token.MatchingBracket.Cursor else: token += (':', ) + Token.MatchingBracket.Other tokens[col] = (token, text) return Transformation(tokens) class DisplayMultipleCursors(Processor): """ When we're in Vi block insert mode, display all the cursors. """ _insert_multiple = ViInsertMultipleMode() def __init__(self, buffer_name): self.buffer_name = buffer_name def apply_transformation(self, cli, document, lineno, source_to_display, tokens): buff = cli.buffers[self.buffer_name] if self._insert_multiple(cli): positions = buff.multiple_cursor_positions tokens = explode_tokens(tokens) # If any cursor appears on the current line, highlight that. start_pos = document.translate_row_col_to_index(lineno, 0) end_pos = start_pos + len(document.lines[lineno]) token_suffix = (':', ) + Token.MultipleCursors.Cursor for p in positions: if start_pos <= p < end_pos: column = source_to_display(p - start_pos) # Replace token. token, text = tokens[column] token += token_suffix tokens[column] = (token, text) elif p == end_pos: tokens.append((token_suffix, ' ')) return Transformation(tokens) else: return Transformation(tokens) class BeforeInput(Processor): """ Insert tokens before the input. :param get_tokens: Callable that takes a :class:`~prompt_toolkit.interface.CommandLineInterface` and returns the list of tokens to be inserted. """ def __init__(self, get_tokens): assert callable(get_tokens) self.get_tokens = get_tokens def apply_transformation(self, cli, document, lineno, source_to_display, tokens): if lineno == 0: tokens_before = self.get_tokens(cli) tokens = tokens_before + tokens shift_position = token_list_len(tokens_before) source_to_display = lambda i: i + shift_position display_to_source = lambda i: i - shift_position else: source_to_display = None display_to_source = None return Transformation(tokens, source_to_display=source_to_display, display_to_source=display_to_source) @classmethod def static(cls, text, token=Token): """ Create a :class:`.BeforeInput` instance that always inserts the same text. """ def get_static_tokens(cli): return [(token, text)] return cls(get_static_tokens) def __repr__(self): return '%s(get_tokens=%r)' % ( self.__class__.__name__, self.get_tokens) class AfterInput(Processor): """ Insert tokens after the input. :param get_tokens: Callable that takes a :class:`~prompt_toolkit.interface.CommandLineInterface` and returns the list of tokens to be appended. """ def __init__(self, get_tokens): assert callable(get_tokens) self.get_tokens = get_tokens def apply_transformation(self, cli, document, lineno, source_to_display, tokens): # Insert tokens after the last line. if lineno == document.line_count - 1: return Transformation(tokens=tokens + self.get_tokens(cli)) else: return Transformation(tokens=tokens) @classmethod def static(cls, text, token=Token): """ Create a :class:`.AfterInput` instance that always inserts the same text. """ def get_static_tokens(cli): return [(token, text)] return cls(get_static_tokens) def __repr__(self): return '%s(get_tokens=%r)' % ( self.__class__.__name__, self.get_tokens) class AppendAutoSuggestion(Processor): """ Append the auto suggestion to the input. (The user can then press the right arrow the insert the suggestion.) :param buffer_name: The name of the buffer from where we should take the auto suggestion. If not given, we take the current buffer. """ def __init__(self, buffer_name=None, token=Token.AutoSuggestion): self.buffer_name = buffer_name self.token = token def _get_buffer(self, cli): if self.buffer_name: return cli.buffers[self.buffer_name] else: return cli.current_buffer def apply_transformation(self, cli, document, lineno, source_to_display, tokens): # Insert tokens after the last line. if lineno == document.line_count - 1: buffer = self._get_buffer(cli) if buffer.suggestion and buffer.document.is_cursor_at_the_end: suggestion = buffer.suggestion.text else: suggestion = '' return Transformation(tokens=tokens + [(self.token, suggestion)]) else: return Transformation(tokens=tokens) class ShowLeadingWhiteSpaceProcessor(Processor): """ Make leading whitespace visible. :param get_char: Callable that takes a :class:`CommandLineInterface` instance and returns one character. :param token: Token to be used. """ def __init__(self, get_char=None, token=Token.LeadingWhiteSpace): assert get_char is None or callable(get_char) if get_char is None: def get_char(cli): if '\xb7'.encode(cli.output.encoding(), 'replace') == b'?': return '.' else: return '\xb7' self.token = token self.get_char = get_char def apply_transformation(self, cli, document, lineno, source_to_display, tokens): # Walk through all te tokens. if tokens and token_list_to_text(tokens).startswith(' '): t = (self.token, self.get_char(cli)) tokens = explode_tokens(tokens) for i in range(len(tokens)): if tokens[i][1] == ' ': tokens[i] = t else: break return Transformation(tokens) class ShowTrailingWhiteSpaceProcessor(Processor): """ Make trailing whitespace visible. :param get_char: Callable that takes a :class:`CommandLineInterface` instance and returns one character. :param token: Token to be used. """ def __init__(self, get_char=None, token=Token.TrailingWhiteSpace): assert get_char is None or callable(get_char) if get_char is None: def get_char(cli): if '\xb7'.encode(cli.output.encoding(), 'replace') == b'?': return '.' else: return '\xb7' self.token = token self.get_char = get_char def apply_transformation(self, cli, document, lineno, source_to_display, tokens): if tokens and tokens[-1][1].endswith(' '): t = (self.token, self.get_char(cli)) tokens = explode_tokens(tokens) # Walk backwards through all te tokens and replace whitespace. for i in range(len(tokens) - 1, -1, -1): char = tokens[i][1] if char == ' ': tokens[i] = t else: break return Transformation(tokens) class TabsProcessor(Processor): """ Render tabs as spaces (instead of ^I) or make them visible (for instance, by replacing them with dots.) :param tabstop: (Integer) Horizontal space taken by a tab. :param get_char1: Callable that takes a `CommandLineInterface` and return a character (text of length one). This one is used for the first space taken by the tab. :param get_char2: Like `get_char1`, but for the rest of the space. """ def __init__(self, tabstop=4, get_char1=None, get_char2=None, token=Token.Tab): assert isinstance(tabstop, Integer) assert get_char1 is None or callable(get_char1) assert get_char2 is None or callable(get_char2) self.get_char1 = get_char1 or get_char2 or (lambda cli: '|') self.get_char2 = get_char2 or get_char1 or (lambda cli: '\u2508') self.tabstop = tabstop self.token = token def apply_transformation(self, cli, document, lineno, source_to_display, tokens): tabstop = int(self.tabstop) token = self.token # Create separator for tabs. separator1 = self.get_char1(cli) separator2 = self.get_char2(cli) # Transform tokens. tokens = explode_tokens(tokens) position_mappings = {} result_tokens = [] pos = 0 for i, token_and_text in enumerate(tokens): position_mappings[i] = pos if token_and_text[1] == '\t': # Calculate how many characters we have to insert. count = tabstop - (pos % tabstop) if count == 0: count = tabstop # Insert tab. result_tokens.append((token, separator1)) result_tokens.append((token, separator2 * (count - 1))) pos += count else: result_tokens.append(token_and_text) pos += 1 position_mappings[len(tokens)] = pos def source_to_display(from_position): " Maps original cursor position to the new one. " return position_mappings[from_position] def display_to_source(display_pos): " Maps display cursor position to the original one. " position_mappings_reversed = dict((v, k) for k, v in position_mappings.items()) while display_pos >= 0: try: return position_mappings_reversed[display_pos] except KeyError: display_pos -= 1 return 0 return Transformation( result_tokens, source_to_display=source_to_display, display_to_source=display_to_source) class ConditionalProcessor(Processor): """ Processor that applies another processor, according to a certain condition. Example:: # Create a function that returns whether or not the processor should # currently be applied. def highlight_enabled(cli): return true_or_false # Wrapt it in a `ConditionalProcessor` for usage in a `BufferControl`. BufferControl(input_processors=[ ConditionalProcessor(HighlightSearchProcessor(), Condition(highlight_enabled))]) :param processor: :class:`.Processor` instance. :param filter: :class:`~prompt_toolkit.filters.CLIFilter` instance. """ def __init__(self, processor, filter): assert isinstance(processor, Processor) self.processor = processor self.filter = to_cli_filter(filter) def apply_transformation(self, cli, document, lineno, source_to_display, tokens): # Run processor when enabled. if self.filter(cli): return self.processor.apply_transformation( cli, document, lineno, source_to_display, tokens) else: return Transformation(tokens) def has_focus(self, cli): if self.filter(cli): return self.processor.has_focus(cli) else: return False def __repr__(self): return '%s(processor=%r, filter=%r)' % ( self.__class__.__name__, self.processor, self.filter) prompt_toolkit-1.0.15/prompt_toolkit/layout/dimension.py0000664000175000017500000000612213136130420025250 0ustar jonathanjonathan00000000000000""" Layout dimensions are used to give the minimum, maximum and preferred dimensions for containers and controls. """ from __future__ import unicode_literals __all__ = ( 'LayoutDimension', 'sum_layout_dimensions', 'max_layout_dimensions', ) class LayoutDimension(object): """ Specified dimension (width/height) of a user control or window. The layout engine tries to honor the preferred size. If that is not possible, because the terminal is larger or smaller, it tries to keep in between min and max. :param min: Minimum size. :param max: Maximum size. :param weight: For a VSplit/HSplit, the actual size will be determined by taking the proportion of weights from all the children. E.g. When there are two children, one width a weight of 1, and the other with a weight of 2. The second will always be twice as big as the first, if the min/max values allow it. :param preferred: Preferred size. """ def __init__(self, min=None, max=None, weight=1, preferred=None): assert isinstance(weight, int) and weight > 0 # Cannot be a float. self.min_specified = min is not None self.max_specified = max is not None self.preferred_specified = preferred is not None if min is None: min = 0 # Smallest possible value. if max is None: # 0-values are allowed, so use "is None" max = 1000 ** 10 # Something huge. if preferred is None: preferred = min self.min = min self.max = max self.preferred = preferred self.weight = weight # Make sure that the 'preferred' size is always in the min..max range. if self.preferred < self.min: self.preferred = self.min if self.preferred > self.max: self.preferred = self.max @classmethod def exact(cls, amount): """ Return a :class:`.LayoutDimension` with an exact size. (min, max and preferred set to ``amount``). """ return cls(min=amount, max=amount, preferred=amount) def __repr__(self): return 'LayoutDimension(min=%r, max=%r, preferred=%r, weight=%r)' % ( self.min, self.max, self.preferred, self.weight) def __add__(self, other): return sum_layout_dimensions([self, other]) def sum_layout_dimensions(dimensions): """ Sum a list of :class:`.LayoutDimension` instances. """ min = sum([d.min for d in dimensions if d.min is not None]) max = sum([d.max for d in dimensions if d.max is not None]) preferred = sum([d.preferred for d in dimensions]) return LayoutDimension(min=min, max=max, preferred=preferred) def max_layout_dimensions(dimensions): """ Take the maximum of a list of :class:`.LayoutDimension` instances. """ min_ = max([d.min for d in dimensions if d.min is not None]) max_ = max([d.max for d in dimensions if d.max is not None]) preferred = max([d.preferred for d in dimensions]) return LayoutDimension(min=min_, max=max_, preferred=preferred) prompt_toolkit-1.0.15/prompt_toolkit/layout/lexers.py0000664000175000017500000002610613136130420024571 0ustar jonathanjonathan00000000000000""" Lexer interface and implementation. Used for syntax highlighting. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from six.moves import range from prompt_toolkit.token import Token from prompt_toolkit.filters import to_cli_filter from .utils import split_lines import re import six __all__ = ( 'Lexer', 'SimpleLexer', 'PygmentsLexer', 'SyntaxSync', 'SyncFromStart', 'RegexSync', ) class Lexer(with_metaclass(ABCMeta, object)): """ Base class for all lexers. """ @abstractmethod def lex_document(self, cli, document): """ Takes a :class:`~prompt_toolkit.document.Document` and returns a callable that takes a line number and returns the tokens for that line. """ class SimpleLexer(Lexer): """ Lexer that doesn't do any tokenizing and returns the whole input as one token. :param token: The `Token` for this lexer. """ # `default_token` parameter is deprecated! def __init__(self, token=Token, default_token=None): self.token = token if default_token is not None: self.token = default_token def lex_document(self, cli, document): lines = document.lines def get_line(lineno): " Return the tokens for the given line. " try: return [(self.token, lines[lineno])] except IndexError: return [] return get_line class SyntaxSync(with_metaclass(ABCMeta, object)): """ Syntax synchroniser. This is a tool that finds a start position for the lexer. This is especially important when editing big documents; we don't want to start the highlighting by running the lexer from the beginning of the file. That is very slow when editing. """ @abstractmethod def get_sync_start_position(self, document, lineno): """ Return the position from where we can start lexing as a (row, column) tuple. :param document: `Document` instance that contains all the lines. :param lineno: The line that we want to highlight. (We need to return this line, or an earlier position.) """ class SyncFromStart(SyntaxSync): """ Always start the syntax highlighting from the beginning. """ def get_sync_start_position(self, document, lineno): return 0, 0 class RegexSync(SyntaxSync): """ Synchronize by starting at a line that matches the given regex pattern. """ # Never go more than this amount of lines backwards for synchronisation. # That would be too CPU intensive. MAX_BACKWARDS = 500 # Start lexing at the start, if we are in the first 'n' lines and no # synchronisation position was found. FROM_START_IF_NO_SYNC_POS_FOUND = 100 def __init__(self, pattern): assert isinstance(pattern, six.text_type) self._compiled_pattern = re.compile(pattern) def get_sync_start_position(self, document, lineno): " Scan backwards, and find a possible position to start. " pattern = self._compiled_pattern lines = document.lines # Scan upwards, until we find a point where we can start the syntax # synchronisation. for i in range(lineno, max(-1, lineno - self.MAX_BACKWARDS), -1): match = pattern.match(lines[i]) if match: return i, match.start() # No synchronisation point found. If we aren't that far from the # beginning, start at the very beginning, otherwise, just try to start # at the current line. if lineno < self.FROM_START_IF_NO_SYNC_POS_FOUND: return 0, 0 else: return lineno, 0 @classmethod def from_pygments_lexer_cls(cls, lexer_cls): """ Create a :class:`.RegexSync` instance for this Pygments lexer class. """ patterns = { # For Python, start highlighting at any class/def block. 'Python': r'^\s*(class|def)\s+', 'Python 3': r'^\s*(class|def)\s+', # For HTML, start at any open/close tag definition. 'HTML': r'<[/a-zA-Z]', # For javascript, start at a function. 'JavaScript': r'\bfunction\b' # TODO: Add definitions for other languages. # By default, we start at every possible line. } p = patterns.get(lexer_cls.name, '^') return cls(p) class PygmentsLexer(Lexer): """ Lexer that calls a pygments lexer. Example:: from pygments.lexers import HtmlLexer lexer = PygmentsLexer(HtmlLexer) Note: Don't forget to also load a Pygments compatible style. E.g.:: from prompt_toolkit.styles.from_pygments import style_from_pygments from pygments.styles import get_style_by_name style = style_from_pygments(get_style_by_name('monokai')) :param pygments_lexer_cls: A `Lexer` from Pygments. :param sync_from_start: Start lexing at the start of the document. This will always give the best results, but it will be slow for bigger documents. (When the last part of the document is display, then the whole document will be lexed by Pygments on every key stroke.) It is recommended to disable this for inputs that are expected to be more than 1,000 lines. :param syntax_sync: `SyntaxSync` object. """ # Minimum amount of lines to go backwards when starting the parser. # This is important when the lines are retrieved in reverse order, or when # scrolling upwards. (Due to the complexity of calculating the vertical # scroll offset in the `Window` class, lines are not always retrieved in # order.) MIN_LINES_BACKWARDS = 50 # When a parser was started this amount of lines back, read the parser # until we get the current line. Otherwise, start a new parser. # (This should probably be bigger than MIN_LINES_BACKWARDS.) REUSE_GENERATOR_MAX_DISTANCE = 100 def __init__(self, pygments_lexer_cls, sync_from_start=True, syntax_sync=None): assert syntax_sync is None or isinstance(syntax_sync, SyntaxSync) self.pygments_lexer_cls = pygments_lexer_cls self.sync_from_start = to_cli_filter(sync_from_start) # Instantiate the Pygments lexer. self.pygments_lexer = pygments_lexer_cls( stripnl=False, stripall=False, ensurenl=False) # Create syntax sync instance. self.syntax_sync = syntax_sync or RegexSync.from_pygments_lexer_cls(pygments_lexer_cls) @classmethod def from_filename(cls, filename, sync_from_start=True): """ Create a `Lexer` from a filename. """ # Inline imports: the Pygments dependency is optional! from pygments.util import ClassNotFound from pygments.lexers import get_lexer_for_filename try: pygments_lexer = get_lexer_for_filename(filename) except ClassNotFound: return SimpleLexer() else: return cls(pygments_lexer.__class__, sync_from_start=sync_from_start) def lex_document(self, cli, document): """ Create a lexer function that takes a line number and returns the list of (Token, text) tuples as the Pygments lexer returns for that line. """ # Cache of already lexed lines. cache = {} # Pygments generators that are currently lexing. line_generators = {} # Map lexer generator to the line number. def get_syntax_sync(): " The Syntax synchronisation objcet that we currently use. " if self.sync_from_start(cli): return SyncFromStart() else: return self.syntax_sync def find_closest_generator(i): " Return a generator close to line 'i', or None if none was fonud. " for generator, lineno in line_generators.items(): if lineno < i and i - lineno < self.REUSE_GENERATOR_MAX_DISTANCE: return generator def create_line_generator(start_lineno, column=0): """ Create a generator that yields the lexed lines. Each iteration it yields a (line_number, [(token, text), ...]) tuple. """ def get_tokens(): text = '\n'.join(document.lines[start_lineno:])[column:] # We call `get_tokens_unprocessed`, because `get_tokens` will # still replace \r\n and \r by \n. (We don't want that, # Pygments should return exactly the same amount of text, as we # have given as input.) for _, t, v in self.pygments_lexer.get_tokens_unprocessed(text): yield t, v return enumerate(split_lines(get_tokens()), start_lineno) def get_generator(i): """ Find an already started generator that is close, or create a new one. """ # Find closest line generator. generator = find_closest_generator(i) if generator: return generator # No generator found. Determine starting point for the syntax # synchronisation first. # Go at least x lines back. (Make scrolling upwards more # efficient.) i = max(0, i - self.MIN_LINES_BACKWARDS) if i == 0: row = 0 column = 0 else: row, column = get_syntax_sync().get_sync_start_position(document, i) # Find generator close to this point, or otherwise create a new one. generator = find_closest_generator(i) if generator: return generator else: generator = create_line_generator(row, column) # If the column is not 0, ignore the first line. (Which is # incomplete. This happens when the synchronisation algorithm tells # us to start parsing in the middle of a line.) if column: next(generator) row += 1 line_generators[generator] = row return generator def get_line(i): " Return the tokens for a given line number. " try: return cache[i] except KeyError: generator = get_generator(i) # Exhaust the generator, until we find the requested line. for num, line in generator: cache[num] = line if num == i: line_generators[generator] = i # Remove the next item from the cache. # (It could happen that it's already there, because of # another generator that started filling these lines, # but we want to synchronise these lines with the # current lexer's state.) if num + 1 in cache: del cache[num + 1] return cache[num] return [] return get_line prompt_toolkit-1.0.15/prompt_toolkit/layout/toolbars.py0000664000175000017500000001557713136130420025126 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from ..enums import IncrementalSearchDirection from .processors import BeforeInput from .lexers import SimpleLexer from .dimension import LayoutDimension from .controls import BufferControl, TokenListControl, UIControl, UIContent from .containers import Window, ConditionalContainer from .screen import Char from .utils import token_list_len from prompt_toolkit.enums import SEARCH_BUFFER, SYSTEM_BUFFER from prompt_toolkit.filters import HasFocus, HasArg, HasCompletions, HasValidationError, HasSearch, Always, IsDone from prompt_toolkit.token import Token __all__ = ( 'TokenListToolbar', 'ArgToolbar', 'CompletionsToolbar', 'SearchToolbar', 'SystemToolbar', 'ValidationToolbar', ) class TokenListToolbar(ConditionalContainer): def __init__(self, get_tokens, filter=Always(), **kw): super(TokenListToolbar, self).__init__( content=Window( TokenListControl(get_tokens, **kw), height=LayoutDimension.exact(1)), filter=filter) class SystemToolbarControl(BufferControl): def __init__(self): token = Token.Toolbar.System super(SystemToolbarControl, self).__init__( buffer_name=SYSTEM_BUFFER, default_char=Char(token=token), lexer=SimpleLexer(token=token.Text), input_processors=[BeforeInput.static('Shell command: ', token)],) class SystemToolbar(ConditionalContainer): def __init__(self): super(SystemToolbar, self).__init__( content=Window( SystemToolbarControl(), height=LayoutDimension.exact(1)), filter=HasFocus(SYSTEM_BUFFER) & ~IsDone()) class ArgToolbarControl(TokenListControl): def __init__(self): def get_tokens(cli): arg = cli.input_processor.arg if arg == '-': arg = '-1' return [ (Token.Toolbar.Arg, 'Repeat: '), (Token.Toolbar.Arg.Text, arg), ] super(ArgToolbarControl, self).__init__(get_tokens) class ArgToolbar(ConditionalContainer): def __init__(self): super(ArgToolbar, self).__init__( content=Window( ArgToolbarControl(), height=LayoutDimension.exact(1)), filter=HasArg()) class SearchToolbarControl(BufferControl): """ :param vi_mode: Display '/' and '?' instead of I-search. """ def __init__(self, vi_mode=False): token = Token.Toolbar.Search def get_before_input(cli): if not cli.is_searching: text = '' elif cli.search_state.direction == IncrementalSearchDirection.BACKWARD: text = ('?' if vi_mode else 'I-search backward: ') else: text = ('/' if vi_mode else 'I-search: ') return [(token, text)] super(SearchToolbarControl, self).__init__( buffer_name=SEARCH_BUFFER, input_processors=[BeforeInput(get_before_input)], default_char=Char(token=token), lexer=SimpleLexer(token=token.Text)) class SearchToolbar(ConditionalContainer): def __init__(self, vi_mode=False): super(SearchToolbar, self).__init__( content=Window( SearchToolbarControl(vi_mode=vi_mode), height=LayoutDimension.exact(1)), filter=HasSearch() & ~IsDone()) class CompletionsToolbarControl(UIControl): token = Token.Toolbar.Completions def create_content(self, cli, width, height): complete_state = cli.current_buffer.complete_state if complete_state: completions = complete_state.current_completions index = complete_state.complete_index # Can be None! # Width of the completions without the left/right arrows in the margins. content_width = width - 6 # Booleans indicating whether we stripped from the left/right cut_left = False cut_right = False # Create Menu content. tokens = [] for i, c in enumerate(completions): # When there is no more place for the next completion if token_list_len(tokens) + len(c.display) >= content_width: # If the current one was not yet displayed, page to the next sequence. if i <= (index or 0): tokens = [] cut_left = True # If the current one is visible, stop here. else: cut_right = True break tokens.append((self.token.Completion.Current if i == index else self.token.Completion, c.display)) tokens.append((self.token, ' ')) # Extend/strip until the content width. tokens.append((self.token, ' ' * (content_width - token_list_len(tokens)))) tokens = tokens[:content_width] # Return tokens all_tokens = [ (self.token, ' '), (self.token.Arrow, '<' if cut_left else ' '), (self.token, ' '), ] + tokens + [ (self.token, ' '), (self.token.Arrow, '>' if cut_right else ' '), (self.token, ' '), ] else: all_tokens = [] def get_line(i): return all_tokens return UIContent(get_line=get_line, line_count=1) class CompletionsToolbar(ConditionalContainer): def __init__(self, extra_filter=Always()): super(CompletionsToolbar, self).__init__( content=Window( CompletionsToolbarControl(), height=LayoutDimension.exact(1)), filter=HasCompletions() & ~IsDone() & extra_filter) class ValidationToolbarControl(TokenListControl): def __init__(self, show_position=False): token = Token.Toolbar.Validation def get_tokens(cli): buffer = cli.current_buffer if buffer.validation_error: row, column = buffer.document.translate_index_to_position( buffer.validation_error.cursor_position) if show_position: text = '%s (line=%s column=%s)' % ( buffer.validation_error.message, row + 1, column + 1) else: text = buffer.validation_error.message return [(token, text)] else: return [] super(ValidationToolbarControl, self).__init__(get_tokens) class ValidationToolbar(ConditionalContainer): def __init__(self, show_position=False): super(ValidationToolbar, self).__init__( content=Window( ValidationToolbarControl(show_position=show_position), height=LayoutDimension.exact(1)), filter=HasValidationError() & ~IsDone()) prompt_toolkit-1.0.15/prompt_toolkit/layout/__init__.py0000664000175000017500000000356513136130420025032 0ustar jonathanjonathan00000000000000""" Command line layout definitions ------------------------------- The layout of a command line interface is defined by a Container instance. There are two main groups of classes here. Containers and controls: - A container can contain other containers or controls, it can have multiple children and it decides about the dimensions. - A control is responsible for rendering the actual content to a screen. A control can propose some dimensions, but it's the container who decides about the dimensions -- or when the control consumes more space -- which part of the control will be visible. Container classes:: - Container (Abstract base class) |- HSplit (Horizontal split) |- VSplit (Vertical split) |- FloatContainer (Container which can also contain menus and other floats) `- Window (Container which contains one actual control Control classes:: - UIControl (Abstract base class) |- TokenListControl (Renders a simple list of tokens) |- FillControl (Fills control with one token/character.) `- BufferControl (Renders an input buffer.) Usually, you end up wrapping every control inside a `Window` object, because that's the only way to render it in a layout. There are some prepared toolbars which are ready to use:: - SystemToolbar (Shows the 'system' input buffer, for entering system commands.) - ArgToolbar (Shows the input 'arg', for repetition of input commands.) - SearchToolbar (Shows the 'search' input buffer, for incremental search.) - CompletionsToolbar (Shows the completions of the current buffer.) - ValidationToolbar (Shows validation errors of the current buffer.) And one prepared menu: - CompletionsMenu """ from __future__ import unicode_literals from .containers import Float, FloatContainer, HSplit, VSplit, Window, ConditionalContainer from .controls import TokenListControl, FillControl, BufferControl prompt_toolkit-1.0.15/prompt_toolkit/layout/prompt.py0000664000175000017500000000624413136130420024611 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from six import text_type from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER from prompt_toolkit.token import Token from .utils import token_list_len from .processors import Processor, Transformation __all__ = ( 'DefaultPrompt', ) class DefaultPrompt(Processor): """ Default prompt. This one shows the 'arg' and reverse search like Bash/readline normally do. There are two ways to instantiate a ``DefaultPrompt``. For a prompt with a static message, do for instance:: prompt = DefaultPrompt.from_message('prompt> ') For a dynamic prompt, generated from a token list function:: def get_tokens(cli): return [(Token.A, 'text'), (Token.B, 'text2')] prompt = DefaultPrompt(get_tokens) """ def __init__(self, get_tokens): assert callable(get_tokens) self.get_tokens = get_tokens @classmethod def from_message(cls, message='> '): """ Create a default prompt with a static message text. """ assert isinstance(message, text_type) def get_message_tokens(cli): return [(Token.Prompt, message)] return cls(get_message_tokens) def apply_transformation(self, cli, document, lineno, source_to_display, tokens): # Get text before cursor. if cli.is_searching: before = _get_isearch_tokens(cli) elif cli.input_processor.arg is not None: before = _get_arg_tokens(cli) else: before = self.get_tokens(cli) # Insert before buffer text. shift_position = token_list_len(before) # Only show the prompt before the first line. For the following lines, # only indent using spaces. if lineno != 0: before = [(Token.Prompt, ' ' * shift_position)] return Transformation( tokens=before + tokens, source_to_display=lambda i: i + shift_position, display_to_source=lambda i: i - shift_position) def has_focus(self, cli): # Obtain focus when the CLI is searching. # Usually, when using this `DefaultPrompt`, we don't have a # `BufferControl` instance that displays the content of the search # buffer. Instead the search text is displayed before the current text. # So, we can still show the cursor here, while it's actually not this # buffer that's focussed. return cli.is_searching def _get_isearch_tokens(cli): def before(): if cli.search_state.direction == IncrementalSearchDirection.BACKWARD: text = 'reverse-i-search' else: text = 'i-search' return [(Token.Prompt.Search, '(%s)`' % text)] def text(): return [(Token.Prompt.Search.Text, cli.buffers[SEARCH_BUFFER].text)] def after(): return [(Token.Prompt.Search, '`: ')] return before() + text() + after() def _get_arg_tokens(cli): """ Tokens for the arg-prompt. """ arg = cli.input_processor.arg return [ (Token.Prompt.Arg, '(arg: '), (Token.Prompt.Arg.Text, str(arg)), (Token.Prompt.Arg, ') '), ] prompt_toolkit-1.0.15/prompt_toolkit/layout/menus.py0000664000175000017500000004574213136130420024425 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from six.moves import zip_longest, range from prompt_toolkit.filters import HasCompletions, IsDone, Condition, to_cli_filter from prompt_toolkit.mouse_events import MouseEventType from prompt_toolkit.token import Token from prompt_toolkit.utils import get_cwidth from .containers import Window, HSplit, ConditionalContainer, ScrollOffsets from .controls import UIControl, UIContent from .dimension import LayoutDimension from .margins import ScrollbarMargin from .screen import Point, Char import math __all__ = ( 'CompletionsMenu', 'MultiColumnCompletionsMenu', ) class CompletionsMenuControl(UIControl): """ Helper for drawing the complete menu to the screen. :param scroll_offset: Number (integer) representing the preferred amount of completions to be displayed before and after the current one. When this is a very high number, the current completion will be shown in the middle most of the time. """ # Preferred minimum size of the menu control. # The CompletionsMenu class defines a width of 8, and there is a scrollbar # of 1.) MIN_WIDTH = 7 def __init__(self): self.token = Token.Menu.Completions def has_focus(self, cli): return False def preferred_width(self, cli, max_available_width): complete_state = cli.current_buffer.complete_state if complete_state: menu_width = self._get_menu_width(500, complete_state) menu_meta_width = self._get_menu_meta_width(500, complete_state) return menu_width + menu_meta_width else: return 0 def preferred_height(self, cli, width, max_available_height, wrap_lines): complete_state = cli.current_buffer.complete_state if complete_state: return len(complete_state.current_completions) else: return 0 def create_content(self, cli, width, height): """ Create a UIContent object for this control. """ complete_state = cli.current_buffer.complete_state if complete_state: completions = complete_state.current_completions index = complete_state.complete_index # Can be None! # Calculate width of completions menu. menu_width = self._get_menu_width(width, complete_state) menu_meta_width = self._get_menu_meta_width(width - menu_width, complete_state) show_meta = self._show_meta(complete_state) def get_line(i): c = completions[i] is_current_completion = (i == index) result = self._get_menu_item_tokens(c, is_current_completion, menu_width) if show_meta: result += self._get_menu_item_meta_tokens(c, is_current_completion, menu_meta_width) return result return UIContent(get_line=get_line, cursor_position=Point(x=0, y=index or 0), line_count=len(completions), default_char=Char(' ', self.token)) return UIContent() def _show_meta(self, complete_state): """ Return ``True`` if we need to show a column with meta information. """ return any(c.display_meta for c in complete_state.current_completions) def _get_menu_width(self, max_width, complete_state): """ Return the width of the main column. """ return min(max_width, max(self.MIN_WIDTH, max(get_cwidth(c.display) for c in complete_state.current_completions) + 2)) def _get_menu_meta_width(self, max_width, complete_state): """ Return the width of the meta column. """ if self._show_meta(complete_state): return min(max_width, max(get_cwidth(c.display_meta) for c in complete_state.current_completions) + 2) else: return 0 def _get_menu_item_tokens(self, completion, is_current_completion, width): if is_current_completion: token = self.token.Completion.Current else: token = self.token.Completion text, tw = _trim_text(completion.display, width - 2) padding = ' ' * (width - 2 - tw) return [(token, ' %s%s ' % (text, padding))] def _get_menu_item_meta_tokens(self, completion, is_current_completion, width): if is_current_completion: token = self.token.Meta.Current else: token = self.token.Meta text, tw = _trim_text(completion.display_meta, width - 2) padding = ' ' * (width - 2 - tw) return [(token, ' %s%s ' % (text, padding))] def mouse_handler(self, cli, mouse_event): """ Handle mouse events: clicking and scrolling. """ b = cli.current_buffer if mouse_event.event_type == MouseEventType.MOUSE_UP: # Select completion. b.go_to_completion(mouse_event.position.y) b.complete_state = None elif mouse_event.event_type == MouseEventType.SCROLL_DOWN: # Scroll up. b.complete_next(count=3, disable_wrap_around=True) elif mouse_event.event_type == MouseEventType.SCROLL_UP: # Scroll down. b.complete_previous(count=3, disable_wrap_around=True) def _trim_text(text, max_width): """ Trim the text to `max_width`, append dots when the text is too long. Returns (text, width) tuple. """ width = get_cwidth(text) # When the text is too wide, trim it. if width > max_width: # When there are no double width characters, just use slice operation. if len(text) == width: trimmed_text = (text[:max(1, max_width-3)] + '...')[:max_width] return trimmed_text, len(trimmed_text) # Otherwise, loop until we have the desired width. (Rather # inefficient, but ok for now.) else: trimmed_text = '' for c in text: if get_cwidth(trimmed_text + c) <= max_width - 3: trimmed_text += c trimmed_text += '...' return (trimmed_text, get_cwidth(trimmed_text)) else: return text, width class CompletionsMenu(ConditionalContainer): def __init__(self, max_height=None, scroll_offset=0, extra_filter=True, display_arrows=False): extra_filter = to_cli_filter(extra_filter) display_arrows = to_cli_filter(display_arrows) super(CompletionsMenu, self).__init__( content=Window( content=CompletionsMenuControl(), width=LayoutDimension(min=8), height=LayoutDimension(min=1, max=max_height), scroll_offsets=ScrollOffsets(top=scroll_offset, bottom=scroll_offset), right_margins=[ScrollbarMargin(display_arrows=display_arrows)], dont_extend_width=True, ), # Show when there are completions but not at the point we are # returning the input. filter=HasCompletions() & ~IsDone() & extra_filter) class MultiColumnCompletionMenuControl(UIControl): """ Completion menu that displays all the completions in several columns. When there are more completions than space for them to be displayed, an arrow is shown on the left or right side. `min_rows` indicates how many rows will be available in any possible case. When this is langer than one, in will try to use less columns and more rows until this value is reached. Be careful passing in a too big value, if less than the given amount of rows are available, more columns would have been required, but `preferred_width` doesn't know about that and reports a too small value. This results in less completions displayed and additional scrolling. (It's a limitation of how the layout engine currently works: first the widths are calculated, then the heights.) :param suggested_max_column_width: The suggested max width of a column. The column can still be bigger than this, but if there is place for two columns of this width, we will display two columns. This to avoid that if there is one very wide completion, that it doesn't significantly reduce the amount of columns. """ _required_margin = 3 # One extra padding on the right + space for arrows. def __init__(self, min_rows=3, suggested_max_column_width=30): assert isinstance(min_rows, int) and min_rows >= 1 self.min_rows = min_rows self.suggested_max_column_width = suggested_max_column_width self.token = Token.Menu.Completions self.scroll = 0 # Info of last rendering. self._rendered_rows = 0 self._rendered_columns = 0 self._total_columns = 0 self._render_pos_to_completion = {} self._render_left_arrow = False self._render_right_arrow = False self._render_width = 0 def reset(self): self.scroll = 0 def has_focus(self, cli): return False def preferred_width(self, cli, max_available_width): """ Preferred width: prefer to use at least min_rows, but otherwise as much as possible horizontally. """ complete_state = cli.current_buffer.complete_state column_width = self._get_column_width(complete_state) result = int(column_width * math.ceil(len(complete_state.current_completions) / float(self.min_rows))) # When the desired width is still more than the maximum available, # reduce by removing columns until we are less than the available # width. while result > column_width and result > max_available_width - self._required_margin: result -= column_width return result + self._required_margin def preferred_height(self, cli, width, max_available_height, wrap_lines): """ Preferred height: as much as needed in order to display all the completions. """ complete_state = cli.current_buffer.complete_state column_width = self._get_column_width(complete_state) column_count = max(1, (width - self._required_margin) // column_width) return int(math.ceil(len(complete_state.current_completions) / float(column_count))) def create_content(self, cli, width, height): """ Create a UIContent object for this menu. """ complete_state = cli.current_buffer.complete_state column_width = self._get_column_width(complete_state) self._render_pos_to_completion = {} def grouper(n, iterable, fillvalue=None): " grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx " args = [iter(iterable)] * n return zip_longest(fillvalue=fillvalue, *args) def is_current_completion(completion): " Returns True when this completion is the currently selected one. " return complete_state.complete_index is not None and c == complete_state.current_completion # Space required outside of the regular columns, for displaying the # left and right arrow. HORIZONTAL_MARGIN_REQUIRED = 3 if complete_state: # There should be at least one column, but it cannot be wider than # the available width. column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width) # However, when the columns tend to be very wide, because there are # some very wide entries, shrink it anyway. if column_width > self.suggested_max_column_width: # `column_width` can still be bigger that `suggested_max_column_width`, # but if there is place for two columns, we divide by two. column_width //= (column_width // self.suggested_max_column_width) visible_columns = max(1, (width - self._required_margin) // column_width) columns_ = list(grouper(height, complete_state.current_completions)) rows_ = list(zip(*columns_)) # Make sure the current completion is always visible: update scroll offset. selected_column = (complete_state.complete_index or 0) // height self.scroll = min(selected_column, max(self.scroll, selected_column - visible_columns + 1)) render_left_arrow = self.scroll > 0 render_right_arrow = self.scroll < len(rows_[0]) - visible_columns # Write completions to screen. tokens_for_line = [] for row_index, row in enumerate(rows_): tokens = [] middle_row = row_index == len(rows_) // 2 # Draw left arrow if we have hidden completions on the left. if render_left_arrow: tokens += [(Token.Scrollbar, '<' if middle_row else ' ')] # Draw row content. for column_index, c in enumerate(row[self.scroll:][:visible_columns]): if c is not None: tokens += self._get_menu_item_tokens(c, is_current_completion(c), column_width) # Remember render position for mouse click handler. for x in range(column_width): self._render_pos_to_completion[(column_index * column_width + x, row_index)] = c else: tokens += [(self.token.Completion, ' ' * column_width)] # Draw trailing padding. (_get_menu_item_tokens only returns padding on the left.) tokens += [(self.token.Completion, ' ')] # Draw right arrow if we have hidden completions on the right. if render_right_arrow: tokens += [(Token.Scrollbar, '>' if middle_row else ' ')] # Newline. tokens_for_line.append(tokens) else: tokens = [] self._rendered_rows = height self._rendered_columns = visible_columns self._total_columns = len(columns_) self._render_left_arrow = render_left_arrow self._render_right_arrow = render_right_arrow self._render_width = column_width * visible_columns + render_left_arrow + render_right_arrow + 1 def get_line(i): return tokens_for_line[i] return UIContent(get_line=get_line, line_count=len(rows_)) def _get_column_width(self, complete_state): """ Return the width of each column. """ return max(get_cwidth(c.display) for c in complete_state.current_completions) + 1 def _get_menu_item_tokens(self, completion, is_current_completion, width): if is_current_completion: token = self.token.Completion.Current else: token = self.token.Completion text, tw = _trim_text(completion.display, width) padding = ' ' * (width - tw - 1) return [(token, ' %s%s' % (text, padding))] def mouse_handler(self, cli, mouse_event): """ Handle scoll and click events. """ b = cli.current_buffer def scroll_left(): b.complete_previous(count=self._rendered_rows, disable_wrap_around=True) self.scroll = max(0, self.scroll - 1) def scroll_right(): b.complete_next(count=self._rendered_rows, disable_wrap_around=True) self.scroll = min(self._total_columns - self._rendered_columns, self.scroll + 1) if mouse_event.event_type == MouseEventType.SCROLL_DOWN: scroll_right() elif mouse_event.event_type == MouseEventType.SCROLL_UP: scroll_left() elif mouse_event.event_type == MouseEventType.MOUSE_UP: x = mouse_event.position.x y = mouse_event.position.y # Mouse click on left arrow. if x == 0: if self._render_left_arrow: scroll_left() # Mouse click on right arrow. elif x == self._render_width - 1: if self._render_right_arrow: scroll_right() # Mouse click on completion. else: completion = self._render_pos_to_completion.get((x, y)) if completion: b.apply_completion(completion) class MultiColumnCompletionsMenu(HSplit): """ Container that displays the completions in several columns. When `show_meta` (a :class:`~prompt_toolkit.filters.CLIFilter`) evaluates to True, it shows the meta information at the bottom. """ def __init__(self, min_rows=3, suggested_max_column_width=30, show_meta=True, extra_filter=True): show_meta = to_cli_filter(show_meta) extra_filter = to_cli_filter(extra_filter) # Display filter: show when there are completions but not at the point # we are returning the input. full_filter = HasCompletions() & ~IsDone() & extra_filter any_completion_has_meta = Condition(lambda cli: any(c.display_meta for c in cli.current_buffer.complete_state.current_completions)) # Create child windows. completions_window = ConditionalContainer( content=Window( content=MultiColumnCompletionMenuControl( min_rows=min_rows, suggested_max_column_width=suggested_max_column_width), width=LayoutDimension(min=8), height=LayoutDimension(min=1)), filter=full_filter) meta_window = ConditionalContainer( content=Window(content=_SelectedCompletionMetaControl()), filter=show_meta & full_filter & any_completion_has_meta) # Initialise split. super(MultiColumnCompletionsMenu, self).__init__([ completions_window, meta_window ]) class _SelectedCompletionMetaControl(UIControl): """ Control that shows the meta information of the selected token. """ def preferred_width(self, cli, max_available_width): """ Report the width of the longest meta text as the preferred width of this control. It could be that we use less width, but this way, we're sure that the layout doesn't change when we select another completion (E.g. that completions are suddenly shown in more or fewer columns.) """ if cli.current_buffer.complete_state: state = cli.current_buffer.complete_state return 2 + max(get_cwidth(c.display_meta) for c in state.current_completions) else: return 0 def preferred_height(self, cli, width, max_available_height, wrap_lines): return 1 def create_content(self, cli, width, height): tokens = self._get_tokens(cli) def get_line(i): return tokens return UIContent(get_line=get_line, line_count=1 if tokens else 0) def _get_tokens(self, cli): token = Token.Menu.Completions.MultiColumnMeta state = cli.current_buffer.complete_state if state and state.current_completion and state.current_completion.display_meta: return [(token, ' %s ' % state.current_completion.display_meta)] return [] prompt_toolkit-1.0.15/prompt_toolkit/layout/margins.py0000664000175000017500000002122113136130420024720 0ustar jonathanjonathan00000000000000""" Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from six.moves import range from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.token import Token from prompt_toolkit.utils import get_cwidth from .utils import token_list_to_text __all__ = ( 'Margin', 'NumberredMargin', 'ScrollbarMargin', 'ConditionalMargin', 'PromptMargin', ) class Margin(with_metaclass(ABCMeta, object)): """ Base interface for a margin. """ @abstractmethod def get_width(self, cli, get_ui_content): """ Return the width that this margin is going to consume. :param cli: :class:`.CommandLineInterface` instance. :param get_ui_content: Callable that asks the user control to create a :class:`.UIContent` instance. This can be used for instance to obtain the number of lines. """ return 0 @abstractmethod def create_margin(self, cli, window_render_info, width, height): """ Creates a margin. This should return a list of (Token, text) tuples. :param cli: :class:`.CommandLineInterface` instance. :param window_render_info: :class:`~prompt_toolkit.layout.containers.WindowRenderInfo` instance, generated after rendering and copying the visible part of the :class:`~prompt_toolkit.layout.controls.UIControl` into the :class:`~prompt_toolkit.layout.containers.Window`. :param width: The width that's available for this margin. (As reported by :meth:`.get_width`.) :param height: The height that's available for this margin. (The height of the :class:`~prompt_toolkit.layout.containers.Window`.) """ return [] class NumberredMargin(Margin): """ Margin that displays the line numbers. :param relative: Number relative to the cursor position. Similar to the Vi 'relativenumber' option. :param display_tildes: Display tildes after the end of the document, just like Vi does. """ def __init__(self, relative=False, display_tildes=False): self.relative = to_cli_filter(relative) self.display_tildes = to_cli_filter(display_tildes) def get_width(self, cli, get_ui_content): line_count = get_ui_content().line_count return max(3, len('%s' % line_count) + 1) def create_margin(self, cli, window_render_info, width, height): relative = self.relative(cli) token = Token.LineNumber token_current = Token.LineNumber.Current # Get current line number. current_lineno = window_render_info.ui_content.cursor_position.y # Construct margin. result = [] last_lineno = None for y, lineno in enumerate(window_render_info.displayed_lines): # Only display line number if this line is not a continuation of the previous line. if lineno != last_lineno: if lineno is None: pass elif lineno == current_lineno: # Current line. if relative: # Left align current number in relative mode. result.append((token_current, '%i' % (lineno + 1))) else: result.append((token_current, ('%i ' % (lineno + 1)).rjust(width))) else: # Other lines. if relative: lineno = abs(lineno - current_lineno) - 1 result.append((token, ('%i ' % (lineno + 1)).rjust(width))) last_lineno = lineno result.append((Token, '\n')) # Fill with tildes. if self.display_tildes(cli): while y < window_render_info.window_height: result.append((Token.Tilde, '~\n')) y += 1 return result class ConditionalMargin(Margin): """ Wrapper around other :class:`.Margin` classes to show/hide them. """ def __init__(self, margin, filter): assert isinstance(margin, Margin) self.margin = margin self.filter = to_cli_filter(filter) def get_width(self, cli, ui_content): if self.filter(cli): return self.margin.get_width(cli, ui_content) else: return 0 def create_margin(self, cli, window_render_info, width, height): if width and self.filter(cli): return self.margin.create_margin(cli, window_render_info, width, height) else: return [] class ScrollbarMargin(Margin): """ Margin displaying a scrollbar. :param display_arrows: Display scroll up/down arrows. """ def __init__(self, display_arrows=False): self.display_arrows = to_cli_filter(display_arrows) def get_width(self, cli, ui_content): return 1 def create_margin(self, cli, window_render_info, width, height): total_height = window_render_info.content_height display_arrows = self.display_arrows(cli) window_height = window_render_info.window_height if display_arrows: window_height -= 2 try: items_per_row = float(total_height) / min(total_height, window_height) except ZeroDivisionError: return [] else: def is_scroll_button(row): " True if we should display a button on this row. " current_row_middle = int((row + .5) * items_per_row) return current_row_middle in window_render_info.displayed_lines # Up arrow. result = [] if display_arrows: result.extend([ (Token.Scrollbar.Arrow, '^'), (Token.Scrollbar, '\n') ]) # Scrollbar body. for i in range(window_height): if is_scroll_button(i): result.append((Token.Scrollbar.Button, ' ')) else: result.append((Token.Scrollbar, ' ')) result.append((Token, '\n')) # Down arrow if display_arrows: result.append((Token.Scrollbar.Arrow, 'v')) return result class PromptMargin(Margin): """ Create margin that displays a prompt. This can display one prompt at the first line, and a continuation prompt (e.g, just dots) on all the following lines. :param get_prompt_tokens: Callable that takes a CommandLineInterface as input and returns a list of (Token, type) tuples to be shown as the prompt at the first line. :param get_continuation_tokens: Callable that takes a CommandLineInterface and a width as input and returns a list of (Token, type) tuples for the next lines of the input. :param show_numbers: (bool or :class:`~prompt_toolkit.filters.CLIFilter`) Display line numbers instead of the continuation prompt. """ def __init__(self, get_prompt_tokens, get_continuation_tokens=None, show_numbers=False): assert callable(get_prompt_tokens) assert get_continuation_tokens is None or callable(get_continuation_tokens) show_numbers = to_cli_filter(show_numbers) self.get_prompt_tokens = get_prompt_tokens self.get_continuation_tokens = get_continuation_tokens self.show_numbers = show_numbers def get_width(self, cli, ui_content): " Width to report to the `Window`. " # Take the width from the first line. text = token_list_to_text(self.get_prompt_tokens(cli)) return get_cwidth(text) def create_margin(self, cli, window_render_info, width, height): # First line. tokens = self.get_prompt_tokens(cli)[:] # Next lines. (Show line numbering when numbering is enabled.) if self.get_continuation_tokens: # Note: we turn this into a list, to make sure that we fail early # in case `get_continuation_tokens` returns something else, # like `None`. tokens2 = list(self.get_continuation_tokens(cli, width)) else: tokens2 = [] show_numbers = self.show_numbers(cli) last_y = None for y in window_render_info.displayed_lines[1:]: tokens.append((Token, '\n')) if show_numbers: if y != last_y: tokens.append((Token.LineNumber, ('%i ' % (y + 1)).rjust(width))) else: tokens.extend(tokens2) last_y = y return tokens prompt_toolkit-1.0.15/prompt_toolkit/layout/utils.py0000664000175000017500000001252713136130420024431 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.utils import get_cwidth from prompt_toolkit.token import Token __all__ = ( 'token_list_len', 'token_list_width', 'token_list_to_text', 'explode_tokens', 'split_lines', 'find_window_for_buffer_name', ) def token_list_len(tokenlist): """ Return the amount of characters in this token list. :param tokenlist: List of (token, text) or (token, text, mouse_handler) tuples. """ ZeroWidthEscape = Token.ZeroWidthEscape return sum(len(item[1]) for item in tokenlist if item[0] != ZeroWidthEscape) def token_list_width(tokenlist): """ Return the character width of this token list. (Take double width characters into account.) :param tokenlist: List of (token, text) or (token, text, mouse_handler) tuples. """ ZeroWidthEscape = Token.ZeroWidthEscape return sum(get_cwidth(c) for item in tokenlist for c in item[1] if item[0] != ZeroWidthEscape) def token_list_to_text(tokenlist): """ Concatenate all the text parts again. """ ZeroWidthEscape = Token.ZeroWidthEscape return ''.join(item[1] for item in tokenlist if item[0] != ZeroWidthEscape) def iter_token_lines(tokenlist): """ Iterator that yields tokenlists for each line. """ line = [] for token, c in explode_tokens(tokenlist): line.append((token, c)) if c == '\n': yield line line = [] yield line def split_lines(tokenlist): """ Take a single list of (Token, text) tuples and yield one such list for each line. Just like str.split, this will yield at least one item. :param tokenlist: List of (token, text) or (token, text, mouse_handler) tuples. """ line = [] for item in tokenlist: # For (token, text) tuples. if len(item) == 2: token, string = item parts = string.split('\n') for part in parts[:-1]: if part: line.append((token, part)) yield line line = [] line.append((token, parts[-1])) # Note that parts[-1] can be empty, and that's fine. It happens # in the case of [(Token.SetCursorPosition, '')]. # For (token, text, mouse_handler) tuples. # I know, partly copy/paste, but understandable and more efficient # than many tests. else: token, string, mouse_handler = item parts = string.split('\n') for part in parts[:-1]: if part: line.append((token, part, mouse_handler)) yield line line = [] line.append((token, parts[-1], mouse_handler)) # Always yield the last line, even when this is an empty line. This ensures # that when `tokenlist` ends with a newline character, an additional empty # line is yielded. (Otherwise, there's no way to differentiate between the # cases where `tokenlist` does and doesn't end with a newline.) yield line class _ExplodedList(list): """ Wrapper around a list, that marks it as 'exploded'. As soon as items are added or the list is extended, the new items are automatically exploded as well. """ def __init__(self, *a, **kw): super(_ExplodedList, self).__init__(*a, **kw) self.exploded = True def append(self, item): self.extend([item]) def extend(self, lst): super(_ExplodedList, self).extend(explode_tokens(lst)) def insert(self, index, item): raise NotImplementedError # TODO # TODO: When creating a copy() or [:], return also an _ExplodedList. def __setitem__(self, index, value): """ Ensure that when `(Token, 'long string')` is set, the string will be exploded. """ if not isinstance(index, slice): index = slice(index, index + 1) value = explode_tokens([value]) super(_ExplodedList, self).__setitem__(index, value) def explode_tokens(tokenlist): """ Turn a list of (token, text) tuples into another list where each string is exactly one character. It should be fine to call this function several times. Calling this on a list that is already exploded, is a null operation. :param tokenlist: List of (token, text) tuples. """ # When the tokenlist is already exploded, don't explode again. if getattr(tokenlist, 'exploded', False): return tokenlist result = [] for token, string in tokenlist: for c in string: result.append((token, c)) return _ExplodedList(result) def find_window_for_buffer_name(cli, buffer_name): """ Look for a :class:`~prompt_toolkit.layout.containers.Window` in the Layout that contains the :class:`~prompt_toolkit.layout.controls.BufferControl` for the given buffer and return it. If no such Window is found, return None. """ from prompt_toolkit.interface import CommandLineInterface assert isinstance(cli, CommandLineInterface) from .containers import Window from .controls import BufferControl for l in cli.layout.walk(cli): if isinstance(l, Window) and isinstance(l.content, BufferControl): if l.content.buffer_name == buffer_name: return l prompt_toolkit-1.0.15/prompt_toolkit/search_state.py0000664000175000017500000000211313136130420024407 0ustar jonathanjonathan00000000000000from .enums import IncrementalSearchDirection from .filters import to_simple_filter __all__ = ( 'SearchState', ) class SearchState(object): """ A search 'query'. """ __slots__ = ('text', 'direction', 'ignore_case') def __init__(self, text='', direction=IncrementalSearchDirection.FORWARD, ignore_case=False): ignore_case = to_simple_filter(ignore_case) self.text = text self.direction = direction self.ignore_case = ignore_case def __repr__(self): return '%s(%r, direction=%r, ignore_case=%r)' % ( self.__class__.__name__, self.text, self.direction, self.ignore_case) def __invert__(self): """ Create a new SearchState where backwards becomes forwards and the other way around. """ if self.direction == IncrementalSearchDirection.BACKWARD: direction = IncrementalSearchDirection.FORWARD else: direction = IncrementalSearchDirection.BACKWARD return SearchState(text=self.text, direction=direction, ignore_case=self.ignore_case) prompt_toolkit-1.0.15/prompt_toolkit/token.py0000664000175000017500000000261413136130420023070 0ustar jonathanjonathan00000000000000""" The Token class, interchangeable with ``pygments.token``. A `Token` has some semantics for a piece of text that is given a style through a :class:`~prompt_toolkit.styles.Style` class. A pygments lexer for instance, returns a list of (Token, text) tuples. Each fragment of text has a token assigned, which when combined with a style sheet, will determine the fine style. """ # If we don't need any lexers or style classes from Pygments, we don't want # Pygments to be installed for only the following 10 lines of code. So, there # is some duplication, but this should stay compatible with Pygments. __all__ = ( 'Token', 'ZeroWidthEscape', ) class _TokenType(tuple): def __getattr__(self, val): if not val or not val[0].isupper(): return tuple.__getattribute__(self, val) new = _TokenType(self + (val,)) setattr(self, val, new) return new def __repr__(self): return 'Token' + (self and '.' or '') + '.'.join(self) # Prefer the Token class from Pygments. If Pygments is not installed, use our # minimalistic Token class. try: from pygments.token import Token except ImportError: Token = _TokenType() # Built-in tokens: #: `ZeroWidthEscape` can be used for raw VT escape sequences that don't #: cause the cursor position to move. (E.g. FinalTerm's escape sequences #: for shell integration.) ZeroWidthEscape = Token.ZeroWidthEscape prompt_toolkit-1.0.15/prompt_toolkit/buffer_mapping.py0000664000175000017500000000551213136130420024734 0ustar jonathanjonathan00000000000000""" The BufferMapping contains all the buffers for a command line interface, and it keeps track of which buffer gets the focus. """ from __future__ import unicode_literals from .enums import DEFAULT_BUFFER, SEARCH_BUFFER, SYSTEM_BUFFER, DUMMY_BUFFER from .buffer import Buffer, AcceptAction from .history import InMemoryHistory import six __all__ = ( 'BufferMapping', ) class BufferMapping(dict): """ Dictionary that maps the name of the buffers to the :class:`~prompt_toolkit.buffer.Buffer` instances. This mapping also keeps track of which buffer currently has the focus. (Some methods receive a 'cli' parameter. This is useful for applications where this `BufferMapping` is shared between several applications.) """ def __init__(self, buffers=None, initial=DEFAULT_BUFFER): assert buffers is None or isinstance(buffers, dict) # Start with an empty dict. super(BufferMapping, self).__init__() # Add default buffers. self.update({ # For the 'search' and 'system' buffers, 'returnable' is False, in # order to block normal Enter/ControlC behaviour. DEFAULT_BUFFER: Buffer(accept_action=AcceptAction.RETURN_DOCUMENT), SEARCH_BUFFER: Buffer(history=InMemoryHistory(), accept_action=AcceptAction.IGNORE), SYSTEM_BUFFER: Buffer(history=InMemoryHistory(), accept_action=AcceptAction.IGNORE), DUMMY_BUFFER: Buffer(read_only=True), }) # Add received buffers. if buffers is not None: self.update(buffers) # Focus stack. self.focus_stack = [initial or DEFAULT_BUFFER] def current(self, cli): """ The active :class:`.Buffer`. """ return self[self.focus_stack[-1]] def current_name(self, cli): """ The name of the active :class:`.Buffer`. """ return self.focus_stack[-1] def previous(self, cli): """ Return the previously focussed :class:`.Buffer` or `None`. """ if len(self.focus_stack) > 1: try: return self[self.focus_stack[-2]] except KeyError: pass def focus(self, cli, buffer_name): """ Focus the buffer with the given name. """ assert isinstance(buffer_name, six.text_type) self.focus_stack = [buffer_name] def push_focus(self, cli, buffer_name): """ Push buffer on the focus stack. """ assert isinstance(buffer_name, six.text_type) self.focus_stack.append(buffer_name) def pop_focus(self, cli): """ Pop buffer from the focus stack. """ if len(self.focus_stack) > 1: self.focus_stack.pop() else: raise IndexError('Cannot pop last item from the focus stack.') prompt_toolkit-1.0.15/prompt_toolkit/__init__.py0000664000175000017500000000117013136335153023515 0ustar jonathanjonathan00000000000000""" prompt_toolkit ============== Author: Jonathan Slenders Description: prompt_toolkit is a Library for building powerful interactive command lines in Python. It can be a replacement for GNU readline, but it can be much more than that. See the examples directory to learn about the usage. Probably, to get started, you meight also want to have a look at `prompt_toolkit.shortcuts.prompt`. """ from .interface import CommandLineInterface from .application import AbortAction, Application from .shortcuts import prompt, prompt_async # Don't forget to update in `docs/conf.py`! __version__ = '1.0.15' prompt_toolkit-1.0.15/prompt_toolkit/document.py0000664000175000017500000010621013136130420023563 0ustar jonathanjonathan00000000000000""" The `Document` that implements all the text operations/querying. """ from __future__ import unicode_literals import bisect import re import six import string import weakref from six.moves import range, map from .selection import SelectionType, SelectionState, PasteMode from .clipboard import ClipboardData __all__ = ('Document',) # Regex for finding "words" in documents. (We consider a group of alnum # characters a word, but also a group of special characters a word, as long as # it doesn't contain a space.) # (This is a 'word' in Vi.) _FIND_WORD_RE = re.compile(r'([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)') _FIND_CURRENT_WORD_RE = re.compile(r'^([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)') _FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r'^(([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)\s*)') # Regex for finding "WORDS" in documents. # (This is a 'WORD in Vi.) _FIND_BIG_WORD_RE = re.compile(r'([^\s]+)') _FIND_CURRENT_BIG_WORD_RE = re.compile(r'^([^\s]+)') _FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r'^([^\s]+\s*)') # Share the Document._cache between all Document instances. # (Document instances are considered immutable. That means that if another # `Document` is constructed with the same text, it should have the same # `_DocumentCache`.) _text_to_document_cache = weakref.WeakValueDictionary() # Maps document.text to DocumentCache instance. class _ImmutableLineList(list): """ Some protection for our 'lines' list, which is assumed to be immutable in the cache. (Useful for detecting obvious bugs.) """ def _error(self, *a, **kw): raise NotImplementedError('Attempt to modifiy an immutable list.') __setitem__ = _error append = _error clear = _error extend = _error insert = _error pop = _error remove = _error reverse = _error sort = _error class _DocumentCache(object): def __init__(self): #: List of lines for the Document text. self.lines = None #: List of index positions, pointing to the start of all the lines. self.line_indexes = None 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.buffer.Buffer` object, and accessed as the `document` property of that class. :param text: string :param cursor_position: int :param selection: :class:`.SelectionState` """ __slots__ = ('_text', '_cursor_position', '_selection', '_cache') def __init__(self, text='', cursor_position=None, selection=None): assert isinstance(text, six.text_type), 'Got %r' % text assert selection is None or isinstance(selection, SelectionState) # Check cursor position. It can also be right after the end. (Where we # insert text.) assert cursor_position is None or cursor_position <= len(text), AssertionError( 'cursor_position=%r, len_text=%r' % (cursor_position, len(text))) # By default, if no cursor position was given, make sure to put the # cursor position is at the end of the document. This is what makes # sense in most places. if cursor_position is None: cursor_position = len(text) # Keep these attributes private. A `Document` really has to be # considered to be immutable, because otherwise the caching will break # things. Because of that, we wrap these into read-only properties. self._text = text self._cursor_position = cursor_position self._selection = selection # Cache for lines/indexes. (Shared with other Document instances that # contain the same text. try: self._cache = _text_to_document_cache[self.text] except KeyError: self._cache = _DocumentCache() _text_to_document_cache[self.text] = self._cache # XX: For some reason, above, we can't use 'WeakValueDictionary.setdefault'. # This fails in Pypy3. `self._cache` becomes None, because that's what # 'setdefault' returns. # self._cache = _text_to_document_cache.setdefault(self.text, _DocumentCache()) # assert self._cache def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.text, self.cursor_position) @property def text(self): " The document text. " return self._text @property def cursor_position(self): " The document cursor position. " return self._cursor_position @property def selection(self): " :class:`.SelectionState` object. " return self._selection @property def current_char(self): """ Return character under cursor or an empty string. """ return self._get_char_relative_to_cursor(0) or '' @property def char_before_cursor(self): """ Return character before the cursor or an empty string. """ return self._get_char_relative_to_cursor(-1) or '' @property def text_before_cursor(self): return self.text[:self.cursor_position:] @property def text_after_cursor(self): return self.text[self.cursor_position:] @property def current_line_before_cursor(self): """ Text from the start of the line until the cursor. """ _, _, text = self.text_before_cursor.rpartition('\n') return text @property def current_line_after_cursor(self): """ Text from the cursor until the end of the line. """ text, _, _ = self.text_after_cursor.partition('\n') return text @property def lines(self): """ Array of all the lines. """ # Cache, because this one is reused very often. if self._cache.lines is None: self._cache.lines = _ImmutableLineList(self.text.split('\n')) return self._cache.lines @property def _line_start_indexes(self): """ Array pointing to the start indexes of all the lines. """ # Cache, because this is often reused. (If it is used, it's often used # many times. And this has to be fast for editing big documents!) if self._cache.line_indexes is None: # Create list of line lengths. line_lengths = map(len, self.lines) # Calculate cumulative sums. indexes = [0] append = indexes.append pos = 0 for line_length in line_lengths: pos += line_length + 1 append(pos) # Remove the last item. (This is not a new line.) if len(indexes) > 1: indexes.pop() self._cache.line_indexes = indexes return self._cache.line_indexes @property def lines_from_current(self): """ Array of the lines starting from the current line, until the last line. """ return self.lines[self.cursor_position_row:] @property def line_count(self): r""" Return the number of lines in this document. If the document ends with a trailing \n, that counts as the beginning of a new line. """ return len(self.lines) @property def current_line(self): """ Return the text on the line where the cursor is. (when the input consists of just one line, it equals `text`. """ return self.current_line_before_cursor + self.current_line_after_cursor @property def leading_whitespace_in_current_line(self): """ The leading whitespace in the left margin of the current line. """ current_line = self.current_line length = len(current_line) - len(current_line.lstrip()) return current_line[:length] def _get_char_relative_to_cursor(self, offset=0): """ Return character relative to cursor position, or empty string """ try: return self.text[self.cursor_position + offset] except IndexError: return '' @property def on_first_line(self): """ True when we are at the first line. """ return self.cursor_position_row == 0 @property def on_last_line(self): """ True when we are at the last line. """ return self.cursor_position_row == self.line_count - 1 @property def cursor_position_row(self): """ Current row. (0-based.) """ row, _ = self._find_line_start_index(self.cursor_position) return row @property def cursor_position_col(self): """ Current column. (0-based.) """ # (Don't use self.text_before_cursor to calculate this. Creating # substrings and doing rsplit is too expensive for getting the cursor # position.) _, line_start_index = self._find_line_start_index(self.cursor_position) return self.cursor_position - line_start_index def _find_line_start_index(self, index): """ For the index of a character at a certain line, calculate the index of the first character on that line. Return (row, index) tuple. """ indexes = self._line_start_indexes pos = bisect.bisect_right(indexes, index) - 1 return pos, indexes[pos] def translate_index_to_position(self, index): """ Given an index for the text, return the corresponding (row, col) tuple. (0-based. Returns (0, 0) for index=0.) """ # Find start of this line. row, row_index = self._find_line_start_index(index) col = index - row_index return row, col def translate_row_col_to_index(self, row, col): """ Given a (row, col) tuple, return the corresponding index. (Row and col params are 0-based.) Negative row/col values are turned into zero. """ try: result = self._line_start_indexes[row] line = self.lines[row] except IndexError: if row < 0: result = self._line_start_indexes[0] line = self.lines[0] else: result = self._line_start_indexes[-1] line = self.lines[-1] result += max(0, min(col, len(line))) # Keep in range. (len(self.text) is included, because the cursor can be # right after the end of the text as well.) result = max(0, min(result, len(self.text))) return result @property def is_cursor_at_the_end(self): """ True when the cursor is at the end of the text. """ return self.cursor_position == len(self.text) @property def is_cursor_at_the_end_of_line(self): """ True when the cursor is at the end of this line. """ return self.current_char in ('\n', '') def has_match_at_current_position(self, sub): """ `True` when this substring is found at the cursor position. """ return self.text.find(sub, self.cursor_position) == self.cursor_position def find(self, sub, in_current_line=False, include_current_position=False, ignore_case=False, count=1): """ Find `text` after the cursor, return position relative to the cursor position. Return `None` if nothing was found. :param count: Find the n-th occurance. """ assert isinstance(ignore_case, bool) if in_current_line: text = self.current_line_after_cursor else: text = self.text_after_cursor if not include_current_position: if len(text) == 0: return # (Otherwise, we always get a match for the empty string.) else: text = text[1:] flags = re.IGNORECASE if ignore_case else 0 iterator = re.finditer(re.escape(sub), text, flags) try: for i, match in enumerate(iterator): if i + 1 == count: if include_current_position: return match.start(0) else: return match.start(0) + 1 except StopIteration: pass def find_all(self, sub, ignore_case=False): """ Find all occurances of the substring. Return a list of absolute positions in the document. """ flags = re.IGNORECASE if ignore_case else 0 return [a.start() for a in re.finditer(re.escape(sub), self.text, flags)] def find_backwards(self, sub, in_current_line=False, ignore_case=False, count=1): """ Find `text` before the cursor, return position relative to the cursor position. Return `None` if nothing was found. :param count: Find the n-th occurance. """ if in_current_line: before_cursor = self.current_line_before_cursor[::-1] else: before_cursor = self.text_before_cursor[::-1] flags = re.IGNORECASE if ignore_case else 0 iterator = re.finditer(re.escape(sub[::-1]), before_cursor, flags) try: for i, match in enumerate(iterator): if i + 1 == count: return - match.start(0) - len(sub) except StopIteration: pass def get_word_before_cursor(self, WORD=False): """ Give the word before the cursor. If we have whitespace before the cursor this returns an empty string. """ if self.text_before_cursor[-1:].isspace(): return '' else: return self.text_before_cursor[self.find_start_of_previous_word(WORD=WORD):] def find_start_of_previous_word(self, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the start of the previous word. Return `None` if nothing was found. """ # Reverse the text before the cursor, in order to do an efficient # backwards search. text_before_cursor = self.text_before_cursor[::-1] regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE iterator = regex.finditer(text_before_cursor) try: for i, match in enumerate(iterator): if i + 1 == count: return - match.end(1) except StopIteration: pass def find_boundaries_of_current_word(self, WORD=False, include_leading_whitespace=False, include_trailing_whitespace=False): """ Return the relative boundaries (startpos, endpos) of the current word under the cursor. (This is at the current line, because line boundaries obviously don't belong to any word.) If not on a word, this returns (0,0) """ text_before_cursor = self.current_line_before_cursor[::-1] text_after_cursor = self.current_line_after_cursor def get_regex(include_whitespace): return { (False, False): _FIND_CURRENT_WORD_RE, (False, True): _FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE, (True, False): _FIND_CURRENT_BIG_WORD_RE, (True, True): _FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE, }[(WORD, include_whitespace)] match_before = get_regex(include_leading_whitespace).search(text_before_cursor) match_after = get_regex(include_trailing_whitespace).search(text_after_cursor) # When there is a match before and after, and we're not looking for # WORDs, make sure that both the part before and after the cursor are # either in the [a-zA-Z_] alphabet or not. Otherwise, drop the part # before the cursor. if not WORD and match_before and match_after: c1 = self.text[self.cursor_position - 1] c2 = self.text[self.cursor_position] alphabet = string.ascii_letters + '0123456789_' if (c1 in alphabet) != (c2 in alphabet): match_before = None return ( - match_before.end(1) if match_before else 0, match_after.end(1) if match_after else 0 ) def get_word_under_cursor(self, WORD=False): """ Return the word, currently below the cursor. This returns an empty string when the cursor is on a whitespace region. """ start, end = self.find_boundaries_of_current_word(WORD=WORD) return self.text[self.cursor_position + start: self.cursor_position + end] def find_next_word_beginning(self, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the start of the next word. Return `None` if nothing was found. """ if count < 0: return self.find_previous_word_beginning(count=-count, WORD=WORD) regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE iterator = regex.finditer(self.text_after_cursor) try: for i, match in enumerate(iterator): # Take first match, unless it's the word on which we're right now. if i == 0 and match.start(1) == 0: count += 1 if i + 1 == count: return match.start(1) except StopIteration: pass def find_next_word_ending(self, include_current_position=False, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the end of the next word. Return `None` if nothing was found. """ if count < 0: return self.find_previous_word_ending(count=-count, WORD=WORD) if include_current_position: text = self.text_after_cursor else: text = self.text_after_cursor[1:] regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE iterable = regex.finditer(text) try: for i, match in enumerate(iterable): if i + 1 == count: value = match.end(1) if include_current_position: return value else: return value + 1 except StopIteration: pass def find_previous_word_beginning(self, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the start of the previous word. Return `None` if nothing was found. """ if count < 0: return self.find_next_word_beginning(count=-count, WORD=WORD) regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE iterator = regex.finditer(self.text_before_cursor[::-1]) try: for i, match in enumerate(iterator): if i + 1 == count: return - match.end(1) except StopIteration: pass def find_previous_word_ending(self, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the end of the previous word. Return `None` if nothing was found. """ if count < 0: return self.find_next_word_ending(count=-count, WORD=WORD) text_before_cursor = self.text_after_cursor[:1] + self.text_before_cursor[::-1] regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE iterator = regex.finditer(text_before_cursor) try: for i, match in enumerate(iterator): # Take first match, unless it's the word on which we're right now. if i == 0 and match.start(1) == 0: count += 1 if i + 1 == count: return -match.start(1) + 1 except StopIteration: pass def find_next_matching_line(self, match_func, count=1): """ Look downwards for empty lines. Return the line index, relative to the current line. """ result = None for index, line in enumerate(self.lines[self.cursor_position_row + 1:]): if match_func(line): result = 1 + index count -= 1 if count == 0: break return result def find_previous_matching_line(self, match_func, count=1): """ Look upwards for empty lines. Return the line index, relative to the current line. """ result = None for index, line in enumerate(self.lines[:self.cursor_position_row][::-1]): if match_func(line): result = -1 - index count -= 1 if count == 0: break return result def get_cursor_left_position(self, count=1): """ Relative position for cursor left. """ if count < 0: return self.get_cursor_right_position(-count) return - min(self.cursor_position_col, count) def get_cursor_right_position(self, count=1): """ Relative position for cursor_right. """ if count < 0: return self.get_cursor_left_position(-count) return min(count, len(self.current_line_after_cursor)) def get_cursor_up_position(self, count=1, preferred_column=None): """ Return the relative cursor position (character index) where we would be if the user pressed the arrow-up button. :param preferred_column: When given, go to this column instead of staying at the current column. """ assert count >= 1 column = self.cursor_position_col if preferred_column is None else preferred_column return self.translate_row_col_to_index( max(0, self.cursor_position_row - count), column) - self.cursor_position def get_cursor_down_position(self, count=1, preferred_column=None): """ Return the relative cursor position (character index) where we would be if the user pressed the arrow-down button. :param preferred_column: When given, go to this column instead of staying at the current column. """ assert count >= 1 column = self.cursor_position_col if preferred_column is None else preferred_column return self.translate_row_col_to_index( self.cursor_position_row + count, column) - self.cursor_position def find_enclosing_bracket_right(self, left_ch, right_ch, end_pos=None): """ Find the right bracket enclosing current position. Return the relative position to the cursor position. When `end_pos` is given, don't look past the position. """ if self.current_char == right_ch: return 0 if end_pos is None: end_pos = len(self.text) else: end_pos = min(len(self.text), end_pos) stack = 1 # Look forward. for i in range(self.cursor_position + 1, end_pos): c = self.text[i] if c == left_ch: stack += 1 elif c == right_ch: stack -= 1 if stack == 0: return i - self.cursor_position def find_enclosing_bracket_left(self, left_ch, right_ch, start_pos=None): """ Find the left bracket enclosing current position. Return the relative position to the cursor position. When `start_pos` is given, don't look past the position. """ if self.current_char == left_ch: return 0 if start_pos is None: start_pos = 0 else: start_pos = max(0, start_pos) stack = 1 # Look backward. for i in range(self.cursor_position - 1, start_pos - 1, -1): c = self.text[i] if c == right_ch: stack += 1 elif c == left_ch: stack -= 1 if stack == 0: return i - self.cursor_position def find_matching_bracket_position(self, start_pos=None, end_pos=None): """ Return relative cursor position of matching [, (, { or < bracket. When `start_pos` or `end_pos` are given. Don't look past the positions. """ # Look for a match. for A, B in '()', '[]', '{}', '<>': if self.current_char == A: return self.find_enclosing_bracket_right(A, B, end_pos=end_pos) or 0 elif self.current_char == B: return self.find_enclosing_bracket_left(A, B, start_pos=start_pos) or 0 return 0 def get_start_of_document_position(self): """ Relative position for the start of the document. """ return - self.cursor_position def get_end_of_document_position(self): """ Relative position for the end of the document. """ return len(self.text) - self.cursor_position def get_start_of_line_position(self, after_whitespace=False): """ Relative position for the start of this line. """ if after_whitespace: current_line = self.current_line return len(current_line) - len(current_line.lstrip()) - self.cursor_position_col else: return - len(self.current_line_before_cursor) def get_end_of_line_position(self): """ Relative position for the end of this line. """ return len(self.current_line_after_cursor) def last_non_blank_of_current_line_position(self): """ Relative position for the last non blank character of this line. """ return len(self.current_line.rstrip()) - self.cursor_position_col - 1 def get_column_cursor_position(self, column): """ Return the relative cursor position for this column at the current line. (It will stay between the boundaries of the line in case of a larger number.) """ line_length = len(self.current_line) current_column = self.cursor_position_col column = max(0, min(line_length, column)) return column - current_column def selection_range(self): # XXX: shouldn't this return `None` if there is no selection??? """ Return (from, to) tuple of the selection. start and end position are included. This doesn't take the selection type into account. Use `selection_ranges` instead. """ if self.selection: from_, to = sorted([self.cursor_position, self.selection.original_cursor_position]) else: from_, to = self.cursor_position, self.cursor_position return from_, to def selection_ranges(self): """ Return a list of (from, to) tuples for the selection or none if nothing was selected. start and end position are always included in the selection. This will yield several (from, to) tuples in case of a BLOCK selection. """ if self.selection: from_, to = sorted([self.cursor_position, self.selection.original_cursor_position]) if self.selection.type == SelectionType.BLOCK: from_line, from_column = self.translate_index_to_position(from_) to_line, to_column = self.translate_index_to_position(to) from_column, to_column = sorted([from_column, to_column]) lines = self.lines for l in range(from_line, to_line + 1): line_length = len(lines[l]) if from_column < line_length: yield (self.translate_row_col_to_index(l, from_column), self.translate_row_col_to_index(l, min(line_length - 1, to_column))) else: # In case of a LINES selection, go to the start/end of the lines. if self.selection.type == SelectionType.LINES: from_ = max(0, self.text.rfind('\n', 0, from_) + 1) if self.text.find('\n', to) >= 0: to = self.text.find('\n', to) else: to = len(self.text) - 1 yield from_, to def selection_range_at_line(self, row): """ If the selection spans a portion of the given line, return a (from, to) tuple. Otherwise, return None. """ if self.selection: row_start = self.translate_row_col_to_index(row, 0) row_end = self.translate_row_col_to_index(row, max(0, len(self.lines[row]) - 1)) from_, to = sorted([self.cursor_position, self.selection.original_cursor_position]) # Take the intersection of the current line and the selection. intersection_start = max(row_start, from_) intersection_end = min(row_end, to) if intersection_start <= intersection_end: if self.selection.type == SelectionType.LINES: intersection_start = row_start intersection_end = row_end elif self.selection.type == SelectionType.BLOCK: _, col1 = self.translate_index_to_position(from_) _, col2 = self.translate_index_to_position(to) col1, col2 = sorted([col1, col2]) intersection_start = self.translate_row_col_to_index(row, col1) intersection_end = self.translate_row_col_to_index(row, col2) _, from_column = self.translate_index_to_position(intersection_start) _, to_column = self.translate_index_to_position(intersection_end) return from_column, to_column def cut_selection(self): """ Return a (:class:`.Document`, :class:`.ClipboardData`) tuple, where the document represents the new document when the selection is cut, and the clipboard data, represents whatever has to be put on the clipboard. """ if self.selection: cut_parts = [] remaining_parts = [] new_cursor_position = self.cursor_position last_to = 0 for from_, to in self.selection_ranges(): if last_to == 0: new_cursor_position = from_ remaining_parts.append(self.text[last_to:from_]) cut_parts.append(self.text[from_:to + 1]) last_to = to + 1 remaining_parts.append(self.text[last_to:]) cut_text = '\n'.join(cut_parts) remaining_text = ''.join(remaining_parts) # In case of a LINES selection, don't include the trailing newline. if self.selection.type == SelectionType.LINES and cut_text.endswith('\n'): cut_text = cut_text[:-1] return (Document(text=remaining_text, cursor_position=new_cursor_position), ClipboardData(cut_text, self.selection.type)) else: return self, ClipboardData('') def paste_clipboard_data(self, data, paste_mode=PasteMode.EMACS, count=1): """ Return a new :class:`.Document` instance which contains the result if we would paste this data at the current cursor position. :param paste_mode: Where to paste. (Before/after/emacs.) :param count: When >1, Paste multiple times. """ assert isinstance(data, ClipboardData) assert paste_mode in (PasteMode.VI_BEFORE, PasteMode.VI_AFTER, PasteMode.EMACS) before = (paste_mode == PasteMode.VI_BEFORE) after = (paste_mode == PasteMode.VI_AFTER) if data.type == SelectionType.CHARACTERS: if after: new_text = (self.text[:self.cursor_position + 1] + data.text * count + self.text[self.cursor_position + 1:]) else: new_text = self.text_before_cursor + data.text * count + self.text_after_cursor new_cursor_position = self.cursor_position + len(data.text) * count if before: new_cursor_position -= 1 elif data.type == SelectionType.LINES: l = self.cursor_position_row if before: lines = self.lines[:l] + [data.text] * count + self.lines[l:] new_text = '\n'.join(lines) new_cursor_position = len(''.join(self.lines[:l])) + l else: lines = self.lines[:l + 1] + [data.text] * count + self.lines[l + 1:] new_cursor_position = len(''.join(self.lines[:l + 1])) + l + 1 new_text = '\n'.join(lines) elif data.type == SelectionType.BLOCK: lines = self.lines[:] start_line = self.cursor_position_row start_column = self.cursor_position_col + (0 if before else 1) for i, line in enumerate(data.text.split('\n')): index = i + start_line if index >= len(lines): lines.append('') lines[index] = lines[index].ljust(start_column) lines[index] = lines[index][:start_column] + line * count + lines[index][start_column:] new_text = '\n'.join(lines) new_cursor_position = self.cursor_position + (0 if before else 1) return Document(text=new_text, cursor_position=new_cursor_position) def empty_line_count_at_the_end(self): """ Return number of empty lines at the end of the document. """ count = 0 for line in self.lines[::-1]: if not line or line.isspace(): count += 1 else: break return count def start_of_paragraph(self, count=1, before=False): """ Return the start of the current paragraph. (Relative cursor position.) """ def match_func(text): return not text or text.isspace() line_index = self.find_previous_matching_line(match_func=match_func, count=count) if line_index: add = 0 if before else 1 return min(0, self.get_cursor_up_position(count=-line_index) + add) else: return -self.cursor_position def end_of_paragraph(self, count=1, after=False): """ Return the end of the current paragraph. (Relative cursor position.) """ def match_func(text): return not text or text.isspace() line_index = self.find_next_matching_line(match_func=match_func, count=count) if line_index: add = 0 if after else 1 return max(0, self.get_cursor_down_position(count=line_index) - add) else: return len(self.text_after_cursor) # Modifiers. def insert_after(self, text): """ Create a new document, with this text inserted after the buffer. It keeps selection ranges and cursor position in sync. """ return Document( text=self.text + text, cursor_position=self.cursor_position, selection=self.selection) def insert_before(self, text): """ Create a new document, with this text inserted before the buffer. It keeps selection ranges and cursor position in sync. """ selection_state = self.selection if selection_state: selection_state = SelectionState( original_cursor_position=selection_state.original_cursor_position + len(text), type=selection_state.type) return Document( text=text + self.text, cursor_position=self.cursor_position + len(text), selection=selection_state) prompt_toolkit-1.0.15/prompt_toolkit/renderer.py0000664000175000017500000004642413136130420023565 0ustar jonathanjonathan00000000000000""" Renders the command line on the console. (Redraws parts of the input line that were changed.) """ from __future__ import unicode_literals from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.layout.mouse_handlers import MouseHandlers from prompt_toolkit.layout.screen import Point, Screen, WritePosition from prompt_toolkit.output import Output from prompt_toolkit.styles import Style from prompt_toolkit.token import Token from prompt_toolkit.utils import is_windows from six.moves import range __all__ = ( 'Renderer', 'print_tokens', ) def _output_screen_diff(output, screen, current_pos, previous_screen=None, last_token=None, is_done=False, use_alternate_screen=False, attrs_for_token=None, size=None, previous_width=0): # XXX: drop is_done """ Render the diff between this screen and the previous screen. This takes two `Screen` instances. The one that represents the output like it was during the last rendering and one that represents the current output raster. Looking at these two `Screen` instances, this function will render the difference by calling the appropriate methods of the `Output` object that only paint the changes to the terminal. This is some performance-critical code which is heavily optimized. Don't change things without profiling first. :param current_pos: Current cursor position. :param last_token: `Token` instance that represents the output attributes of the last drawn character. (Color/attributes.) :param attrs_for_token: :class:`._TokenToAttrsCache` instance. :param width: The width of the terminal. :param prevous_width: The width of the terminal during the last rendering. """ width, height = size.columns, size.rows #: Remember the last printed character. last_token = [last_token] # nonlocal #: Variable for capturing the output. write = output.write write_raw = output.write_raw # Create locals for the most used output methods. # (Save expensive attribute lookups.) _output_set_attributes = output.set_attributes _output_reset_attributes = output.reset_attributes _output_cursor_forward = output.cursor_forward _output_cursor_up = output.cursor_up _output_cursor_backward = output.cursor_backward # Hide cursor before rendering. (Avoid flickering.) output.hide_cursor() def reset_attributes(): " Wrapper around Output.reset_attributes. " _output_reset_attributes() last_token[0] = None # Forget last char after resetting attributes. def move_cursor(new): " Move cursor to this `new` point. Returns the given Point. " current_x, current_y = current_pos.x, current_pos.y if new.y > current_y: # Use newlines instead of CURSOR_DOWN, because this meight add new lines. # CURSOR_DOWN will never create new lines at the bottom. # Also reset attributes, otherwise the newline could draw a # background color. reset_attributes() write('\r\n' * (new.y - current_y)) current_x = 0 _output_cursor_forward(new.x) return new elif new.y < current_y: _output_cursor_up(current_y - new.y) if current_x >= width - 1: write('\r') _output_cursor_forward(new.x) elif new.x < current_x or current_x >= width - 1: _output_cursor_backward(current_x - new.x) elif new.x > current_x: _output_cursor_forward(new.x - current_x) return new def output_char(char): """ Write the output of this character. """ # If the last printed character has the same token, it also has the # same style, so we don't output it. the_last_token = last_token[0] if the_last_token and the_last_token == char.token: write(char.char) else: _output_set_attributes(attrs_for_token[char.token]) write(char.char) last_token[0] = char.token # Render for the first time: reset styling. if not previous_screen: reset_attributes() # Disable autowrap. (When entering a the alternate screen, or anytime when # we have a prompt. - In the case of a REPL, like IPython, people can have # background threads, and it's hard for debugging if their output is not # wrapped.) if not previous_screen or not use_alternate_screen: output.disable_autowrap() # When the previous screen has a different size, redraw everything anyway. # Also when we are done. (We meight take up less rows, so clearing is important.) if is_done or not previous_screen or previous_width != width: # XXX: also consider height?? current_pos = move_cursor(Point(0, 0)) reset_attributes() output.erase_down() previous_screen = Screen() # Get height of the screen. # (height changes as we loop over data_buffer, so remember the current value.) # (Also make sure to clip the height to the size of the output.) current_height = min(screen.height, height) # Loop over the rows. row_count = min(max(screen.height, previous_screen.height), height) c = 0 # Column counter. for y in range(row_count): new_row = screen.data_buffer[y] previous_row = previous_screen.data_buffer[y] zero_width_escapes_row = screen.zero_width_escapes[y] new_max_line_len = min(width - 1, max(new_row.keys()) if new_row else 0) previous_max_line_len = min(width - 1, max(previous_row.keys()) if previous_row else 0) # Loop over the columns. c = 0 while c < new_max_line_len + 1: new_char = new_row[c] old_char = previous_row[c] char_width = (new_char.width or 1) # When the old and new character at this position are different, # draw the output. (Because of the performance, we don't call # `Char.__ne__`, but inline the same expression.) if new_char.char != old_char.char or new_char.token != old_char.token: current_pos = move_cursor(Point(y=y, x=c)) # Send injected escape sequences to output. if c in zero_width_escapes_row: write_raw(zero_width_escapes_row[c]) output_char(new_char) current_pos = current_pos._replace(x=current_pos.x + char_width) c += char_width # If the new line is shorter, trim it. if previous_screen and new_max_line_len < previous_max_line_len: current_pos = move_cursor(Point(y=y, x=new_max_line_len+1)) reset_attributes() output.erase_end_of_line() # Correctly reserve vertical space as required by the layout. # When this is a new screen (drawn for the first time), or for some reason # higher than the previous one. Move the cursor once to the bottom of the # output. That way, we're sure that the terminal scrolls up, even when the # lower lines of the canvas just contain whitespace. # The most obvious reason that we actually want this behaviour is the avoid # the artifact of the input scrolling when the completion menu is shown. # (If the scrolling is actually wanted, the layout can still be build in a # way to behave that way by setting a dynamic height.) if current_height > previous_screen.height: current_pos = move_cursor(Point(y=current_height - 1, x=0)) # Move cursor: if is_done: current_pos = move_cursor(Point(y=current_height, x=0)) output.erase_down() else: current_pos = move_cursor(screen.cursor_position) if is_done or not use_alternate_screen: output.enable_autowrap() # Always reset the color attributes. This is important because a background # thread could print data to stdout and we want that to be displayed in the # default colors. (Also, if a background color has been set, many terminals # give weird artifacs on resize events.) reset_attributes() if screen.show_cursor or is_done: output.show_cursor() return current_pos, last_token[0] class HeightIsUnknownError(Exception): " Information unavailable. Did not yet receive the CPR response. " class _TokenToAttrsCache(dict): """ A cache structure that maps Pygments Tokens to :class:`.Attr`. (This is an important speed up.) """ def __init__(self, get_style_for_token): self.get_style_for_token = get_style_for_token def __missing__(self, token): try: result = self.get_style_for_token(token) except KeyError: result = None self[token] = result return result class Renderer(object): """ Typical usage: :: output = Vt100_Output.from_pty(sys.stdout) r = Renderer(style, output) r.render(cli, layout=...) """ def __init__(self, style, output, use_alternate_screen=False, mouse_support=False): assert isinstance(style, Style) assert isinstance(output, Output) self.style = style self.output = output self.use_alternate_screen = use_alternate_screen self.mouse_support = to_cli_filter(mouse_support) self._in_alternate_screen = False self._mouse_support_enabled = False self._bracketed_paste_enabled = False # Waiting for CPR flag. True when we send the request, but didn't got a # response. self.waiting_for_cpr = False self.reset(_scroll=True) def reset(self, _scroll=False, leave_alternate_screen=True): # Reset position self._cursor_pos = Point(x=0, y=0) # Remember the last screen instance between renderers. This way, # we can create a `diff` between two screens and only output the # difference. It's also to remember the last height. (To show for # instance a toolbar at the bottom position.) self._last_screen = None self._last_size = None self._last_token = None # When the style hash changes, we have to do a full redraw as well as # clear the `_attrs_for_token` dictionary. self._last_style_hash = None self._attrs_for_token = None # Default MouseHandlers. (Just empty.) self.mouse_handlers = MouseHandlers() # Remember the last title. Only set the title when it changes. self._last_title = None #: Space from the top of the layout, until the bottom of the terminal. #: 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. (Only when rendering the first time.) if is_windows() and _scroll: self.output.scroll_buffer_to_prompt() # Quit alternate screen. if self._in_alternate_screen and leave_alternate_screen: self.output.quit_alternate_screen() self._in_alternate_screen = False # Disable mouse support. if self._mouse_support_enabled: self.output.disable_mouse_support() self._mouse_support_enabled = False # Disable bracketed paste. if self._bracketed_paste_enabled: self.output.disable_bracketed_paste() self._bracketed_paste_enabled = False # Flush output. `disable_mouse_support` needs to write to stdout. self.output.flush() @property def height_is_known(self): """ True when the height from the cursor until the bottom of the terminal is known. (It's often nicer to draw bottom toolbars only if the height is known, in order to avoid flickering when the CPR response arrives.) """ return self.use_alternate_screen or self._min_available_height > 0 or \ is_windows() # On Windows, we don't have to wait for a CPR. @property def rows_above_layout(self): """ Return the number of rows visible in the terminal above the layout. """ if self._in_alternate_screen: return 0 elif self._min_available_height > 0: total_rows = self.output.get_size().rows last_screen_height = self._last_screen.height if self._last_screen else 0 return total_rows - max(self._min_available_height, last_screen_height) else: raise HeightIsUnknownError('Rows above layout is unknown.') def request_absolute_cursor_position(self): """ Get current cursor position. For vt100: Do CPR request. (answer will arrive later.) For win32: Do API call. (Answer comes immediately.) """ # Only do this request when the cursor is at the top row. (after a # clear or reset). We will rely on that in `report_absolute_cursor_row`. assert self._cursor_pos.y == 0 # For Win32, we have an API call to get the number of rows below the # cursor. if is_windows(): self._min_available_height = self.output.get_rows_below_cursor_position() else: if self.use_alternate_screen: self._min_available_height = self.output.get_size().rows else: # Asks for a cursor position report (CPR). self.waiting_for_cpr = True self.output.ask_for_cpr() def report_absolute_cursor_row(self, row): """ To be called when we know the absolute cursor position. (As an answer of a "Cursor Position Request" response.) """ # Calculate the amount of rows from the cursor position until the # bottom of the terminal. total_rows = self.output.get_size().rows rows_below_cursor = total_rows - row + 1 # Set the self._min_available_height = rows_below_cursor self.waiting_for_cpr = False def render(self, cli, layout, is_done=False): """ Render the current interface to the output. :param is_done: When True, put the cursor at the end of the interface. We won't print any changes to this part. """ output = self.output # Enter alternate screen. if self.use_alternate_screen and not self._in_alternate_screen: self._in_alternate_screen = True output.enter_alternate_screen() # Enable bracketed paste. if not self._bracketed_paste_enabled: self.output.enable_bracketed_paste() self._bracketed_paste_enabled = True # Enable/disable mouse support. needs_mouse_support = self.mouse_support(cli) if needs_mouse_support and not self._mouse_support_enabled: output.enable_mouse_support() self._mouse_support_enabled = True elif not needs_mouse_support and self._mouse_support_enabled: output.disable_mouse_support() self._mouse_support_enabled = False # Create screen and write layout to it. size = output.get_size() screen = Screen() screen.show_cursor = False # Hide cursor by default, unless one of the # containers decides to display it. mouse_handlers = MouseHandlers() if is_done: height = 0 # When we are done, we don't necessary want to fill up until the bottom. else: height = self._last_screen.height if self._last_screen else 0 height = max(self._min_available_height, height) # When te size changes, don't consider the previous screen. if self._last_size != size: self._last_screen = None # When we render using another style, do a full repaint. (Forget about # the previous rendered screen.) # (But note that we still use _last_screen to calculate the height.) if self.style.invalidation_hash() != self._last_style_hash: self._last_screen = None self._attrs_for_token = None if self._attrs_for_token is None: self._attrs_for_token = _TokenToAttrsCache(self.style.get_attrs_for_token) self._last_style_hash = self.style.invalidation_hash() layout.write_to_screen(cli, screen, mouse_handlers, WritePosition( xpos=0, ypos=0, width=size.columns, height=(size.rows if self.use_alternate_screen else height), extended_height=size.rows, )) # When grayed. Replace all tokens in the new screen. if cli.is_aborting or cli.is_exiting: screen.replace_all_tokens(Token.Aborted) # Process diff and write to output. self._cursor_pos, self._last_token = _output_screen_diff( output, screen, self._cursor_pos, self._last_screen, self._last_token, is_done, use_alternate_screen=self.use_alternate_screen, attrs_for_token=self._attrs_for_token, size=size, previous_width=(self._last_size.columns if self._last_size else 0)) self._last_screen = screen self._last_size = size self.mouse_handlers = mouse_handlers # Write title if it changed. new_title = cli.terminal_title if new_title != self._last_title: if new_title is None: self.output.clear_title() else: self.output.set_title(new_title) self._last_title = new_title output.flush() def erase(self, leave_alternate_screen=True, erase_title=True): """ Hide all output and put the cursor back at the first line. This is for instance used for running a system command (while hiding the CLI) and later resuming the same CLI.) :param leave_alternate_screen: When True, and when inside an alternate screen buffer, quit the alternate screen. :param erase_title: When True, clear the title from the title bar. """ output = self.output output.cursor_backward(self._cursor_pos.x) output.cursor_up(self._cursor_pos.y) output.erase_down() output.reset_attributes() output.enable_autowrap() output.flush() # Erase title. if self._last_title and erase_title: output.clear_title() self.reset(leave_alternate_screen=leave_alternate_screen) def clear(self): """ Clear screen and go to 0,0 """ # Erase current output first. self.erase() # Send "Erase Screen" command and go to (0, 0). output = self.output output.erase_screen() output.cursor_goto(0, 0) output.flush() self.request_absolute_cursor_position() def print_tokens(output, tokens, style): """ Print a list of (Token, text) tuples in the given style to the output. """ assert isinstance(output, Output) assert isinstance(style, Style) # Reset first. output.reset_attributes() output.enable_autowrap() # Print all (token, text) tuples. attrs_for_token = _TokenToAttrsCache(style.get_attrs_for_token) for token, text in tokens: attrs = attrs_for_token[token] if attrs: output.set_attributes(attrs) else: output.reset_attributes() output.write(text) # Reset again. output.reset_attributes() output.flush() prompt_toolkit-1.0.15/prompt_toolkit/history.py0000664000175000017500000000544513136130420023456 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass import datetime import os __all__ = ( 'FileHistory', 'History', 'InMemoryHistory', ) class History(with_metaclass(ABCMeta, object)): """ Base ``History`` interface. """ @abstractmethod def append(self, string): " Append string to history. " @abstractmethod def __getitem__(self, key): " Return one item of the history. It should be accessible like a `list`. " @abstractmethod def __iter__(self): " Iterate through all the items of the history. Cronologically. " @abstractmethod def __len__(self): " Return the length of the history. " def __bool__(self): """ Never evaluate to False, even when the history is empty. (Python calls __len__ if __bool__ is not implemented.) This is mainly to allow lazy evaluation:: x = history or InMemoryHistory() """ return True __nonzero__ = __bool__ # For Python 2. class InMemoryHistory(History): """ :class:`.History` class that keeps a list of all strings in memory. """ def __init__(self): self.strings = [] def append(self, string): self.strings.append(string) def __getitem__(self, key): return self.strings[key] def __iter__(self): return iter(self.strings) def __len__(self): return len(self.strings) class FileHistory(History): """ :class:`.History` class that stores all strings in a file. """ def __init__(self, filename): self.strings = [] self.filename = filename self._load() def _load(self): lines = [] def add(): if lines: # Join and drop trailing newline. string = ''.join(lines)[:-1] self.strings.append(string) if os.path.exists(self.filename): with open(self.filename, 'rb') as f: for line in f: line = line.decode('utf-8') if line.startswith('+'): lines.append(line[1:]) else: add() lines = [] add() def append(self, string): self.strings.append(string) # Save to file. with open(self.filename, 'ab') as f: def write(t): f.write(t.encode('utf-8')) write('\n# %s\n' % datetime.datetime.now()) for line in string.split('\n'): write('+%s\n' % line) def __getitem__(self, key): return self.strings[key] def __iter__(self): return iter(self.strings) def __len__(self): return len(self.strings) prompt_toolkit-1.0.15/prompt_toolkit/contrib/0000775000175000017500000000000013136335632023047 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/contrib/telnet/0000775000175000017500000000000013136335632024342 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/contrib/telnet/server.py0000664000175000017500000003113513136130420026211 0ustar jonathanjonathan00000000000000""" Telnet server. Example usage:: class MyTelnetApplication(TelnetApplication): def client_connected(self, telnet_connection): # Set CLI with simple prompt. telnet_connection.set_application( telnet_connection.create_prompt_application(...)) def handle_command(self, telnet_connection, document): # When the client enters a command, just reply. telnet_connection.send('You said: %r\n\n' % document.text) ... a = MyTelnetApplication() TelnetServer(application=a, host='127.0.0.1', port=23).run() """ from __future__ import unicode_literals import socket import select import threading import os import fcntl from six import int2byte, text_type, binary_type from codecs import getincrementaldecoder from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.eventloop.base import EventLoop from prompt_toolkit.interface import CommandLineInterface, Application from prompt_toolkit.layout.screen import Size from prompt_toolkit.shortcuts import create_prompt_application from prompt_toolkit.terminal.vt100_input import InputStream from prompt_toolkit.terminal.vt100_output import Vt100_Output from .log import logger from .protocol import IAC, DO, LINEMODE, SB, MODE, SE, WILL, ECHO, NAWS, SUPPRESS_GO_AHEAD from .protocol import TelnetProtocolParser from .application import TelnetApplication __all__ = ( 'TelnetServer', ) def _initialize_telnet(connection): logger.info('Initializing telnet connection') # Iac Do Linemode connection.send(IAC + DO + LINEMODE) # Suppress Go Ahead. (This seems important for Putty to do correct echoing.) # This will allow bi-directional operation. connection.send(IAC + WILL + SUPPRESS_GO_AHEAD) # Iac sb connection.send(IAC + SB + LINEMODE + MODE + int2byte(0) + IAC + SE) # IAC Will Echo connection.send(IAC + WILL + ECHO) # Negotiate window size connection.send(IAC + DO + NAWS) class _ConnectionStdout(object): """ Wrapper around socket which provides `write` and `flush` methods for the Vt100_Output output. """ def __init__(self, connection, encoding): self._encoding = encoding self._connection = connection self._buffer = [] def write(self, data): assert isinstance(data, text_type) self._buffer.append(data.encode(self._encoding)) self.flush() def flush(self): try: self._connection.send(b''.join(self._buffer)) except socket.error as e: logger.error("Couldn't send data over socket: %s" % e) self._buffer = [] class TelnetConnection(object): """ Class that represents one Telnet connection. """ def __init__(self, conn, addr, application, server, encoding): assert isinstance(addr, tuple) # (addr, port) tuple assert isinstance(application, TelnetApplication) assert isinstance(server, TelnetServer) assert isinstance(encoding, text_type) # e.g. 'utf-8' self.conn = conn self.addr = addr self.application = application self.closed = False self.handling_command = True self.server = server self.encoding = encoding self.callback = None # Function that handles the CLI result. # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create output. def get_size(): return self.size self.stdout = _ConnectionStdout(conn, encoding=encoding) self.vt100_output = Vt100_Output(self.stdout, get_size, write_binary=False) # Create an eventloop (adaptor) for the CommandLineInterface. self.eventloop = _TelnetEventLoopInterface(server) # Set default CommandLineInterface. self.set_application(create_prompt_application()) # Call client_connected application.client_connected(self) # Draw for the first time. self.handling_command = False self.cli._redraw() def set_application(self, app, callback=None): """ Set ``CommandLineInterface`` instance for this connection. (This can be replaced any time.) :param cli: CommandLineInterface instance. :param callback: Callable that takes the result of the CLI. """ assert isinstance(app, Application) assert callback is None or callable(callback) self.cli = CommandLineInterface( application=app, eventloop=self.eventloop, output=self.vt100_output) self.callback = callback # Create a parser, and parser callbacks. cb = self.cli.create_eventloop_callbacks() inputstream = InputStream(cb.feed_key) # Input decoder for stdin. (Required when working with multibyte # characters, like chinese input.) stdin_decoder_cls = getincrementaldecoder(self.encoding) stdin_decoder = [stdin_decoder_cls()] # nonlocal # Tell the CLI that it's running. We don't start it through the run() # call, but will still want _redraw() to work. self.cli._is_running = True def data_received(data): """ TelnetProtocolParser 'data_received' callback """ assert isinstance(data, binary_type) try: result = stdin_decoder[0].decode(data) inputstream.feed(result) except UnicodeDecodeError: stdin_decoder[0] = stdin_decoder_cls() return '' def size_received(rows, columns): """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) cb.terminal_size_changed() self.parser = TelnetProtocolParser(data_received, size_received) def feed(self, data): """ Handler for incoming data. (Called by TelnetServer.) """ assert isinstance(data, binary_type) self.parser.feed(data) # Render again. self.cli._redraw() # When a return value has been set (enter was pressed), handle command. if self.cli.is_returning: try: return_value = self.cli.return_value() except (EOFError, KeyboardInterrupt) as e: # Control-D or Control-C was pressed. logger.info('%s, closing connection.', type(e).__name__) self.close() return # Handle CLI command self._handle_command(return_value) def _handle_command(self, command): """ Handle command. This will run in a separate thread, in order not to block the event loop. """ logger.info('Handle command %r', command) def in_executor(): self.handling_command = True try: if self.callback is not None: self.callback(self, command) finally: self.server.call_from_executor(done) def done(): self.handling_command = False # Reset state and draw again. (If the connection is still open -- # the application could have called TelnetConnection.close() if not self.closed: self.cli.reset() self.cli.buffers[DEFAULT_BUFFER].reset() self.cli.renderer.request_absolute_cursor_position() self.vt100_output.flush() self.cli._redraw() self.server.run_in_executor(in_executor) def erase_screen(self): """ Erase output screen. """ self.vt100_output.erase_screen() self.vt100_output.cursor_goto(0, 0) self.vt100_output.flush() def send(self, data): """ Send text to the client. """ assert isinstance(data, text_type) # When data is send back to the client, we should replace the line # endings. (We didn't allocate a real pseudo terminal, and the telnet # connection is raw, so we are responsible for inserting \r.) self.stdout.write(data.replace('\n', '\r\n')) self.stdout.flush() def close(self): """ Close the connection. """ self.application.client_leaving(self) self.conn.close() self.closed = True class _TelnetEventLoopInterface(EventLoop): """ Eventloop object to be assigned to `CommandLineInterface`. """ def __init__(self, server): self._server = server def close(self): " Ignore. " def stop(self): " Ignore. " def run_in_executor(self, callback): self._server.run_in_executor(callback) def call_from_executor(self, callback, _max_postpone_until=None): self._server.call_from_executor(callback) def add_reader(self, fd, callback): raise NotImplementedError def remove_reader(self, fd): raise NotImplementedError class TelnetServer(object): """ Telnet server implementation. """ def __init__(self, host='127.0.0.1', port=23, application=None, encoding='utf-8'): assert isinstance(host, text_type) assert isinstance(port, int) assert isinstance(application, TelnetApplication) assert isinstance(encoding, text_type) self.host = host self.port = port self.application = application self.encoding = encoding self.connections = set() self._calls_from_executor = [] # Create a pipe for inter thread communication. self._schedule_pipe = os.pipe() fcntl.fcntl(self._schedule_pipe[0], fcntl.F_SETFL, os.O_NONBLOCK) @classmethod def create_socket(cls, host, port): # Create and bind socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) s.listen(4) return s def run_in_executor(self, callback): threading.Thread(target=callback).start() def call_from_executor(self, callback): self._calls_from_executor.append(callback) if self._schedule_pipe: os.write(self._schedule_pipe[1], b'x') def _process_callbacks(self): """ Process callbacks from `call_from_executor` in eventloop. """ # Flush all the pipe content. os.read(self._schedule_pipe[0], 1024) # Process calls from executor. calls_from_executor, self._calls_from_executor = self._calls_from_executor, [] for c in calls_from_executor: c() def run(self): """ Run the eventloop for the telnet server. """ listen_socket = self.create_socket(self.host, self.port) logger.info('Listening for telnet connections on %s port %r', self.host, self.port) try: while True: # Removed closed connections. self.connections = set([c for c in self.connections if not c.closed]) # Ignore connections handling commands. connections = set([c for c in self.connections if not c.handling_command]) # Wait for next event. read_list = ( [listen_socket, self._schedule_pipe[0]] + [c.conn for c in connections]) read, _, _ = select.select(read_list, [], []) for s in read: # When the socket itself is ready, accept a new connection. if s == listen_socket: self._accept(listen_socket) # If we receive something on our "call_from_executor" pipe, process # these callbacks in a thread safe way. elif s == self._schedule_pipe[0]: self._process_callbacks() # Handle incoming data on socket. else: self._handle_incoming_data(s) finally: listen_socket.close() def _accept(self, listen_socket): """ Accept new incoming connection. """ conn, addr = listen_socket.accept() connection = TelnetConnection(conn, addr, self.application, self, encoding=self.encoding) self.connections.add(connection) logger.info('New connection %r %r', *addr) def _handle_incoming_data(self, conn): """ Handle incoming data on socket. """ connection = [c for c in self.connections if c.conn == conn][0] data = conn.recv(1024) if data: connection.feed(data) else: self.connections.remove(connection) prompt_toolkit-1.0.15/prompt_toolkit/contrib/telnet/__init__.py0000664000175000017500000000006113136130420026434 0ustar jonathanjonathan00000000000000from .server import * from .application import * prompt_toolkit-1.0.15/prompt_toolkit/contrib/telnet/application.py0000664000175000017500000000157613136130420027214 0ustar jonathanjonathan00000000000000""" Interface for Telnet applications. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'TelnetApplication', ) class TelnetApplication(with_metaclass(ABCMeta, object)): """ The interface which has to be implemented for any telnet application. An instance of this class has to be passed to `TelnetServer`. """ @abstractmethod def client_connected(self, telnet_connection): """ Called when a new client was connected. Probably you want to call `telnet_connection.set_cli` here to set a the CommandLineInterface instance to be used. Hint: Use the following shortcut: `prompt_toolkit.shortcuts.create_cli` """ @abstractmethod def client_leaving(self, telnet_connection): """ Called when a client quits. """ prompt_toolkit-1.0.15/prompt_toolkit/contrib/telnet/protocol.py0000664000175000017500000001142413136130420026543 0ustar jonathanjonathan00000000000000""" Parser for the Telnet protocol. (Not a complete implementation of the telnet specification, but sufficient for a command line interface.) Inspired by `Twisted.conch.telnet`. """ from __future__ import unicode_literals import struct from six import int2byte, binary_type, iterbytes from .log import logger __all__ = ( 'TelnetProtocolParser', ) # Telnet constants. NOP = int2byte(0) SGA = int2byte(3) IAC = int2byte(255) DO = int2byte(253) DONT = int2byte(254) LINEMODE = int2byte(34) SB = int2byte(250) WILL = int2byte(251) WONT = int2byte(252) MODE = int2byte(1) SE = int2byte(240) ECHO = int2byte(1) NAWS = int2byte(31) LINEMODE = int2byte(34) SUPPRESS_GO_AHEAD = int2byte(3) DM = int2byte(242) BRK = int2byte(243) IP = int2byte(244) AO = int2byte(245) AYT = int2byte(246) EC = int2byte(247) EL = int2byte(248) GA = int2byte(249) class TelnetProtocolParser(object): """ Parser for the Telnet protocol. Usage:: def data_received(data): print(data) def size_received(rows, columns): print(rows, columns) p = TelnetProtocolParser(data_received, size_received) p.feed(binary_data) """ def __init__(self, data_received_callback, size_received_callback): self.data_received_callback = data_received_callback self.size_received_callback = size_received_callback self._parser = self._parse_coroutine() self._parser.send(None) def received_data(self, data): self.data_received_callback(data) def do_received(self, data): """ Received telnet DO command. """ logger.info('DO %r', data) def dont_received(self, data): """ Received telnet DONT command. """ logger.info('DONT %r', data) def will_received(self, data): """ Received telnet WILL command. """ logger.info('WILL %r', data) def wont_received(self, data): """ Received telnet WONT command. """ logger.info('WONT %r', data) def command_received(self, command, data): if command == DO: self.do_received(data) elif command == DONT: self.dont_received(data) elif command == WILL: self.will_received(data) elif command == WONT: self.wont_received(data) else: logger.info('command received %r %r', command, data) def naws(self, data): """ Received NAWS. (Window dimensions.) """ if len(data) == 4: # NOTE: the first parameter of struct.unpack should be # a 'str' object. Both on Py2/py3. This crashes on OSX # otherwise. columns, rows = struct.unpack(str('!HH'), data) self.size_received_callback(rows, columns) else: logger.warning('Wrong number of NAWS bytes') def negotiate(self, data): """ Got negotiate data. """ command, payload = data[0:1], data[1:] assert isinstance(command, bytes) if command == NAWS: self.naws(payload) else: logger.info('Negotiate (%r got bytes)', len(data)) def _parse_coroutine(self): """ Parser state machine. Every 'yield' expression returns the next byte. """ while True: d = yield if d == int2byte(0): pass # NOP # Go to state escaped. elif d == IAC: d2 = yield if d2 == IAC: self.received_data(d2) # Handle simple commands. elif d2 in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA): self.command_received(d2, None) # Handle IAC-[DO/DONT/WILL/WONT] commands. elif d2 in (DO, DONT, WILL, WONT): d3 = yield self.command_received(d2, d3) # Subnegotiation elif d2 == SB: # Consume everything until next IAC-SE data = [] while True: d3 = yield if d3 == IAC: d4 = yield if d4 == SE: break else: data.append(d4) else: data.append(d3) self.negotiate(b''.join(data)) else: self.received_data(d) def feed(self, data): """ Feed data to the parser. """ assert isinstance(data, binary_type) for b in iterbytes(data): self._parser.send(int2byte(b)) prompt_toolkit-1.0.15/prompt_toolkit/contrib/telnet/log.py0000664000175000017500000000025213136130420025460 0ustar jonathanjonathan00000000000000""" Python logger for the telnet server. """ from __future__ import unicode_literals import logging logger = logging.getLogger(__package__) __all__ = ( 'logger', ) prompt_toolkit-1.0.15/prompt_toolkit/contrib/regular_languages/0000775000175000017500000000000013136335632026536 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/contrib/regular_languages/compiler.py0000664000175000017500000003642113136130420030714 0ustar jonathanjonathan00000000000000r""" Compiler for a regular grammar. Example usage:: # Create and compile grammar. p = compile('add \s+ (?P[^\s]+) \s+ (?P[^\s]+)') # Match input string. m = p.match('add 23 432') # Get variables. m.variables().get('var1') # Returns "23" m.variables().get('var2') # Returns "432" Partial matches are possible:: # Create and compile grammar. p = compile(''' # Operators with two arguments. ((?P[^\s]+) \s+ (?P[^\s]+) \s+ (?P[^\s]+)) | # Operators with only one arguments. ((?P[^\s]+) \s+ (?P[^\s]+)) ''') # Match partial input string. m = p.match_prefix('add 23') # Get variables. (Notice that both operator1 and operator2 contain the # value "add".) This is because our input is incomplete, and we don't know # yet in which rule of the regex we we'll end up. It could also be that # `operator1` and `operator2` have a different autocompleter and we want to # call all possible autocompleters that would result in valid input.) m.variables().get('var1') # Returns "23" m.variables().get('operator1') # Returns "add" m.variables().get('operator2') # Returns "add" """ from __future__ import unicode_literals import re from six.moves import range from .regex_parser import Any, Sequence, Regex, Variable, Repeat, Lookahead from .regex_parser import parse_regex, tokenize_regex __all__ = ( 'compile', ) # Name of the named group in the regex, matching trailing input. # (Trailing input is when the input contains characters after the end of the # expression has been matched.) _INVALID_TRAILING_INPUT = 'invalid_trailing' class _CompiledGrammar(object): """ Compiles a grammar. This will take the parse tree of a regular expression and compile the grammar. :param root_node: :class~`.regex_parser.Node` instance. :param escape_funcs: `dict` mapping variable names to escape callables. :param unescape_funcs: `dict` mapping variable names to unescape callables. """ def __init__(self, root_node, escape_funcs=None, unescape_funcs=None): self.root_node = root_node self.escape_funcs = escape_funcs or {} self.unescape_funcs = unescape_funcs or {} #: Dictionary that will map the redex names to Node instances. self._group_names_to_nodes = {} # Maps regex group names to varnames. counter = [0] def create_group_func(node): name = 'n%s' % counter[0] self._group_names_to_nodes[name] = node.varname counter[0] += 1 return name # Compile regex strings. self._re_pattern = '^%s$' % self._transform(root_node, create_group_func) self._re_prefix_patterns = list(self._transform_prefix(root_node, create_group_func)) # Compile the regex itself. flags = re.DOTALL # Note that we don't need re.MULTILINE! (^ and $ # still represent the start and end of input text.) self._re = re.compile(self._re_pattern, flags) self._re_prefix = [re.compile(t, flags) for t in self._re_prefix_patterns] # We compile one more set of regexes, similar to `_re_prefix`, but accept any trailing # input. This will ensure that we can still highlight the input correctly, even when the # input contains some additional characters at the end that don't match the grammar.) self._re_prefix_with_trailing_input = [ re.compile(r'(?:%s)(?P<%s>.*?)$' % (t.rstrip('$'), _INVALID_TRAILING_INPUT), flags) for t in self._re_prefix_patterns] def escape(self, varname, value): """ Escape `value` to fit in the place of this variable into the grammar. """ f = self.escape_funcs.get(varname) return f(value) if f else value def unescape(self, varname, value): """ Unescape `value`. """ f = self.unescape_funcs.get(varname) return f(value) if f else value @classmethod def _transform(cls, root_node, create_group_func): """ Turn a :class:`Node` object into a regular expression. :param root_node: The :class:`Node` instance for which we generate the grammar. :param create_group_func: A callable which takes a `Node` and returns the next free name for this node. """ def transform(node): # Turn `Any` into an OR. if isinstance(node, Any): return '(?:%s)' % '|'.join(transform(c) for c in node.children) # Concatenate a `Sequence` elif isinstance(node, Sequence): return ''.join(transform(c) for c in node.children) # For Regex and Lookahead nodes, just insert them literally. elif isinstance(node, Regex): return node.regex elif isinstance(node, Lookahead): before = ('(?!' if node.negative else '(=') return before + transform(node.childnode) + ')' # A `Variable` wraps the children into a named group. elif isinstance(node, Variable): return '(?P<%s>%s)' % (create_group_func(node), transform(node.childnode)) # `Repeat`. elif isinstance(node, Repeat): return '(?:%s){%i,%s}%s' % ( transform(node.childnode), node.min_repeat, ('' if node.max_repeat is None else str(node.max_repeat)), ('' if node.greedy else '?') ) else: raise TypeError('Got %r' % (node, )) return transform(root_node) @classmethod def _transform_prefix(cls, root_node, create_group_func): """ Yield all the regular expressions matching a prefix of the grammar defined by the `Node` instance. This can yield multiple expressions, because in the case of on OR operation in the grammar, we can have another outcome depending on which clause would appear first. E.g. "(A|B)C" is not the same as "(B|A)C" because the regex engine is lazy and takes the first match. However, because we the current input is actually a prefix of the grammar which meight not yet contain the data for "C", we need to know both intermediate states, in order to call the appropriate autocompletion for both cases. :param root_node: The :class:`Node` instance for which we generate the grammar. :param create_group_func: A callable which takes a `Node` and returns the next free name for this node. """ def transform(node): # Generate regexes for all permutations of this OR. Each node # should be in front once. if isinstance(node, Any): for c in node.children: for r in transform(c): yield '(?:%s)?' % r # For a sequence. We can either have a match for the sequence # of all the children, or for an exact match of the first X # children, followed by a partial match of the next children. elif isinstance(node, Sequence): for i in range(len(node.children)): a = [cls._transform(c, create_group_func) for c in node.children[:i]] for c in transform(node.children[i]): yield '(?:%s)' % (''.join(a) + c) elif isinstance(node, Regex): yield '(?:%s)?' % node.regex elif isinstance(node, Lookahead): if node.negative: yield '(?!%s)' % cls._transform(node.childnode, create_group_func) else: # Not sure what the correct semantics are in this case. # (Probably it's not worth implementing this.) raise Exception('Positive lookahead not yet supported.') elif isinstance(node, Variable): # (Note that we should not append a '?' here. the 'transform' # method will already recursively do that.) for c in transform(node.childnode): yield '(?P<%s>%s)' % (create_group_func(node), c) elif isinstance(node, Repeat): # If we have a repetition of 8 times. That would mean that the # current input could have for instance 7 times a complete # match, followed by a partial match. prefix = cls._transform(node.childnode, create_group_func) for c in transform(node.childnode): if node.max_repeat: repeat_sign = '{,%i}' % (node.max_repeat - 1) else: repeat_sign = '*' yield '(?:%s)%s%s(?:%s)?' % ( prefix, repeat_sign, ('' if node.greedy else '?'), c) else: raise TypeError('Got %r' % node) for r in transform(root_node): yield '^%s$' % r def match(self, string): """ Match the string with the grammar. Returns a :class:`Match` instance or `None` when the input doesn't match the grammar. :param string: The input string. """ m = self._re.match(string) if m: return Match(string, [(self._re, m)], self._group_names_to_nodes, self.unescape_funcs) def match_prefix(self, string): """ Do a partial match of the string with the grammar. The returned :class:`Match` instance can contain multiple representations of the match. This will never return `None`. If it doesn't match at all, the "trailing input" part will capture all of the input. :param string: The input string. """ # First try to match using `_re_prefix`. If nothing is found, use the patterns that # also accept trailing characters. for patterns in [self._re_prefix, self._re_prefix_with_trailing_input]: matches = [(r, r.match(string)) for r in patterns] matches = [(r, m) for r, m in matches if m] if matches != []: return Match(string, matches, self._group_names_to_nodes, self.unescape_funcs) class Match(object): """ :param string: The input string. :param re_matches: List of (compiled_re_pattern, re_match) tuples. :param group_names_to_nodes: Dictionary mapping all the re group names to the matching Node instances. """ def __init__(self, string, re_matches, group_names_to_nodes, unescape_funcs): self.string = string self._re_matches = re_matches self._group_names_to_nodes = group_names_to_nodes self._unescape_funcs = unescape_funcs def _nodes_to_regs(self): """ Return a list of (varname, reg) tuples. """ def get_tuples(): for r, re_match in self._re_matches: for group_name, group_index in r.groupindex.items(): if group_name != _INVALID_TRAILING_INPUT: reg = re_match.regs[group_index] node = self._group_names_to_nodes[group_name] yield (node, reg) return list(get_tuples()) def _nodes_to_values(self): """ Returns list of list of (Node, string_value) tuples. """ def is_none(slice): return slice[0] == -1 and slice[1] == -1 def get(slice): return self.string[slice[0]:slice[1]] return [(varname, get(slice), slice) for varname, slice in self._nodes_to_regs() if not is_none(slice)] def _unescape(self, varname, value): unwrapper = self._unescape_funcs.get(varname) return unwrapper(value) if unwrapper else value def variables(self): """ Returns :class:`Variables` instance. """ return Variables([(k, self._unescape(k, v), sl) for k, v, sl in self._nodes_to_values()]) def trailing_input(self): """ Get the `MatchVariable` instance, representing trailing input, if there is any. "Trailing input" is input at the end that does not match the grammar anymore, but when this is removed from the end of the input, the input would be a valid string. """ slices = [] # Find all regex group for the name _INVALID_TRAILING_INPUT. for r, re_match in self._re_matches: for group_name, group_index in r.groupindex.items(): if group_name == _INVALID_TRAILING_INPUT: slices.append(re_match.regs[group_index]) # Take the smallest part. (Smaller trailing text means that a larger input has # been matched, so that is better.) if slices: slice = [max(i[0] for i in slices), max(i[1] for i in slices)] value = self.string[slice[0]:slice[1]] return MatchVariable('', value, slice) def end_nodes(self): """ Yields `MatchVariable` instances for all the nodes having their end position at the end of the input string. """ for varname, reg in self._nodes_to_regs(): # If this part goes until the end of the input string. if reg[1] == len(self.string): value = self._unescape(varname, self.string[reg[0]: reg[1]]) yield MatchVariable(varname, value, (reg[0], reg[1])) class Variables(object): def __init__(self, tuples): #: List of (varname, value, slice) tuples. self._tuples = tuples def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, ', '.join('%s=%r' % (k, v) for k, v, _ in self._tuples)) def get(self, key, default=None): items = self.getall(key) return items[0] if items else default def getall(self, key): return [v for k, v, _ in self._tuples if k == key] def __getitem__(self, key): return self.get(key) def __iter__(self): """ Yield `MatchVariable` instances. """ for varname, value, slice in self._tuples: yield MatchVariable(varname, value, slice) class MatchVariable(object): """ Represents a match of a variable in the grammar. :param varname: (string) Name of the variable. :param value: (string) Value of this variable. :param slice: (start, stop) tuple, indicating the position of this variable in the input string. """ def __init__(self, varname, value, slice): self.varname = varname self.value = value self.slice = slice self.start = self.slice[0] self.stop = self.slice[1] def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.varname, self.value) def compile(expression, escape_funcs=None, unescape_funcs=None): """ Compile grammar (given as regex string), returning a `CompiledGrammar` instance. """ return _compile_from_parse_tree( parse_regex(tokenize_regex(expression)), escape_funcs=escape_funcs, unescape_funcs=unescape_funcs) def _compile_from_parse_tree(root_node, *a, **kw): """ Compile grammar (given as parse tree), returning a `CompiledGrammar` instance. """ return _CompiledGrammar(root_node, *a, **kw) prompt_toolkit-1.0.15/prompt_toolkit/contrib/regular_languages/validation.py0000664000175000017500000000401313136130420031224 0ustar jonathanjonathan00000000000000""" Validator for a regular langage. """ from __future__ import unicode_literals from prompt_toolkit.validation import Validator, ValidationError from prompt_toolkit.document import Document from .compiler import _CompiledGrammar __all__ = ( 'GrammarValidator', ) class GrammarValidator(Validator): """ Validator which can be used for validation according to variables in the grammar. Each variable can have its own validator. :param compiled_grammar: `GrammarCompleter` instance. :param validators: `dict` mapping variable names of the grammar to the `Validator` instances to be used for each variable. """ def __init__(self, compiled_grammar, validators): assert isinstance(compiled_grammar, _CompiledGrammar) assert isinstance(validators, dict) self.compiled_grammar = compiled_grammar self.validators = validators def validate(self, document): # Parse input document. # We use `match`, not `match_prefix`, because for validation, we want # the actual, unambiguous interpretation of the input. m = self.compiled_grammar.match(document.text) if m: for v in m.variables(): validator = self.validators.get(v.varname) if validator: # Unescape text. unwrapped_text = self.compiled_grammar.unescape(v.varname, v.value) # Create a document, for the completions API (text/cursor_position) inner_document = Document(unwrapped_text, len(unwrapped_text)) try: validator.validate(inner_document) except ValidationError as e: raise ValidationError( cursor_position=v.start + e.cursor_position, message=e.message) else: raise ValidationError(cursor_position=len(document.text), message='Invalid command') prompt_toolkit-1.0.15/prompt_toolkit/contrib/regular_languages/__init__.py0000664000175000017500000000632613136130420030642 0ustar jonathanjonathan00000000000000r""" Tool for expressing the grammar of an input as a regular language. ================================================================== The grammar for the input of many simple command line interfaces can be expressed by a regular language. Examples are PDB (the Python debugger); a simple (bash-like) shell with "pwd", "cd", "cat" and "ls" commands; arguments that you can pass to an executable; etc. It is possible to use regular expressions for validation and parsing of such a grammar. (More about regular languages: http://en.wikipedia.org/wiki/Regular_language) Example ------- Let's take the pwd/cd/cat/ls example. We want to have a shell that accepts these three commands. "cd" is followed by a quoted directory name and "cat" is followed by a quoted file name. (We allow quotes inside the filename when they're escaped with a backslash.) We could define the grammar using the following regular expression:: grammar = \s* ( pwd | ls | (cd \s+ " ([^"]|\.)+ ") | (cat \s+ " ([^"]|\.)+ ") ) \s* What can we do with this grammar? --------------------------------- - Syntax highlighting: We could use this for instance to give file names different colour. - Parse the result: .. We can extract the file names and commands by using a regular expression with named groups. - Input validation: .. Don't accept anything that does not match this grammar. When combined with a parser, we can also recursively do filename validation (and accept only existing files.) - Autocompletion: .... Each part of the grammar can have its own autocompleter. "cat" has to be completed using file names, while "cd" has to be completed using directory names. How does it work? ----------------- As a user of this library, you have to define the grammar of the input as a regular expression. The parts of this grammar where autocompletion, validation or any other processing is required need to be marked using a regex named group. Like ``(?P...)`` for instance. When the input is processed for validation (for instance), the regex will execute, the named group is captured, and the validator associated with this named group will test the captured string. There is one tricky bit: Ofter we operate on incomplete input (this is by definition the case for autocompletion) and we have to decide for the cursor position in which possible state the grammar it could be and in which way variables could be matched up to that point. To solve this problem, the compiler takes the original regular expression and translates it into a set of other regular expressions which each match prefixes of strings that would match the first expression. (We translate it into multiple expression, because we want to have each possible state the regex could be in -- in case there are several or-clauses with each different completers.) TODO: some examples of: - How to create a highlighter from this grammar. - How to create a validator from this grammar. - How to create an autocompleter from this grammar. - How to create a parser from this grammar. """ from .compiler import compile prompt_toolkit-1.0.15/prompt_toolkit/contrib/regular_languages/completion.py0000664000175000017500000000563713136130420031260 0ustar jonathanjonathan00000000000000""" Completer for a regular grammar. """ from __future__ import unicode_literals from prompt_toolkit.completion import Completer, Completion from prompt_toolkit.document import Document from .compiler import _CompiledGrammar __all__ = ( 'GrammarCompleter', ) class GrammarCompleter(Completer): """ Completer which can be used for autocompletion according to variables in the grammar. Each variable can have a different autocompleter. :param compiled_grammar: `GrammarCompleter` instance. :param completers: `dict` mapping variable names of the grammar to the `Completer` instances to be used for each variable. """ def __init__(self, compiled_grammar, completers): assert isinstance(compiled_grammar, _CompiledGrammar) assert isinstance(completers, dict) self.compiled_grammar = compiled_grammar self.completers = completers def get_completions(self, document, complete_event): m = self.compiled_grammar.match_prefix(document.text_before_cursor) if m: completions = self._remove_duplicates( self._get_completions_for_match(m, complete_event)) for c in completions: yield c def _get_completions_for_match(self, match, complete_event): """ Yield all the possible completions for this input string. (The completer assumes that the cursor position was at the end of the input string.) """ for match_variable in match.end_nodes(): varname = match_variable.varname start = match_variable.start completer = self.completers.get(varname) if completer: text = match_variable.value # Unwrap text. unwrapped_text = self.compiled_grammar.unescape(varname, text) # Create a document, for the completions API (text/cursor_position) document = Document(unwrapped_text, len(unwrapped_text)) # Call completer for completion in completer.get_completions(document, complete_event): new_text = unwrapped_text[:len(text) + completion.start_position] + completion.text # Wrap again. yield Completion( text=self.compiled_grammar.escape(varname, new_text), start_position=start - len(match.string), display=completion.display, display_meta=completion.display_meta) def _remove_duplicates(self, items): """ Remove duplicates, while keeping the order. (Sometimes we have duplicates, because the there several matches of the same grammar, each yielding similar completions.) """ result = [] for i in items: if i not in result: result.append(i) return result prompt_toolkit-1.0.15/prompt_toolkit/contrib/regular_languages/lexer.py0000664000175000017500000000651213136130420030217 0ustar jonathanjonathan00000000000000""" `GrammarLexer` is compatible with Pygments lexers and can be used to highlight the input using a regular grammar with token annotations. """ from __future__ import unicode_literals from prompt_toolkit.document import Document from prompt_toolkit.layout.lexers import Lexer from prompt_toolkit.layout.utils import split_lines from prompt_toolkit.token import Token from .compiler import _CompiledGrammar from six.moves import range __all__ = ( 'GrammarLexer', ) class GrammarLexer(Lexer): """ Lexer which can be used for highlighting of tokens according to variables in the grammar. (It does not actual lexing of the string, but it exposes an API, compatible with the Pygments lexer class.) :param compiled_grammar: Grammar as returned by the `compile()` function. :param lexers: Dictionary mapping variable names of the regular grammar to the lexers that should be used for this part. (This can call other lexers recursively.) If you wish a part of the grammar to just get one token, use a `prompt_toolkit.layout.lexers.SimpleLexer`. """ def __init__(self, compiled_grammar, default_token=None, lexers=None): assert isinstance(compiled_grammar, _CompiledGrammar) assert default_token is None or isinstance(default_token, tuple) assert lexers is None or all(isinstance(v, Lexer) for k, v in lexers.items()) assert lexers is None or isinstance(lexers, dict) self.compiled_grammar = compiled_grammar self.default_token = default_token or Token self.lexers = lexers or {} def _get_tokens(self, cli, text): m = self.compiled_grammar.match_prefix(text) if m: characters = [[self.default_token, c] for c in text] for v in m.variables(): # If we have a `Lexer` instance for this part of the input. # Tokenize recursively and apply tokens. lexer = self.lexers.get(v.varname) if lexer: document = Document(text[v.start:v.stop]) lexer_tokens_for_line = lexer.lex_document(cli, document) lexer_tokens = [] for i in range(len(document.lines)): lexer_tokens.extend(lexer_tokens_for_line(i)) lexer_tokens.append((Token, '\n')) if lexer_tokens: lexer_tokens.pop() i = v.start for t, s in lexer_tokens: for c in s: if characters[i][0] == self.default_token: characters[i][0] = t i += 1 # Highlight trailing input. trailing_input = m.trailing_input() if trailing_input: for i in range(trailing_input.start, trailing_input.stop): characters[i][0] = Token.TrailingInput return characters else: return [(Token, text)] def lex_document(self, cli, document): lines = list(split_lines(self._get_tokens(cli, document.text))) def get_line(lineno): try: return lines[lineno] except IndexError: return [] return get_line prompt_toolkit-1.0.15/prompt_toolkit/contrib/regular_languages/regex_parser.py0000664000175000017500000001614213136130420031566 0ustar jonathanjonathan00000000000000""" Parser for parsing a regular expression. Take a string representing a regular expression and return the root node of its parse tree. usage:: root_node = parse_regex('(hello|world)') Remarks: - The regex parser processes multiline, it ignores all whitespace and supports multiple named groups with the same name and #-style comments. Limitations: - Lookahead is not supported. """ from __future__ import unicode_literals import re __all__ = ( 'Repeat', 'Variable', 'Regex', 'Lookahead', 'tokenize_regex', 'parse_regex', ) class Node(object): """ Base class for all the grammar nodes. (You don't initialize this one.) """ def __add__(self, other_node): return Sequence([self, other_node]) def __or__(self, other_node): return Any([self, other_node]) class Any(Node): """ Union operation (OR operation) between several grammars. You don't initialize this yourself, but it's a result of a "Grammar1 | Grammar2" operation. """ def __init__(self, children): self.children = children def __or__(self, other_node): return Any(self.children + [other_node]) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.children) class Sequence(Node): """ Concatenation operation of several grammars. You don't initialize this yourself, but it's a result of a "Grammar1 + Grammar2" operation. """ def __init__(self, children): self.children = children def __add__(self, other_node): return Sequence(self.children + [other_node]) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.children) class Regex(Node): """ Regular expression. """ def __init__(self, regex): re.compile(regex) # Validate self.regex = regex def __repr__(self): return '%s(/%s/)' % (self.__class__.__name__, self.regex) class Lookahead(Node): """ Lookahead expression. """ def __init__(self, childnode, negative=False): self.childnode = childnode self.negative = negative def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.childnode) class Variable(Node): """ Mark a variable in the regular grammar. This will be translated into a named group. Each variable can have his own completer, validator, etc.. :param childnode: The grammar which is wrapped inside this variable. :param varname: String. """ def __init__(self, childnode, varname=None): self.childnode = childnode self.varname = varname def __repr__(self): return '%s(childnode=%r, varname=%r)' % ( self.__class__.__name__, self.childnode, self.varname) class Repeat(Node): def __init__(self, childnode, min_repeat=0, max_repeat=None, greedy=True): self.childnode = childnode self.min_repeat = min_repeat self.max_repeat = max_repeat self.greedy = greedy def __repr__(self): return '%s(childnode=%r)' % (self.__class__.__name__, self.childnode) def tokenize_regex(input): """ Takes a string, representing a regular expression as input, and tokenizes it. :param input: string, representing a regular expression. :returns: List of tokens. """ # Regular expression for tokenizing other regular expressions. p = re.compile(r'''^( \(\?P\<[a-zA-Z0-9_-]+\> | # Start of named group. \(\?#[^)]*\) | # Comment \(\?= | # Start of lookahead assertion \(\?! | # Start of negative lookahead assertion \(\?<= | # If preceded by. \(\?< | # If not preceded by. \(?: | # Start of group. (non capturing.) \( | # Start of group. \(?[iLmsux] | # Flags. \(?P=[a-zA-Z]+\) | # Back reference to named group \) | # End of group. \{[^{}]*\} | # Repetition \*\? | \+\? | \?\?\ | # Non greedy repetition. \* | \+ | \? | # Repetition \#.*\n | # Comment \\. | # Character group. \[ ( [^\]\\] | \\.)* \] | [^(){}] | . )''', re.VERBOSE) tokens = [] while input: m = p.match(input) if m: token, input = input[:m.end()], input[m.end():] if not token.isspace(): tokens.append(token) else: raise Exception('Could not tokenize input regex.') return tokens def parse_regex(regex_tokens): """ Takes a list of tokens from the tokenizer, and returns a parse tree. """ # We add a closing brace because that represents the final pop of the stack. tokens = [')'] + regex_tokens[::-1] def wrap(lst): """ Turn list into sequence when it contains several items. """ if len(lst) == 1: return lst[0] else: return Sequence(lst) def _parse(): or_list = [] result = [] def wrapped_result(): if or_list == []: return wrap(result) else: or_list.append(result) return Any([wrap(i) for i in or_list]) while tokens: t = tokens.pop() if t.startswith('(?P<'): variable = Variable(_parse(), varname=t[4:-1]) result.append(variable) elif t in ('*', '*?'): greedy = (t == '*') result[-1] = Repeat(result[-1], greedy=greedy) elif t in ('+', '+?'): greedy = (t == '+') result[-1] = Repeat(result[-1], min_repeat=1, greedy=greedy) elif t in ('?', '??'): if result == []: raise Exception('Nothing to repeat.' + repr(tokens)) else: greedy = (t == '?') result[-1] = Repeat(result[-1], min_repeat=0, max_repeat=1, greedy=greedy) elif t == '|': or_list.append(result) result = [] elif t in ('(', '(?:'): result.append(_parse()) elif t == '(?!': result.append(Lookahead(_parse(), negative=True)) elif t == '(?=': result.append(Lookahead(_parse(), negative=False)) elif t == ')': return wrapped_result() elif t.startswith('#'): pass elif t.startswith('{'): # TODO: implement! raise Exception('{}-style repitition not yet supported' % t) elif t.startswith('(?'): raise Exception('%r not supported' % t) elif t.isspace(): pass else: result.append(Regex(t)) raise Exception("Expecting ')' token") result = _parse() if len(tokens) != 0: raise Exception("Unmatched parantheses.") else: return result prompt_toolkit-1.0.15/prompt_toolkit/contrib/__init__.py0000664000175000017500000000000013136130420025132 0ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/contrib/completers/0000775000175000017500000000000013136335632025224 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/contrib/completers/filesystem.py0000664000175000017500000000725713136130420027761 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.completion import Completer, Completion import os __all__ = ( 'PathCompleter', 'ExecutableCompleter', ) class PathCompleter(Completer): """ Complete for Path variables. :param get_paths: Callable which returns a list of directories to look into when the user enters a relative path. :param file_filter: Callable which takes a filename and returns whether this file should show up in the completion. ``None`` when no filtering has to be done. :param min_input_len: Don't do autocompletion when the input string is shorter. """ def __init__(self, only_directories=False, get_paths=None, file_filter=None, min_input_len=0, expanduser=False): assert get_paths is None or callable(get_paths) assert file_filter is None or callable(file_filter) assert isinstance(min_input_len, int) assert isinstance(expanduser, bool) self.only_directories = only_directories self.get_paths = get_paths or (lambda: ['.']) self.file_filter = file_filter or (lambda _: True) self.min_input_len = min_input_len self.expanduser = expanduser def get_completions(self, document, complete_event): text = document.text_before_cursor # Complete only when we have at least the minimal input length, # otherwise, we can too many results and autocompletion will become too # heavy. if len(text) < self.min_input_len: return try: # Do tilde expansion. if self.expanduser: text = os.path.expanduser(text) # Directories where to look. dirname = os.path.dirname(text) if dirname: directories = [os.path.dirname(os.path.join(p, text)) for p in self.get_paths()] else: directories = self.get_paths() # Start of current file. prefix = os.path.basename(text) # Get all filenames. filenames = [] for directory in directories: # Look for matches in this directory. if os.path.isdir(directory): for filename in os.listdir(directory): if filename.startswith(prefix): filenames.append((directory, filename)) # Sort filenames = sorted(filenames, key=lambda k: k[1]) # Yield them. for directory, filename in filenames: completion = filename[len(prefix):] full_name = os.path.join(directory, filename) if os.path.isdir(full_name): # For directories, add a slash to the filename. # (We don't add them to the `completion`. Users can type it # to trigger the autocompletion themself.) filename += '/' elif self.only_directories: continue if not self.file_filter(full_name): continue yield Completion(completion, 0, display=filename) except OSError: pass class ExecutableCompleter(PathCompleter): """ Complete only excutable files in the current path. """ def __init__(self): PathCompleter.__init__( self, only_directories=False, min_input_len=1, get_paths=lambda: os.environ.get('PATH', '').split(os.pathsep), file_filter=lambda name: os.access(name, os.X_OK), expanduser=True), prompt_toolkit-1.0.15/prompt_toolkit/contrib/completers/system.py0000664000175000017500000000357413136130420027117 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter from prompt_toolkit.contrib.regular_languages.compiler import compile from .filesystem import PathCompleter, ExecutableCompleter __all__ = ( 'SystemCompleter', ) class SystemCompleter(GrammarCompleter): """ Completer for system commands. """ def __init__(self): # Compile grammar. g = compile( r""" # First we have an executable. (?P[^\s]+) # Ignore literals in between. ( \s+ ("[^"]*" | '[^']*' | [^'"]+ ) )* \s+ # Filename as parameters. ( (?P[^\s]+) | "(?P[^\s]+)" | '(?P[^\s]+)' ) """, escape_funcs={ 'double_quoted_filename': (lambda string: string.replace('"', '\\"')), 'single_quoted_filename': (lambda string: string.replace("'", "\\'")), }, unescape_funcs={ 'double_quoted_filename': (lambda string: string.replace('\\"', '"')), # XXX: not enterily correct. 'single_quoted_filename': (lambda string: string.replace("\\'", "'")), }) # Create GrammarCompleter super(SystemCompleter, self).__init__( g, { 'executable': ExecutableCompleter(), 'filename': PathCompleter(only_directories=False, expanduser=True), 'double_quoted_filename': PathCompleter(only_directories=False, expanduser=True), 'single_quoted_filename': PathCompleter(only_directories=False, expanduser=True), }) prompt_toolkit-1.0.15/prompt_toolkit/contrib/completers/__init__.py0000664000175000017500000000022313136130420027316 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .filesystem import PathCompleter from .base import WordCompleter from .system import SystemCompleter prompt_toolkit-1.0.15/prompt_toolkit/contrib/completers/base.py0000664000175000017500000000434013136130420026475 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from six import string_types from prompt_toolkit.completion import Completer, Completion __all__ = ( 'WordCompleter', ) class WordCompleter(Completer): """ Simple autocompletion on a list of words. :param words: List of words. :param ignore_case: If True, case-insensitive completion. :param meta_dict: Optional dict mapping words to their meta-information. :param WORD: When True, use WORD characters. :param sentence: When True, don't complete by comparing the word before the cursor, but by comparing all the text before the cursor. In this case, the list of words is just a list of strings, where each string can contain spaces. (Can not be used together with the WORD option.) :param match_middle: When True, match not only the start, but also in the middle of the word. """ def __init__(self, words, ignore_case=False, meta_dict=None, WORD=False, sentence=False, match_middle=False): assert not (WORD and sentence) self.words = list(words) self.ignore_case = ignore_case self.meta_dict = meta_dict or {} self.WORD = WORD self.sentence = sentence self.match_middle = match_middle assert all(isinstance(w, string_types) for w in self.words) def get_completions(self, document, complete_event): # Get word/text before cursor. if self.sentence: word_before_cursor = document.text_before_cursor else: word_before_cursor = document.get_word_before_cursor(WORD=self.WORD) if self.ignore_case: word_before_cursor = word_before_cursor.lower() def word_matches(word): """ True when the word before the cursor matches. """ if self.ignore_case: word = word.lower() if self.match_middle: return word_before_cursor in word else: return word.startswith(word_before_cursor) for a in self.words: if word_matches(a): display_meta = self.meta_dict.get(a, '') yield Completion(a, -len(word_before_cursor), display_meta=display_meta) prompt_toolkit-1.0.15/prompt_toolkit/contrib/validators/0000775000175000017500000000000013136335632025217 5ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/contrib/validators/__init__.py0000664000175000017500000000000013136130420027302 0ustar jonathanjonathan00000000000000prompt_toolkit-1.0.15/prompt_toolkit/contrib/validators/base.py0000664000175000017500000000233013136130420026465 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.validation import Validator, ValidationError from six import string_types class SentenceValidator(Validator): """ Validate input only when it appears in this list of sentences. :param sentences: List of sentences. :param ignore_case: If True, case-insensitive comparisons. """ def __init__(self, sentences, ignore_case=False, error_message='Invalid input', move_cursor_to_end=False): assert all(isinstance(s, string_types) for s in sentences) assert isinstance(ignore_case, bool) assert isinstance(error_message, string_types) self.sentences = list(sentences) self.ignore_case = ignore_case self.error_message = error_message self.move_cursor_to_end = move_cursor_to_end if ignore_case: self.sentences = set([s.lower() for s in self.sentences]) def validate(self, document): if document.text not in self.sentences: if self.move_cursor_to_end: index = len(document.text) else: index = 0 raise ValidationError(cursor_position=index, message=self.error_message) prompt_toolkit-1.0.15/prompt_toolkit/completion.py0000664000175000017500000001313313136130420024117 0ustar jonathanjonathan00000000000000""" """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'Completion', 'Completer', 'CompleteEvent', 'get_common_complete_suffix', ) class Completion(object): """ :param text: The new string that will be inserted into the document. :param start_position: Position relative to the cursor_position where the new text will start. The text will be inserted between the start_position and the original cursor position. :param display: (optional string) If the completion has to be displayed differently in the completion menu. :param display_meta: (Optional string) Meta information about the completion, e.g. the path or source where it's coming from. :param get_display_meta: Lazy `display_meta`. Retrieve meta information only when meta is displayed. """ def __init__(self, text, start_position=0, display=None, display_meta=None, get_display_meta=None): self.text = text self.start_position = start_position self._display_meta = display_meta self._get_display_meta = get_display_meta if display is None: self.display = text else: self.display = display assert self.start_position <= 0 def __repr__(self): if self.display == self.text: return '%s(text=%r, start_position=%r)' % ( self.__class__.__name__, self.text, self.start_position) else: return '%s(text=%r, start_position=%r, display=%r)' % ( self.__class__.__name__, self.text, self.start_position, self.display) def __eq__(self, other): return ( self.text == other.text and self.start_position == other.start_position and self.display == other.display and self.display_meta == other.display_meta) def __hash__(self): return hash((self.text, self.start_position, self.display, self.display_meta)) @property def display_meta(self): # Return meta-text. (This is lazy when using "get_display_meta".) if self._display_meta is not None: return self._display_meta elif self._get_display_meta: self._display_meta = self._get_display_meta() return self._display_meta else: return '' def new_completion_from_position(self, position): """ (Only for internal use!) Get a new completion by splitting this one. Used by `CommandLineInterface` when it needs to have a list of new completions after inserting the common prefix. """ assert isinstance(position, int) and position - self.start_position >= 0 return Completion( text=self.text[position - self.start_position:], display=self.display, display_meta=self._display_meta, get_display_meta=self._get_display_meta) class CompleteEvent(object): """ Event that called the completer. :param text_inserted: When True, it means that completions are requested because of a text insert. (`Buffer.complete_while_typing`.) :param completion_requested: When True, it means that the user explicitely pressed the `Tab` key in order to view the completions. These two flags can be used for instance to implemented a completer that shows some completions when ``Tab`` has been pressed, but not automatically when the user presses a space. (Because of `complete_while_typing`.) """ def __init__(self, text_inserted=False, completion_requested=False): assert not (text_inserted and completion_requested) #: Automatic completion while typing. self.text_inserted = text_inserted #: Used explicitely requested completion by pressing 'tab'. self.completion_requested = completion_requested def __repr__(self): return '%s(text_inserted=%r, completion_requested=%r)' % ( self.__class__.__name__, self.text_inserted, self.completion_requested) class Completer(with_metaclass(ABCMeta, object)): """ Base class for completer implementations. """ @abstractmethod def get_completions(self, document, complete_event): """ Yield :class:`.Completion` instances. :param document: :class:`~prompt_toolkit.document.Document` instance. :param complete_event: :class:`.CompleteEvent` instance. """ while False: yield def get_common_complete_suffix(document, completions): """ Return the common prefix for all completions. """ # Take only completions that don't change the text before the cursor. def doesnt_change_before_cursor(completion): end = completion.text[:-completion.start_position] return document.text_before_cursor.endswith(end) completions2 = [c for c in completions if doesnt_change_before_cursor(c)] # When there is at least one completion that changes the text before the # cursor, don't return any common part. if len(completions2) != len(completions): return '' # Return the common prefix. def get_suffix(completion): return completion.text[-completion.start_position:] return _commonprefix([get_suffix(c) for c in completions2]) def _commonprefix(strings): # Similar to os.path.commonprefix if not strings: return '' else: s1 = min(strings) s2 = max(strings) for i, c in enumerate(s1): if c != s2[i]: return s1[:i] return s1 prompt_toolkit-1.0.15/prompt_toolkit/interface.py0000664000175000017500000012241213136130420023707 0ustar jonathanjonathan00000000000000""" The main `CommandLineInterface` class and logic. """ from __future__ import unicode_literals import functools import os import signal import six import sys import textwrap import threading import time import types import weakref from subprocess import Popen from .application import Application, AbortAction from .buffer import Buffer from .buffer_mapping import BufferMapping from .completion import CompleteEvent, get_common_complete_suffix from .enums import SEARCH_BUFFER from .eventloop.base import EventLoop from .eventloop.callbacks import EventLoopCallbacks from .filters import Condition from .input import StdinInput, Input from .key_binding.input_processor import InputProcessor from .key_binding.input_processor import KeyPress from .key_binding.registry import Registry from .key_binding.vi_state import ViState from .keys import Keys from .output import Output from .renderer import Renderer, print_tokens from .search_state import SearchState from .utils import Event # Following import is required for backwards compatibility. from .buffer import AcceptAction __all__ = ( 'AbortAction', 'CommandLineInterface', ) class CommandLineInterface(object): """ Wrapper around all the other classes, tying everything together. Typical usage:: application = Application(...) cli = CommandLineInterface(application, eventloop) result = cli.run() print(result) :param application: :class:`~prompt_toolkit.application.Application` instance. :param eventloop: The :class:`~prompt_toolkit.eventloop.base.EventLoop` to be used when `run` is called. The easiest way to create an eventloop is by calling :meth:`~prompt_toolkit.shortcuts.create_eventloop`. :param input: :class:`~prompt_toolkit.input.Input` instance. :param output: :class:`~prompt_toolkit.output.Output` instance. (Probably Vt100_Output or Win32Output.) """ def __init__(self, application, eventloop=None, input=None, output=None): assert isinstance(application, Application) assert isinstance(eventloop, EventLoop), 'Passing an eventloop is required.' assert output is None or isinstance(output, Output) assert input is None or isinstance(input, Input) from .shortcuts import create_output self.application = application self.eventloop = eventloop self._is_running = False # Inputs and outputs. self.output = output or create_output() self.input = input or StdinInput(sys.stdin) #: The input buffers. assert isinstance(application.buffers, BufferMapping) self.buffers = application.buffers #: EditingMode.VI or EditingMode.EMACS self.editing_mode = application.editing_mode #: Quoted insert. This flag is set if we go into quoted insert mode. self.quoted_insert = False #: Vi state. (For Vi key bindings.) self.vi_state = ViState() #: The `Renderer` instance. # Make sure that the same stdout is used, when a custom renderer has been passed. self.renderer = Renderer( self.application.style, self.output, use_alternate_screen=application.use_alternate_screen, mouse_support=application.mouse_support) #: Render counter. This one is increased every time the UI is rendered. #: It can be used as a key for caching certain information during one #: rendering. self.render_counter = 0 #: When there is high CPU, postpone the renderering max x seconds. #: '0' means: don't postpone. '.5' means: try to draw at least twice a second. self.max_render_postpone_time = 0 # E.g. .5 # Invalidate flag. When 'True', a repaint has been scheduled. self._invalidated = False #: The `InputProcessor` instance. self.input_processor = InputProcessor(application.key_bindings_registry, weakref.ref(self)) self._async_completers = {} # Map buffer name to completer function. # Pointer to sub CLI. (In chain of CLI instances.) self._sub_cli = None # None or other CommandLineInterface instance. # Call `add_buffer` for each buffer. for name, b in self.buffers.items(): self.add_buffer(name, b) # Events. self.on_buffer_changed = Event(self, application.on_buffer_changed) self.on_initialize = Event(self, application.on_initialize) self.on_input_timeout = Event(self, application.on_input_timeout) self.on_invalidate = Event(self, application.on_invalidate) self.on_render = Event(self, application.on_render) self.on_reset = Event(self, application.on_reset) self.on_start = Event(self, application.on_start) self.on_stop = Event(self, application.on_stop) # Trigger initialize callback. self.reset() self.on_initialize += self.application.on_initialize self.on_initialize.fire() @property def layout(self): return self.application.layout @property def clipboard(self): return self.application.clipboard @property def pre_run_callables(self): return self.application.pre_run_callables def add_buffer(self, name, buffer, focus=False): """ Insert a new buffer. """ assert isinstance(buffer, Buffer) self.buffers[name] = buffer if focus: self.buffers.focus(name) # Create asynchronous completer / auto suggestion. auto_suggest_function = self._create_auto_suggest_function(buffer) completer_function = self._create_async_completer(buffer) self._async_completers[name] = completer_function # Complete/suggest on text insert. def create_on_insert_handler(): """ Wrapper around the asynchronous completer and auto suggestion, that ensures that it's only called while typing if the `complete_while_typing` filter is enabled. """ def on_text_insert(_): # Only complete when "complete_while_typing" is enabled. if buffer.completer and buffer.complete_while_typing(): completer_function() # Call auto_suggest. if buffer.auto_suggest: auto_suggest_function() return on_text_insert buffer.on_text_insert += create_on_insert_handler() def buffer_changed(_): """ When the text in a buffer changes. (A paste event is also a change, but not an insert. So we don't want to do autocompletions in this case, but we want to propagate the on_buffer_changed event.) """ # Trigger on_buffer_changed. self.on_buffer_changed.fire() buffer.on_text_changed += buffer_changed def start_completion(self, buffer_name=None, select_first=False, select_last=False, insert_common_part=False, complete_event=None): """ Start asynchronous autocompletion of this buffer. (This will do nothing if a previous completion was still in progress.) """ buffer_name = buffer_name or self.current_buffer_name completer = self._async_completers.get(buffer_name) if completer: completer(select_first=select_first, select_last=select_last, insert_common_part=insert_common_part, complete_event=CompleteEvent(completion_requested=True)) @property def current_buffer_name(self): """ The name of the current :class:`.Buffer`. (Or `None`.) """ return self.buffers.current_name(self) @property def current_buffer(self): """ The currently focussed :class:`~.Buffer`. (This returns a dummy :class:`.Buffer` when none of the actual buffers has the focus. In this case, it's really not practical to check for `None` values or catch exceptions every time.) """ return self.buffers.current(self) def focus(self, buffer_name): """ Focus the buffer with the given name on the focus stack. """ self.buffers.focus(self, buffer_name) def push_focus(self, buffer_name): """ Push to the focus stack. """ self.buffers.push_focus(self, buffer_name) def pop_focus(self): """ Pop from the focus stack. """ self.buffers.pop_focus(self) @property def terminal_title(self): """ Return the current title to be displayed in the terminal. When this in `None`, the terminal title remains the original. """ result = self.application.get_title() # Make sure that this function returns a unicode object, # and not a byte string. assert result is None or isinstance(result, six.text_type) return result @property def is_searching(self): """ True when we are searching. """ return self.current_buffer_name == SEARCH_BUFFER def reset(self, reset_current_buffer=False): """ Reset everything, for reading the next input. :param reset_current_buffer: XXX: not used anymore. The reason for having this option in the past was when this CommandLineInterface is run multiple times, that we could reset the buffer content from the previous run. This is now handled in the AcceptAction. """ # Notice that we don't reset the buffers. (This happens just before # returning, and when we have multiple buffers, we clearly want the # content in the other buffers to remain unchanged between several # calls of `run`. (And the same is true for the focus stack.) self._exit_flag = False self._abort_flag = False self._return_value = None self.renderer.reset() self.input_processor.reset() self.layout.reset() self.vi_state.reset() # Search new search state. (Does also remember what has to be # highlighted.) self.search_state = SearchState(ignore_case=Condition(lambda: self.is_ignoring_case)) # Trigger reset event. self.on_reset.fire() @property def in_paste_mode(self): """ True when we are in paste mode. """ return self.application.paste_mode(self) @property def is_ignoring_case(self): """ True when we currently ignore casing. """ return self.application.ignore_case(self) def invalidate(self): """ Thread safe way of sending a repaint trigger to the input event loop. """ # Never schedule a second redraw, when a previous one has not yet been # executed. (This should protect against other threads calling # 'invalidate' many times, resulting in 100% CPU.) if self._invalidated: return else: self._invalidated = True # Trigger event. self.on_invalidate.fire() if self.eventloop is not None: def redraw(): self._invalidated = False self._redraw() # Call redraw in the eventloop (thread safe). # Usually with the high priority, in order to make the application # feel responsive, but this can be tuned by changing the value of # `max_render_postpone_time`. if self.max_render_postpone_time: _max_postpone_until = time.time() + self.max_render_postpone_time else: _max_postpone_until = None self.eventloop.call_from_executor( redraw, _max_postpone_until=_max_postpone_until) # Depracated alias for 'invalidate'. request_redraw = invalidate def _redraw(self): """ Render the command line again. (Not thread safe!) (From other threads, or if unsure, use :meth:`.CommandLineInterface.invalidate`.) """ # Only draw when no sub application was started. if self._is_running and self._sub_cli is None: self.render_counter += 1 self.renderer.render(self, self.layout, is_done=self.is_done) # Fire render event. self.on_render.fire() def _on_resize(self): """ When the window size changes, we erase the current output and request again the cursor position. When the CPR answer arrives, the output is drawn again. """ # Erase, request position (when cursor is at the start position) # and redraw again. -- The order is important. self.renderer.erase(leave_alternate_screen=False, erase_title=False) self.renderer.request_absolute_cursor_position() self._redraw() def _load_next_buffer_indexes(self): for buff, index in self._next_buffer_indexes.items(): if buff in self.buffers: self.buffers[buff].working_index = index def _pre_run(self, pre_run=None): " Called during `run`. " if pre_run: pre_run() # Process registered "pre_run_callables" and clear list. for c in self.pre_run_callables: c() del self.pre_run_callables[:] def run(self, reset_current_buffer=False, pre_run=None): """ Read input from the command line. This runs the eventloop until a return value has been set. :param reset_current_buffer: XXX: Not used anymore. :param pre_run: Callable that is called right after the reset has taken place. This allows custom initialisation. """ assert pre_run is None or callable(pre_run) try: self._is_running = True self.on_start.fire() self.reset() # Call pre_run. self._pre_run(pre_run) # Run eventloop in raw mode. with self.input.raw_mode(): self.renderer.request_absolute_cursor_position() self._redraw() self.eventloop.run(self.input, self.create_eventloop_callbacks()) finally: # Clean up renderer. (This will leave the alternate screen, if we use # that.) # If exit/abort haven't been called set, but another exception was # thrown instead for some reason, make sure that we redraw in exit # mode. if not self.is_done: self._exit_flag = True self._redraw() self.renderer.reset() self.on_stop.fire() self._is_running = False # Return result. return self.return_value() try: # The following `run_async` function is compiled at runtime # because it contains syntax which is not supported on older Python # versions. (A 'return' inside a generator.) six.exec_(textwrap.dedent(''' def run_async(self, reset_current_buffer=True, pre_run=None): """ Same as `run`, but this returns a coroutine. This is only available on Python >3.3, with asyncio. """ # Inline import, because it slows down startup when asyncio is not # needed. import asyncio @asyncio.coroutine def run(): assert pre_run is None or callable(pre_run) try: self._is_running = True self.on_start.fire() self.reset() # Call pre_run. self._pre_run(pre_run) with self.input.raw_mode(): self.renderer.request_absolute_cursor_position() self._redraw() yield from self.eventloop.run_as_coroutine( self.input, self.create_eventloop_callbacks()) return self.return_value() finally: if not self.is_done: self._exit_flag = True self._redraw() self.renderer.reset() self.on_stop.fire() self._is_running = False return run() ''')) except SyntaxError: # Python2, or early versions of Python 3. def run_async(self, reset_current_buffer=True, pre_run=None): """ Same as `run`, but this returns a coroutine. This is only available on Python >3.3, with asyncio. """ raise NotImplementedError def run_sub_application(self, application, done_callback=None, erase_when_done=False, _from_application_generator=False): # `erase_when_done` is deprecated, set Application.erase_when_done instead. """ Run a sub :class:`~prompt_toolkit.application.Application`. This will suspend the main application and display the sub application until that one returns a value. The value is returned by calling `done_callback` with the result. The sub application will share the same I/O of the main application. That means, it uses the same input and output channels and it shares the same event loop. .. note:: Technically, it gets another Eventloop instance, but that is only a proxy to our main event loop. The reason is that calling 'stop' --which returns the result of an application when it's done-- is handled differently. """ assert isinstance(application, Application) assert done_callback is None or callable(done_callback) if self._sub_cli is not None: raise RuntimeError('Another sub application started already.') # Erase current application. if not _from_application_generator: self.renderer.erase() # Callback when the sub app is done. def done(): # Redraw sub app in done state. # and reset the renderer. (This reset will also quit the alternate # screen, if the sub application used that.) sub_cli._redraw() if erase_when_done or application.erase_when_done: sub_cli.renderer.erase() sub_cli.renderer.reset() sub_cli._is_running = False # Don't render anymore. self._sub_cli = None # Restore main application. if not _from_application_generator: self.renderer.request_absolute_cursor_position() self._redraw() # Deliver result. if done_callback: done_callback(sub_cli.return_value()) # Create sub CommandLineInterface. sub_cli = CommandLineInterface( application=application, eventloop=_SubApplicationEventLoop(self, done), input=self.input, output=self.output) sub_cli._is_running = True # Allow rendering of sub app. sub_cli._redraw() self._sub_cli = sub_cli def exit(self): """ Set exit. When Control-D has been pressed. """ on_exit = self.application.on_exit self._exit_flag = True self._redraw() if on_exit == AbortAction.RAISE_EXCEPTION: def eof_error(): raise EOFError() self._set_return_callable(eof_error) elif on_exit == AbortAction.RETRY: self.reset() self.renderer.request_absolute_cursor_position() self.current_buffer.reset() elif on_exit == AbortAction.RETURN_NONE: self.set_return_value(None) def abort(self): """ Set abort. When Control-C has been pressed. """ on_abort = self.application.on_abort self._abort_flag = True self._redraw() if on_abort == AbortAction.RAISE_EXCEPTION: def keyboard_interrupt(): raise KeyboardInterrupt() self._set_return_callable(keyboard_interrupt) elif on_abort == AbortAction.RETRY: self.reset() self.renderer.request_absolute_cursor_position() self.current_buffer.reset() elif on_abort == AbortAction.RETURN_NONE: self.set_return_value(None) # Deprecated aliase for exit/abort. set_exit = exit set_abort = abort def set_return_value(self, document): """ Set a return value. The eventloop can retrieve the result it by calling `return_value`. """ self._set_return_callable(lambda: document) self._redraw() # Redraw in "done" state, after the return value has been set. def _set_return_callable(self, value): assert callable(value) self._return_value = value if self.eventloop: self.eventloop.stop() def run_in_terminal(self, func, render_cli_done=False, cooked_mode=True): """ Run function on the terminal above the prompt. What this does is first hiding the prompt, then running this callable (which can safely output to the terminal), and then again rendering the prompt which causes the output of this function to scroll above the prompt. :param func: The callable to execute. :param render_cli_done: When True, render the interface in the 'Done' state first, then execute the function. If False, erase the interface first. :param cooked_mode: When True (the default), switch the input to cooked mode while executing the function. :returns: the result of `func`. """ # Draw interface in 'done' state, or erase. if render_cli_done: self._return_value = True self._redraw() self.renderer.reset() # Make sure to disable mouse mode, etc... else: self.renderer.erase() self._return_value = None # Run system command. if cooked_mode: with self.input.cooked_mode(): result = func() else: result = func() # Redraw interface again. self.renderer.reset() self.renderer.request_absolute_cursor_position() self._redraw() return result def run_application_generator(self, coroutine, render_cli_done=False): """ EXPERIMENTAL Like `run_in_terminal`, but takes a generator that can yield Application instances. Example: def f(): yield Application1(...) print('...') yield Application2(...) cli.run_in_terminal_async(f) The values which are yielded by the given coroutine are supposed to be `Application` instances that run in the current CLI, all other code is supposed to be CPU bound, so except for yielding the applications, there should not be any user interaction or I/O in the given function. """ # Draw interface in 'done' state, or erase. if render_cli_done: self._return_value = True self._redraw() self.renderer.reset() # Make sure to disable mouse mode, etc... else: self.renderer.erase() self._return_value = None # Loop through the generator. g = coroutine() assert isinstance(g, types.GeneratorType) def step_next(send_value=None): " Execute next step of the coroutine." try: # Run until next yield, in cooked mode. with self.input.cooked_mode(): result = g.send(send_value) except StopIteration: done() except: done() raise else: # Process yielded value from coroutine. assert isinstance(result, Application) self.run_sub_application(result, done_callback=step_next, _from_application_generator=True) def done(): # Redraw interface again. self.renderer.reset() self.renderer.request_absolute_cursor_position() self._redraw() # Start processing coroutine. step_next() def run_system_command(self, command): """ Run system command (While hiding the prompt. When finished, all the output will scroll above the prompt.) :param command: Shell command to be executed. """ def wait_for_enter(): """ Create a sub application to wait for the enter key press. This has two advantages over using 'input'/'raw_input': - This will share the same input/output I/O. - This doesn't block the event loop. """ from .shortcuts import create_prompt_application registry = Registry() @registry.add_binding(Keys.ControlJ) @registry.add_binding(Keys.ControlM) def _(event): event.cli.set_return_value(None) application = create_prompt_application( message='Press ENTER to continue...', key_bindings_registry=registry) self.run_sub_application(application) def run(): # Try to use the same input/output file descriptors as the one, # used to run this application. try: input_fd = self.input.fileno() except AttributeError: input_fd = sys.stdin.fileno() try: output_fd = self.output.fileno() except AttributeError: output_fd = sys.stdout.fileno() # Run sub process. # XXX: This will still block the event loop. p = Popen(command, shell=True, stdin=input_fd, stdout=output_fd) p.wait() # Wait for the user to press enter. wait_for_enter() self.run_in_terminal(run) def suspend_to_background(self, suspend_group=True): """ (Not thread safe -- to be called from inside the key bindings.) Suspend process. :param suspend_group: When true, suspend the whole process group. (This is the default, and probably what you want.) """ # Only suspend when the opperating system supports it. # (Not on Windows.) if hasattr(signal, 'SIGTSTP'): def run(): # Send `SIGSTP` to own process. # This will cause it to suspend. # Usually we want the whole process group to be suspended. This # handles the case when input is piped from another process. if suspend_group: os.kill(0, signal.SIGTSTP) else: os.kill(os.getpid(), signal.SIGTSTP) self.run_in_terminal(run) def print_tokens(self, tokens, style=None): """ Print a list of (Token, text) tuples to the output. (When the UI is running, this method has to be called through `run_in_terminal`, otherwise it will destroy the UI.) :param style: Style class to use. Defaults to the active style in the CLI. """ print_tokens(self.output, tokens, style or self.application.style) @property def is_exiting(self): """ ``True`` when the exit flag as been set. """ return self._exit_flag @property def is_aborting(self): """ ``True`` when the abort flag as been set. """ return self._abort_flag @property def is_returning(self): """ ``True`` when a return value has been set. """ return self._return_value is not None def return_value(self): """ Get the return value. Not that this method can throw an exception. """ # Note that it's a method, not a property, because it can throw # exceptions. if self._return_value: return self._return_value() @property def is_done(self): return self.is_exiting or self.is_aborting or self.is_returning def _create_async_completer(self, buffer): """ Create function for asynchronous autocompletion. (Autocomplete in other thread.) """ complete_thread_running = [False] # By ref. def completion_does_nothing(document, completion): """ Return `True` if applying this completion doesn't have any effect. (When it doesn't insert any new text. """ text_before_cursor = document.text_before_cursor replaced_text = text_before_cursor[ len(text_before_cursor) + completion.start_position:] return replaced_text == completion.text def async_completer(select_first=False, select_last=False, insert_common_part=False, complete_event=None): document = buffer.document complete_event = complete_event or CompleteEvent(text_inserted=True) # Don't start two threads at the same time. if complete_thread_running[0]: return # Don't complete when we already have completions. if buffer.complete_state or not buffer.completer: return # Otherwise, get completions in other thread. complete_thread_running[0] = True def run(): completions = list(buffer.completer.get_completions(document, complete_event)) def callback(): """ Set the new complete_state in a safe way. Don't replace an existing complete_state if we had one. (The user could have pressed 'Tab' in the meantime. Also don't set it if the text was changed in the meantime. """ complete_thread_running[0] = False # When there is only one completion, which has nothing to add, ignore it. if (len(completions) == 1 and completion_does_nothing(document, completions[0])): del completions[:] # Set completions if the text was not yet changed. if buffer.text == document.text and \ buffer.cursor_position == document.cursor_position and \ not buffer.complete_state: set_completions = True select_first_anyway = False # When the common part has to be inserted, and there # is a common part. if insert_common_part: common_part = get_common_complete_suffix(document, completions) if common_part: # Insert the common part, update completions. buffer.insert_text(common_part) if len(completions) > 1: # (Don't call `async_completer` again, but # recalculate completions. See: # https://github.com/ipython/ipython/issues/9658) completions[:] = [ c.new_completion_from_position(len(common_part)) for c in completions] else: set_completions = False else: # When we were asked to insert the "common" # prefix, but there was no common suffix but # still exactly one match, then select the # first. (It could be that we have a completion # which does * expansion, like '*.py', with # exactly one match.) if len(completions) == 1: select_first_anyway = True if set_completions: buffer.set_completions( completions=completions, go_to_first=select_first or select_first_anyway, go_to_last=select_last) self.invalidate() elif not buffer.complete_state: # Otherwise, restart thread. async_completer() if self.eventloop: self.eventloop.call_from_executor(callback) self.eventloop.run_in_executor(run) return async_completer def _create_auto_suggest_function(self, buffer): """ Create function for asynchronous auto suggestion. (AutoSuggest in other thread.) """ suggest_thread_running = [False] # By ref. def async_suggestor(): document = buffer.document # Don't start two threads at the same time. if suggest_thread_running[0]: return # Don't suggest when we already have a suggestion. if buffer.suggestion or not buffer.auto_suggest: return # Otherwise, get completions in other thread. suggest_thread_running[0] = True def run(): suggestion = buffer.auto_suggest.get_suggestion(self, buffer, document) def callback(): suggest_thread_running[0] = False # Set suggestion only if the text was not yet changed. if buffer.text == document.text and \ buffer.cursor_position == document.cursor_position: # Set suggestion and redraw interface. buffer.suggestion = suggestion self.invalidate() else: # Otherwise, restart thread. async_suggestor() if self.eventloop: self.eventloop.call_from_executor(callback) self.eventloop.run_in_executor(run) return async_suggestor def stdout_proxy(self, raw=False): """ Create an :class:`_StdoutProxy` class which can be used as a patch for `sys.stdout`. Writing to this proxy will make sure that the text appears above the prompt, and that it doesn't destroy the output from the renderer. :param raw: (`bool`) When True, vt100 terminal escape sequences are not removed/escaped. """ return _StdoutProxy(self, raw=raw) def patch_stdout_context(self, raw=False, patch_stdout=True, patch_stderr=True): """ Return a context manager that will replace ``sys.stdout`` with a proxy that makes sure that all printed text will appear above the prompt, and that it doesn't destroy the output from the renderer. :param patch_stdout: Replace `sys.stdout`. :param patch_stderr: Replace `sys.stderr`. """ return _PatchStdoutContext( self.stdout_proxy(raw=raw), patch_stdout=patch_stdout, patch_stderr=patch_stderr) def create_eventloop_callbacks(self): return _InterfaceEventLoopCallbacks(self) class _InterfaceEventLoopCallbacks(EventLoopCallbacks): """ Callbacks on the :class:`.CommandLineInterface` object, to which an eventloop can talk. """ def __init__(self, cli): assert isinstance(cli, CommandLineInterface) self.cli = cli @property def _active_cli(self): """ Return the active `CommandLineInterface`. """ cli = self.cli # If there is a sub CLI. That one is always active. while cli._sub_cli: cli = cli._sub_cli return cli def terminal_size_changed(self): """ Report terminal size change. This will trigger a redraw. """ self._active_cli._on_resize() def input_timeout(self): cli = self._active_cli cli.on_input_timeout.fire() def feed_key(self, key_press): """ Feed a key press to the CommandLineInterface. """ assert isinstance(key_press, KeyPress) cli = self._active_cli # Feed the key and redraw. # (When the CLI is in 'done' state, it should return to the event loop # as soon as possible. Ignore all key presses beyond this point.) if not cli.is_done: cli.input_processor.feed(key_press) cli.input_processor.process_keys() class _PatchStdoutContext(object): def __init__(self, new_stdout, patch_stdout=True, patch_stderr=True): self.new_stdout = new_stdout self.patch_stdout = patch_stdout self.patch_stderr = patch_stderr def __enter__(self): self.original_stdout = sys.stdout self.original_stderr = sys.stderr if self.patch_stdout: sys.stdout = self.new_stdout if self.patch_stderr: sys.stderr = self.new_stdout def __exit__(self, *a, **kw): if self.patch_stdout: sys.stdout = self.original_stdout if self.patch_stderr: sys.stderr = self.original_stderr class _StdoutProxy(object): """ Proxy for stdout, as returned by :class:`CommandLineInterface.stdout_proxy`. """ def __init__(self, cli, raw=False): assert isinstance(cli, CommandLineInterface) assert isinstance(raw, bool) self._lock = threading.RLock() self._cli = cli self._raw = raw self._buffer = [] self.errors = sys.__stdout__.errors self.encoding = sys.__stdout__.encoding def _do(self, func): if self._cli._is_running: run_in_terminal = functools.partial(self._cli.run_in_terminal, func) self._cli.eventloop.call_from_executor(run_in_terminal) else: func() def _write(self, data): """ Note: print()-statements cause to multiple write calls. (write('line') and write('\n')). Of course we don't want to call `run_in_terminal` for every individual call, because that's too expensive, and as long as the newline hasn't been written, the text itself is again overwritter by the rendering of the input command line. Therefor, we have a little buffer which holds the text until a newline is written to stdout. """ if '\n' in data: # When there is a newline in the data, write everything before the # newline, including the newline itself. before, after = data.rsplit('\n', 1) to_write = self._buffer + [before, '\n'] self._buffer = [after] def run(): for s in to_write: if self._raw: self._cli.output.write_raw(s) else: self._cli.output.write(s) self._do(run) else: # Otherwise, cache in buffer. self._buffer.append(data) def write(self, data): with self._lock: self._write(data) def _flush(self): def run(): for s in self._buffer: if self._raw: self._cli.output.write_raw(s) else: self._cli.output.write(s) self._buffer = [] self._cli.output.flush() self._do(run) def flush(self): """ Flush buffered output. """ with self._lock: self._flush() class _SubApplicationEventLoop(EventLoop): """ Eventloop used by sub applications. A sub application is an `Application` that is "spawned" by a parent application. The parent application is suspended temporarily and the sub application is displayed instead. It doesn't need it's own event loop. The `EventLoopCallbacks` from the parent application are redirected to the sub application. So if the event loop that is run by the parent application detects input, the callbacks will make sure that it's forwarded to the sub application. When the sub application has a return value set, it will terminate by calling the `stop` method of this event loop. This is used to transfer control back to the parent application. """ def __init__(self, cli, stop_callback): assert isinstance(cli, CommandLineInterface) assert callable(stop_callback) self.cli = cli self.stop_callback = stop_callback def stop(self): self.stop_callback() def close(self): pass def run_in_executor(self, callback): self.cli.eventloop.run_in_executor(callback) def call_from_executor(self, callback, _max_postpone_until=None): self.cli.eventloop.call_from_executor( callback, _max_postpone_until=_max_postpone_until) def add_reader(self, fd, callback): self.cli.eventloop.add_reader(fd, callback) def remove_reader(self, fd): self.cli.eventloop.remove_reader(fd) prompt_toolkit-1.0.15/prompt_toolkit/application.py0000664000175000017500000002105313136130420024251 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .buffer import Buffer, AcceptAction from .buffer_mapping import BufferMapping from .clipboard import Clipboard, InMemoryClipboard from .enums import DEFAULT_BUFFER, EditingMode from .filters import CLIFilter, to_cli_filter from .key_binding.bindings.basic import load_basic_bindings from .key_binding.bindings.emacs import load_emacs_bindings from .key_binding.bindings.vi import load_vi_bindings from .key_binding.registry import BaseRegistry from .key_binding.defaults import load_key_bindings from .layout import Window from .layout.containers import Container from .layout.controls import BufferControl from .styles import DEFAULT_STYLE, Style import six __all__ = ( 'AbortAction', 'Application', ) class AbortAction(object): """ Actions to take on an Exit or Abort exception. """ RETRY = 'retry' RAISE_EXCEPTION = 'raise-exception' RETURN_NONE = 'return-none' _all = (RETRY, RAISE_EXCEPTION, RETURN_NONE) class Application(object): """ Application class to be passed to a :class:`~prompt_toolkit.interface.CommandLineInterface`. This contains all customizable logic that is not I/O dependent. (So, what is independent of event loops, input and output.) This way, such an :class:`.Application` can run easily on several :class:`~prompt_toolkit.interface.CommandLineInterface` instances, each with a different I/O backends. that runs for instance over telnet, SSH or any other I/O backend. :param layout: A :class:`~prompt_toolkit.layout.containers.Container` instance. :param buffer: A :class:`~prompt_toolkit.buffer.Buffer` instance for the default buffer. :param initial_focussed_buffer: Name of the buffer that is focussed during start-up. :param key_bindings_registry: :class:`~prompt_toolkit.key_binding.registry.BaseRegistry` instance for the key bindings. :param clipboard: :class:`~prompt_toolkit.clipboard.base.Clipboard` to use. :param on_abort: What to do when Control-C is pressed. :param on_exit: What to do when Control-D is pressed. :param use_alternate_screen: When True, run the application on the alternate screen buffer. :param get_title: Callable that returns the current title to be displayed in the terminal. :param erase_when_done: (bool) Clear the application output when it finishes. :param reverse_vi_search_direction: Normally, in Vi mode, a '/' searches forward and a '?' searches backward. In readline mode, this is usually reversed. Filters: :param mouse_support: (:class:`~prompt_toolkit.filters.CLIFilter` or boolean). When True, enable mouse support. :param paste_mode: :class:`~prompt_toolkit.filters.CLIFilter` or boolean. :param ignore_case: :class:`~prompt_toolkit.filters.CLIFilter` or boolean. :param editing_mode: :class:`~prompt_toolkit.enums.EditingMode`. Callbacks (all of these should accept a :class:`~prompt_toolkit.interface.CommandLineInterface` object as input.) :param on_input_timeout: Called when there is no input for x seconds. (Fired when any eventloop.onInputTimeout is fired.) :param on_start: Called when reading input starts. :param on_stop: Called when reading input ends. :param on_reset: Called during reset. :param on_buffer_changed: Called when the content of a buffer has been changed. :param on_initialize: Called after the :class:`~prompt_toolkit.interface.CommandLineInterface` initializes. :param on_render: Called right after rendering. :param on_invalidate: Called when the UI has been invalidated. """ def __init__(self, layout=None, buffer=None, buffers=None, initial_focussed_buffer=DEFAULT_BUFFER, style=None, key_bindings_registry=None, clipboard=None, on_abort=AbortAction.RAISE_EXCEPTION, on_exit=AbortAction.RAISE_EXCEPTION, use_alternate_screen=False, mouse_support=False, get_title=None, paste_mode=False, ignore_case=False, editing_mode=EditingMode.EMACS, erase_when_done=False, reverse_vi_search_direction=False, on_input_timeout=None, on_start=None, on_stop=None, on_reset=None, on_initialize=None, on_buffer_changed=None, on_render=None, on_invalidate=None): paste_mode = to_cli_filter(paste_mode) ignore_case = to_cli_filter(ignore_case) mouse_support = to_cli_filter(mouse_support) reverse_vi_search_direction = to_cli_filter(reverse_vi_search_direction) assert layout is None or isinstance(layout, Container) assert buffer is None or isinstance(buffer, Buffer) assert buffers is None or isinstance(buffers, (dict, BufferMapping)) assert key_bindings_registry is None or isinstance(key_bindings_registry, BaseRegistry) assert clipboard is None or isinstance(clipboard, Clipboard) assert on_abort in AbortAction._all assert on_exit in AbortAction._all assert isinstance(use_alternate_screen, bool) assert get_title is None or callable(get_title) assert isinstance(paste_mode, CLIFilter) assert isinstance(ignore_case, CLIFilter) assert isinstance(editing_mode, six.string_types) assert on_input_timeout is None or callable(on_input_timeout) assert style is None or isinstance(style, Style) assert isinstance(erase_when_done, bool) assert on_start is None or callable(on_start) assert on_stop is None or callable(on_stop) assert on_reset is None or callable(on_reset) assert on_buffer_changed is None or callable(on_buffer_changed) assert on_initialize is None or callable(on_initialize) assert on_render is None or callable(on_render) assert on_invalidate is None or callable(on_invalidate) self.layout = layout or Window(BufferControl()) # Make sure that the 'buffers' dictionary is a BufferMapping. # NOTE: If no buffer is given, we create a default Buffer, with IGNORE as # default accept_action. This is what makes sense for most users # creating full screen applications. Doing nothing is the obvious # default. Those creating a REPL would use the shortcuts module that # passes in RETURN_DOCUMENT. self.buffer = buffer or Buffer(accept_action=AcceptAction.IGNORE) if not buffers or not isinstance(buffers, BufferMapping): self.buffers = BufferMapping(buffers, initial=initial_focussed_buffer) else: self.buffers = buffers if buffer: self.buffers[DEFAULT_BUFFER] = buffer self.initial_focussed_buffer = initial_focussed_buffer self.style = style or DEFAULT_STYLE if key_bindings_registry is None: key_bindings_registry = load_key_bindings() if get_title is None: get_title = lambda: None self.key_bindings_registry = key_bindings_registry self.clipboard = clipboard or InMemoryClipboard() self.on_abort = on_abort self.on_exit = on_exit self.use_alternate_screen = use_alternate_screen self.mouse_support = mouse_support self.get_title = get_title self.paste_mode = paste_mode self.ignore_case = ignore_case self.editing_mode = editing_mode self.erase_when_done = erase_when_done self.reverse_vi_search_direction = reverse_vi_search_direction def dummy_handler(cli): " Dummy event handler. " self.on_input_timeout = on_input_timeout or dummy_handler self.on_start = on_start or dummy_handler self.on_stop = on_stop or dummy_handler self.on_reset = on_reset or dummy_handler self.on_initialize = on_initialize or dummy_handler self.on_buffer_changed = on_buffer_changed or dummy_handler self.on_render = on_render or dummy_handler self.on_invalidate = on_invalidate or dummy_handler # List of 'extra' functions to execute before a CommandLineInterface.run. # Note: It's important to keep this here, and not in the # CommandLineInterface itself. shortcuts.run_application creates # a new Application instance everytime. (Which is correct, it # could be that we want to detach from one IO backend and attach # the UI on a different backend.) But important is to keep as # much state as possible between runs. self.pre_run_callables = [] prompt_toolkit-1.0.15/prompt_toolkit/shortcuts.py0000664000175000017500000006677313136130420024026 0ustar jonathanjonathan00000000000000""" Shortcuts for retrieving input from the user. If you are using this library for retrieving some input from the user (as a pure Python replacement for GNU readline), probably for 90% of the use cases, the :func:`.prompt` function is all you need. It's the easiest shortcut which does a lot of the underlying work like creating a :class:`~prompt_toolkit.interface.CommandLineInterface` instance for you. When is this not sufficient: - When you want to have more complicated layouts (maybe with sidebars or multiple toolbars. Or visibility of certain user interface controls according to some conditions.) - When you wish to have multiple input buffers. (If you would create an editor like a Vi clone.) - Something else that requires more customization than what is possible with the parameters of `prompt`. In that case, study the code in this file and build your own `CommandLineInterface` instance. It's not too complicated. """ from __future__ import unicode_literals from .buffer import Buffer, AcceptAction from .document import Document from .enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode from .filters import IsDone, HasFocus, RendererHeightIsKnown, to_simple_filter, to_cli_filter, Condition from .history import InMemoryHistory from .interface import CommandLineInterface, Application, AbortAction from .key_binding.defaults import load_key_bindings_for_prompt from .key_binding.registry import Registry from .keys import Keys from .layout import Window, HSplit, FloatContainer, Float from .layout.containers import ConditionalContainer from .layout.controls import BufferControl, TokenListControl from .layout.dimension import LayoutDimension from .layout.lexers import PygmentsLexer from .layout.margins import PromptMargin, ConditionalMargin from .layout.menus import CompletionsMenu, MultiColumnCompletionsMenu from .layout.processors import PasswordProcessor, ConditionalProcessor, AppendAutoSuggestion, HighlightSearchProcessor, HighlightSelectionProcessor, DisplayMultipleCursors from .layout.prompt import DefaultPrompt from .layout.screen import Char from .layout.toolbars import ValidationToolbar, SystemToolbar, ArgToolbar, SearchToolbar from .layout.utils import explode_tokens from .renderer import print_tokens as renderer_print_tokens from .styles import DEFAULT_STYLE, Style, style_from_dict from .token import Token from .utils import is_conemu_ansi, is_windows, DummyContext from six import text_type, exec_, PY2 import os import sys import textwrap import threading import time try: from pygments.lexer import Lexer as pygments_Lexer from pygments.style import Style as pygments_Style except ImportError: pygments_Lexer = None pygments_Style = None if is_windows(): from .terminal.win32_output import Win32Output from .terminal.conemu_output import ConEmuOutput else: from .terminal.vt100_output import Vt100_Output __all__ = ( 'create_eventloop', 'create_output', 'create_prompt_layout', 'create_prompt_application', 'prompt', 'prompt_async', 'create_confirm_application', 'run_application', 'confirm', 'print_tokens', 'clear', ) def create_eventloop(inputhook=None, recognize_win32_paste=True): """ Create and return an :class:`~prompt_toolkit.eventloop.base.EventLoop` instance for a :class:`~prompt_toolkit.interface.CommandLineInterface`. """ if is_windows(): from prompt_toolkit.eventloop.win32 import Win32EventLoop as Loop return Loop(inputhook=inputhook, recognize_paste=recognize_win32_paste) else: from prompt_toolkit.eventloop.posix import PosixEventLoop as Loop return Loop(inputhook=inputhook) def create_output(stdout=None, true_color=False, ansi_colors_only=None): """ Return an :class:`~prompt_toolkit.output.Output` instance for the command line. :param true_color: When True, use 24bit colors instead of 256 colors. (`bool` or :class:`~prompt_toolkit.filters.SimpleFilter`.) :param ansi_colors_only: When True, restrict to 16 ANSI colors only. (`bool` or :class:`~prompt_toolkit.filters.SimpleFilter`.) """ stdout = stdout or sys.__stdout__ true_color = to_simple_filter(true_color) if is_windows(): if is_conemu_ansi(): return ConEmuOutput(stdout) else: return Win32Output(stdout) else: term = os.environ.get('TERM', '') if PY2: term = term.decode('utf-8') return Vt100_Output.from_pty( stdout, true_color=true_color, ansi_colors_only=ansi_colors_only, term=term) def create_asyncio_eventloop(loop=None): """ Returns an asyncio :class:`~prompt_toolkit.eventloop.EventLoop` instance for usage in a :class:`~prompt_toolkit.interface.CommandLineInterface`. It is a wrapper around an asyncio loop. :param loop: The asyncio eventloop (or `None` if the default asyncioloop should be used.) """ # Inline import, to make sure the rest doesn't break on Python 2. (Where # asyncio is not available.) if is_windows(): from prompt_toolkit.eventloop.asyncio_win32 import Win32AsyncioEventLoop as AsyncioEventLoop else: from prompt_toolkit.eventloop.asyncio_posix import PosixAsyncioEventLoop as AsyncioEventLoop return AsyncioEventLoop(loop) def _split_multiline_prompt(get_prompt_tokens): """ Take a `get_prompt_tokens` function and return three new functions instead. One that tells whether this prompt consists of multiple lines; one that returns the tokens to be shown on the lines above the input; and another one with the tokens to be shown at the first line of the input. """ def has_before_tokens(cli): for token, char in get_prompt_tokens(cli): if '\n' in char: return True return False def before(cli): result = [] found_nl = False for token, char in reversed(explode_tokens(get_prompt_tokens(cli))): if found_nl: result.insert(0, (token, char)) elif char == '\n': found_nl = True return result def first_input_line(cli): result = [] for token, char in reversed(explode_tokens(get_prompt_tokens(cli))): if char == '\n': break else: result.insert(0, (token, char)) return result return has_before_tokens, before, first_input_line class _RPrompt(Window): " The prompt that is displayed on the right side of the Window. " def __init__(self, get_tokens=None): get_tokens = get_tokens or (lambda cli: []) super(_RPrompt, self).__init__( TokenListControl(get_tokens, align_right=True)) def create_prompt_layout(message='', lexer=None, is_password=False, reserve_space_for_menu=8, get_prompt_tokens=None, get_continuation_tokens=None, get_rprompt_tokens=None, get_bottom_toolbar_tokens=None, display_completions_in_columns=False, extra_input_processors=None, multiline=False, wrap_lines=True): """ Create a :class:`.Container` instance for a prompt. :param message: Text to be used as prompt. :param lexer: :class:`~prompt_toolkit.layout.lexers.Lexer` to be used for the highlighting. :param is_password: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. When True, display input as '*'. :param reserve_space_for_menu: Space to be reserved for the menu. When >0, make sure that a minimal height is allocated in the terminal, in order to display the completion menu. :param get_prompt_tokens: An optional callable that returns the tokens to be shown in the menu. (To be used instead of a `message`.) :param get_continuation_tokens: An optional callable that takes a CommandLineInterface and width as input and returns a list of (Token, text) tuples to be used for the continuation. :param get_bottom_toolbar_tokens: An optional callable that returns the tokens for a toolbar at the bottom. :param display_completions_in_columns: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. Display the completions in multiple columns. :param multiline: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. When True, prefer a layout that is more adapted for multiline input. Text after newlines is automatically indented, and search/arg input is shown below the input, instead of replacing the prompt. :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. When True (the default), automatically wrap long lines instead of scrolling horizontally. """ assert isinstance(message, text_type), 'Please provide a unicode string.' assert get_bottom_toolbar_tokens is None or callable(get_bottom_toolbar_tokens) assert get_prompt_tokens is None or callable(get_prompt_tokens) assert get_rprompt_tokens is None or callable(get_rprompt_tokens) assert not (message and get_prompt_tokens) display_completions_in_columns = to_cli_filter(display_completions_in_columns) multiline = to_cli_filter(multiline) if get_prompt_tokens is None: get_prompt_tokens = lambda _: [(Token.Prompt, message)] has_before_tokens, get_prompt_tokens_1, get_prompt_tokens_2 = \ _split_multiline_prompt(get_prompt_tokens) # `lexer` is supposed to be a `Lexer` instance. But if a Pygments lexer # class is given, turn it into a PygmentsLexer. (Important for # backwards-compatibility.) try: if pygments_Lexer and issubclass(lexer, pygments_Lexer): lexer = PygmentsLexer(lexer, sync_from_start=True) except TypeError: # Happens when lexer is `None` or an instance of something else. pass # Create processors list. input_processors = [ ConditionalProcessor( # By default, only highlight search when the search # input has the focus. (Note that this doesn't mean # there is no search: the Vi 'n' binding for instance # still allows to jump to the next match in # navigation mode.) HighlightSearchProcessor(preview_search=True), HasFocus(SEARCH_BUFFER)), HighlightSelectionProcessor(), ConditionalProcessor(AppendAutoSuggestion(), HasFocus(DEFAULT_BUFFER) & ~IsDone()), ConditionalProcessor(PasswordProcessor(), is_password), DisplayMultipleCursors(DEFAULT_BUFFER), ] if extra_input_processors: input_processors.extend(extra_input_processors) # Show the prompt before the input (using the DefaultPrompt processor. # This also replaces it with reverse-i-search and 'arg' when required. # (Only for single line mode.) # (DefaultPrompt should always be at the end of the processors.) input_processors.append(ConditionalProcessor( DefaultPrompt(get_prompt_tokens_2), ~multiline)) # Create bottom toolbar. if get_bottom_toolbar_tokens: toolbars = [ConditionalContainer( Window(TokenListControl(get_bottom_toolbar_tokens, default_char=Char(' ', Token.Toolbar)), height=LayoutDimension.exact(1)), filter=~IsDone() & RendererHeightIsKnown())] else: toolbars = [] def get_height(cli): # If there is an autocompletion menu to be shown, make sure that our # layout has at least a minimal height in order to display it. if reserve_space_for_menu and not cli.is_done: buff = cli.current_buffer # Reserve the space, either when there are completions, or when # `complete_while_typing` is true and we expect completions very # soon. if buff.complete_while_typing() or buff.complete_state is not None: return LayoutDimension(min=reserve_space_for_menu) return LayoutDimension() # Create and return Container instance. return HSplit([ # The main input, with completion menus floating on top of it. FloatContainer( HSplit([ ConditionalContainer( Window( TokenListControl(get_prompt_tokens_1), dont_extend_height=True), Condition(has_before_tokens) ), Window( BufferControl( input_processors=input_processors, lexer=lexer, # Enable preview_search, we want to have immediate feedback # in reverse-i-search mode. preview_search=True), get_height=get_height, left_margins=[ # In multiline mode, use the window margin to display # the prompt and continuation tokens. ConditionalMargin( PromptMargin(get_prompt_tokens_2, get_continuation_tokens), filter=multiline ) ], wrap_lines=wrap_lines, ), ]), [ # Completion menus. Float(xcursor=True, ycursor=True, content=CompletionsMenu( max_height=16, scroll_offset=1, extra_filter=HasFocus(DEFAULT_BUFFER) & ~display_completions_in_columns)), Float(xcursor=True, ycursor=True, content=MultiColumnCompletionsMenu( extra_filter=HasFocus(DEFAULT_BUFFER) & display_completions_in_columns, show_meta=True)), # The right prompt. Float(right=0, top=0, hide_when_covering_content=True, content=_RPrompt(get_rprompt_tokens)), ] ), ValidationToolbar(), SystemToolbar(), # In multiline mode, we use two toolbars for 'arg' and 'search'. ConditionalContainer(ArgToolbar(), multiline), ConditionalContainer(SearchToolbar(), multiline), ] + toolbars) def create_prompt_application( message='', multiline=False, wrap_lines=True, is_password=False, vi_mode=False, editing_mode=EditingMode.EMACS, complete_while_typing=True, enable_history_search=False, lexer=None, enable_system_bindings=False, enable_open_in_editor=False, validator=None, completer=None, reserve_space_for_menu=8, auto_suggest=None, style=None, history=None, clipboard=None, get_prompt_tokens=None, get_continuation_tokens=None, get_rprompt_tokens=None, get_bottom_toolbar_tokens=None, display_completions_in_columns=False, get_title=None, mouse_support=False, extra_input_processors=None, key_bindings_registry=None, on_abort=AbortAction.RAISE_EXCEPTION, on_exit=AbortAction.RAISE_EXCEPTION, accept_action=AcceptAction.RETURN_DOCUMENT, erase_when_done=False, default=''): """ Create an :class:`~Application` instance for a prompt. (It is meant to cover 90% of the prompt use cases, where no extreme customization is required. For more complex input, it is required to create a custom :class:`~Application` instance.) :param message: Text to be shown before the prompt. :param mulitiline: Allow multiline input. Pressing enter will insert a newline. (This requires Meta+Enter to accept the input.) :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. When True (the default), automatically wrap long lines instead of scrolling horizontally. :param is_password: Show asterisks instead of the actual typed characters. :param editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``. :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``. :param complete_while_typing: `bool` or :class:`~prompt_toolkit.filters.SimpleFilter`. Enable autocompletion while typing. :param enable_history_search: `bool` or :class:`~prompt_toolkit.filters.SimpleFilter`. Enable up-arrow parting string matching. :param lexer: :class:`~prompt_toolkit.layout.lexers.Lexer` to be used for the syntax highlighting. :param validator: :class:`~prompt_toolkit.validation.Validator` instance for input validation. :param completer: :class:`~prompt_toolkit.completion.Completer` instance for input completion. :param reserve_space_for_menu: Space to be reserved for displaying the menu. (0 means that no space needs to be reserved.) :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest` instance for input suggestions. :param style: :class:`.Style` instance for the color scheme. :param enable_system_bindings: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. Pressing Meta+'!' will show a system prompt. :param enable_open_in_editor: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. Pressing 'v' in Vi mode or C-X C-E in emacs mode will open an external editor. :param history: :class:`~prompt_toolkit.history.History` instance. :param clipboard: :class:`~prompt_toolkit.clipboard.base.Clipboard` instance. (e.g. :class:`~prompt_toolkit.clipboard.in_memory.InMemoryClipboard`) :param get_bottom_toolbar_tokens: Optional callable which takes a :class:`~prompt_toolkit.interface.CommandLineInterface` and returns a list of tokens for the bottom toolbar. :param display_completions_in_columns: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. Display the completions in multiple columns. :param get_title: Callable that returns the title to be displayed in the terminal. :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.CLIFilter` to enable mouse support. :param default: The default text to be shown in the input buffer. (This can be edited by the user.) """ if key_bindings_registry is None: key_bindings_registry = load_key_bindings_for_prompt( enable_system_bindings=enable_system_bindings, enable_open_in_editor=enable_open_in_editor) # Ensure backwards-compatibility, when `vi_mode` is passed. if vi_mode: editing_mode = EditingMode.VI # Make sure that complete_while_typing is disabled when enable_history_search # is enabled. (First convert to SimpleFilter, to avoid doing bitwise operations # on bool objects.) complete_while_typing = to_simple_filter(complete_while_typing) enable_history_search = to_simple_filter(enable_history_search) multiline = to_simple_filter(multiline) complete_while_typing = complete_while_typing & ~enable_history_search # Accept Pygments styles as well for backwards compatibility. try: if pygments_Style and issubclass(style, pygments_Style): style = style_from_dict(style.styles) except TypeError: # Happens when style is `None` or an instance of something else. pass # Create application return Application( layout=create_prompt_layout( message=message, lexer=lexer, is_password=is_password, reserve_space_for_menu=(reserve_space_for_menu if completer is not None else 0), multiline=Condition(lambda cli: multiline()), get_prompt_tokens=get_prompt_tokens, get_continuation_tokens=get_continuation_tokens, get_rprompt_tokens=get_rprompt_tokens, get_bottom_toolbar_tokens=get_bottom_toolbar_tokens, display_completions_in_columns=display_completions_in_columns, extra_input_processors=extra_input_processors, wrap_lines=wrap_lines), buffer=Buffer( enable_history_search=enable_history_search, complete_while_typing=complete_while_typing, is_multiline=multiline, history=(history or InMemoryHistory()), validator=validator, completer=completer, auto_suggest=auto_suggest, accept_action=accept_action, initial_document=Document(default), ), style=style or DEFAULT_STYLE, clipboard=clipboard, key_bindings_registry=key_bindings_registry, get_title=get_title, mouse_support=mouse_support, editing_mode=editing_mode, erase_when_done=erase_when_done, reverse_vi_search_direction=True, on_abort=on_abort, on_exit=on_exit) def prompt(message='', **kwargs): """ Get input from the user and return it. This is a wrapper around a lot of ``prompt_toolkit`` functionality and can be a replacement for `raw_input`. (or GNU readline.) If you want to keep your history across several calls, create one :class:`~prompt_toolkit.history.History` instance and pass it every time. This function accepts many keyword arguments. Except for the following, they are a proxy to the arguments of :func:`.create_prompt_application`. :param patch_stdout: Replace ``sys.stdout`` by a proxy that ensures that print statements from other threads won't destroy the prompt. (They will be printed above the prompt instead.) :param return_asyncio_coroutine: When True, return a asyncio coroutine. (Python >3.3) :param true_color: When True, use 24bit colors instead of 256 colors. :param refresh_interval: (number; in seconds) When given, refresh the UI every so many seconds. """ patch_stdout = kwargs.pop('patch_stdout', False) return_asyncio_coroutine = kwargs.pop('return_asyncio_coroutine', False) true_color = kwargs.pop('true_color', False) refresh_interval = kwargs.pop('refresh_interval', 0) eventloop = kwargs.pop('eventloop', None) application = create_prompt_application(message, **kwargs) return run_application(application, patch_stdout=patch_stdout, return_asyncio_coroutine=return_asyncio_coroutine, true_color=true_color, refresh_interval=refresh_interval, eventloop=eventloop) def run_application( application, patch_stdout=False, return_asyncio_coroutine=False, true_color=False, refresh_interval=0, eventloop=None): """ Run a prompt toolkit application. :param patch_stdout: Replace ``sys.stdout`` by a proxy that ensures that print statements from other threads won't destroy the prompt. (They will be printed above the prompt instead.) :param return_asyncio_coroutine: When True, return a asyncio coroutine. (Python >3.3) :param true_color: When True, use 24bit colors instead of 256 colors. :param refresh_interval: (number; in seconds) When given, refresh the UI every so many seconds. """ assert isinstance(application, Application) if return_asyncio_coroutine: eventloop = create_asyncio_eventloop() else: eventloop = eventloop or create_eventloop() # Create CommandLineInterface. cli = CommandLineInterface( application=application, eventloop=eventloop, output=create_output(true_color=true_color)) # Set up refresh interval. if refresh_interval: done = [False] def start_refresh_loop(cli): def run(): while not done[0]: time.sleep(refresh_interval) cli.request_redraw() t = threading.Thread(target=run) t.daemon = True t.start() def stop_refresh_loop(cli): done[0] = True cli.on_start += start_refresh_loop cli.on_stop += stop_refresh_loop # Replace stdout. patch_context = cli.patch_stdout_context(raw=True) if patch_stdout else DummyContext() # Read input and return it. if return_asyncio_coroutine: # Create an asyncio coroutine and call it. exec_context = {'patch_context': patch_context, 'cli': cli, 'Document': Document} exec_(textwrap.dedent(''' def prompt_coro(): # Inline import, because it slows down startup when asyncio is not # needed. import asyncio @asyncio.coroutine def run(): with patch_context: result = yield from cli.run_async() if isinstance(result, Document): # Backwards-compatibility. return result.text return result return run() '''), exec_context) return exec_context['prompt_coro']() else: try: with patch_context: result = cli.run() if isinstance(result, Document): # Backwards-compatibility. return result.text return result finally: eventloop.close() def prompt_async(message='', **kwargs): """ Similar to :func:`.prompt`, but return an asyncio coroutine instead. """ kwargs['return_asyncio_coroutine'] = True return prompt(message, **kwargs) def create_confirm_application(message): """ Create a confirmation `Application` that returns True/False. """ registry = Registry() @registry.add_binding('y') @registry.add_binding('Y') def _(event): event.cli.buffers[DEFAULT_BUFFER].text = 'y' event.cli.set_return_value(True) @registry.add_binding('n') @registry.add_binding('N') @registry.add_binding(Keys.ControlC) def _(event): event.cli.buffers[DEFAULT_BUFFER].text = 'n' event.cli.set_return_value(False) return create_prompt_application(message, key_bindings_registry=registry) def confirm(message='Confirm (y or n) '): """ Display a confirmation prompt. """ assert isinstance(message, text_type) app = create_confirm_application(message) return run_application(app) def print_tokens(tokens, style=None, true_color=False, file=None): """ Print a list of (Token, text) tuples in the given style to the output. E.g.:: style = style_from_dict({ Token.Hello: '#ff0066', Token.World: '#884444 italic', }) tokens = [ (Token.Hello, 'Hello'), (Token.World, 'World'), ] print_tokens(tokens, style=style) :param tokens: List of ``(Token, text)`` tuples. :param style: :class:`.Style` instance for the color scheme. :param true_color: When True, use 24bit colors instead of 256 colors. :param file: The output file. This can be `sys.stdout` or `sys.stderr`. """ if style is None: style = DEFAULT_STYLE assert isinstance(style, Style) output = create_output(true_color=true_color, stdout=file) renderer_print_tokens(output, tokens, style) def clear(): """ Clear the screen. """ out = create_output() out.erase_screen() out.cursor_goto(0, 0) out.flush() # Deprecated alias for `prompt`. get_input = prompt # Deprecated alias for create_prompt_layout create_default_layout = create_prompt_layout # Deprecated alias for create_prompt_application create_default_application = create_prompt_application prompt_toolkit-1.0.15/prompt_toolkit/mouse_events.py0000664000175000017500000000254413136130420024466 0ustar jonathanjonathan00000000000000""" Mouse events. How it works ------------ The renderer has a 2 dimensional grid of mouse event handlers. (`prompt_toolkit.layout.MouseHandlers`.) When the layout is rendered, the `Window` class will make sure that this grid will also be filled with callbacks. For vt100 terminals, mouse events are received through stdin, just like any other key press. There is a handler among the key bindings that catches these events and forwards them to such a mouse event handler. It passes through the `Window` class where the coordinates are translated from absolute coordinates to coordinates relative to the user control, and there `UIControl.mouse_handler` is called. """ from __future__ import unicode_literals __all__ = ( 'MouseEventType', 'MouseEvent' ) class MouseEventType: MOUSE_UP = 'MOUSE_UP' MOUSE_DOWN = 'MOUSE_DOWN' SCROLL_UP = 'SCROLL_UP' SCROLL_DOWN = 'SCROLL_DOWN' MouseEventTypes = MouseEventType # Deprecated: plural for backwards compatibility. class MouseEvent(object): """ Mouse event, sent to `UIControl.mouse_handler`. :param position: `Point` instance. :param event_type: `MouseEventType`. """ def __init__(self, position, event_type): self.position = position self.event_type = event_type def __repr__(self): return 'MouseEvent(%r, %r)' % (self.position, self.event_type) prompt_toolkit-1.0.15/prompt_toolkit/utils.py0000664000175000017500000001432713136130420023114 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import inspect import os import signal import sys import threading import weakref from wcwidth import wcwidth from six.moves import range __all__ = ( 'Event', 'DummyContext', 'get_cwidth', 'suspend_to_background_supported', 'is_conemu_ansi', 'is_windows', 'in_main_thread', 'take_using_weights', 'test_callable_args', ) class Event(object): """ Simple event to which event handlers can be attached. For instance:: class Cls: def __init__(self): # Define event. The first parameter is the sender. self.event = Event(self) obj = Cls() def handler(sender): pass # Add event handler by using the += operator. obj.event += handler # Fire event. obj.event() """ def __init__(self, sender, handler=None): self.sender = sender self._handlers = [] if handler is not None: self += handler def __call__(self): " Fire event. " for handler in self._handlers: handler(self.sender) def fire(self): " Alias for just calling the event. " self() def __iadd__(self, handler): """ Add another handler to this callback. (Handler should be a callable that takes exactly one parameter: the sender object.) """ # Test handler. assert callable(handler) if not test_callable_args(handler, [None]): raise TypeError("%r doesn't take exactly one argument." % handler) # Add to list of event handlers. self._handlers.append(handler) return self def __isub__(self, handler): """ Remove a handler from this callback. """ self._handlers.remove(handler) return self # Cache of signatures. Improves the performance of `test_callable_args`. _signatures_cache = weakref.WeakKeyDictionary() def test_callable_args(func, args): """ Return True when this function can be called with the given arguments. """ assert isinstance(args, (list, tuple)) signature = getattr(inspect, 'signature', None) if signature is not None: # For Python 3, use inspect.signature. try: sig = _signatures_cache[func] except KeyError: sig = signature(func) _signatures_cache[func] = sig try: sig.bind(*args) except TypeError: return False else: return True else: # For older Python versions, fall back to using getargspec. spec = inspect.getargspec(func) # Drop the 'self' def drop_self(spec): args, varargs, varkw, defaults = spec if args[0:1] == ['self']: args = args[1:] return inspect.ArgSpec(args, varargs, varkw, defaults) spec = drop_self(spec) # When taking *args, always return True. if spec.varargs is not None: return True # Test whether the given amount of args is between the min and max # accepted argument counts. return len(spec.args) - len(spec.defaults or []) <= len(args) <= len(spec.args) class DummyContext(object): """ (contextlib.nested is not available on Py3) """ def __enter__(self): pass def __exit__(self, *a): pass class _CharSizesCache(dict): """ Cache for wcwidth sizes. """ def __missing__(self, string): # Note: We use the `max(0, ...` because some non printable control # characters, like e.g. Ctrl-underscore get a -1 wcwidth value. # It can be possible that these characters end up in the input # text. if len(string) == 1: result = max(0, wcwidth(string)) else: result = sum(max(0, wcwidth(c)) for c in string) # Cache for short strings. # (It's hard to tell what we can consider short...) if len(string) < 256: self[string] = result return result _CHAR_SIZES_CACHE = _CharSizesCache() def get_cwidth(string): """ Return width of a string. Wrapper around ``wcwidth``. """ return _CHAR_SIZES_CACHE[string] def suspend_to_background_supported(): """ Returns `True` when the Python implementation supports suspend-to-background. This is typically `False' on Windows systems. """ return hasattr(signal, 'SIGTSTP') def is_windows(): """ True when we are using Windows. """ return sys.platform.startswith('win') # E.g. 'win32', not 'darwin' or 'linux2' def is_conemu_ansi(): """ True when the ConEmu Windows console is used. """ return is_windows() and os.environ.get('ConEmuANSI', 'OFF') == 'ON' def in_main_thread(): """ True when the current thread is the main thread. """ return threading.current_thread().__class__.__name__ == '_MainThread' def take_using_weights(items, weights): """ Generator that keeps yielding items from the items list, in proportion to their weight. For instance:: # Getting the first 70 items from this generator should have yielded 10 # times A, 20 times B and 40 times C, all distributed equally.. take_using_weights(['A', 'B', 'C'], [5, 10, 20]) :param items: List of items to take from. :param weights: Integers representing the weight. (Numbers have to be integers, not floats.) """ assert isinstance(items, list) assert isinstance(weights, list) assert all(isinstance(i, int) for i in weights) assert len(items) == len(weights) assert len(items) > 0 already_taken = [0 for i in items] item_count = len(items) max_weight = max(weights) i = 0 while True: # Each iteration of this loop, we fill up until by (total_weight/max_weight). adding = True while adding: adding = False for item_i, item, weight in zip(range(item_count), items, weights): if already_taken[item_i] < i * weight / float(max_weight): yield item already_taken[item_i] += 1 adding = True i += 1 prompt_toolkit-1.0.15/prompt_toolkit/cache.py0000664000175000017500000000651013136130420023012 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from collections import deque from functools import wraps __all__ = ( 'SimpleCache', 'FastDictCache', 'memoized', ) class SimpleCache(object): """ Very simple cache that discards the oldest item when the cache size is exceeded. :param maxsize: Maximum size of the cache. (Don't make it too big.) """ def __init__(self, maxsize=8): assert isinstance(maxsize, int) and maxsize > 0 self._data = {} self._keys = deque() self.maxsize = maxsize def get(self, key, getter_func): """ Get object from the cache. If not found, call `getter_func` to resolve it, and put that on the top of the cache instead. """ # Look in cache first. try: return self._data[key] except KeyError: # Not found? Get it. value = getter_func() self._data[key] = value self._keys.append(key) # Remove the oldest key when the size is exceeded. if len(self._data) > self.maxsize: key_to_remove = self._keys.popleft() if key_to_remove in self._data: del self._data[key_to_remove] return value def clear(self): " Clear cache. " self._data = {} self._keys = deque() class FastDictCache(dict): """ Fast, lightweight cache which keeps at most `size` items. It will discard the oldest items in the cache first. The cache is a dictionary, which doesn't keep track of access counts. It is perfect to cache little immutable objects which are not expensive to create, but where a dictionary lookup is still much faster than an object instantiation. :param get_value: Callable that's called in case of a missing key. """ # NOTE: This cache is used to cache `prompt_toolkit.layout.screen.Char` and # `prompt_toolkit.Document`. Make sure to keep this really lightweight. # Accessing the cache should stay faster than instantiating new # objects. # (Dictionary lookups are really fast.) # SimpleCache is still required for cases where the cache key is not # the same as the arguments given to the function that creates the # value.) def __init__(self, get_value=None, size=1000000): assert callable(get_value) assert isinstance(size, int) and size > 0 self._keys = deque() self.get_value = get_value self.size = size def __missing__(self, key): # Remove the oldest key when the size is exceeded. if len(self) > self.size: key_to_remove = self._keys.popleft() if key_to_remove in self: del self[key_to_remove] result = self.get_value(*key) self[key] = result self._keys.append(key) return result def memoized(maxsize=1024): """ Momoization decorator for immutable classes and pure functions. """ cache = SimpleCache(maxsize=maxsize) def decorator(obj): @wraps(obj) def new_callable(*a, **kw): def create_new(): return obj(*a, **kw) key = (a, tuple(kw.items())) return cache.get(key, create_new) return new_callable return decorator prompt_toolkit-1.0.15/LICENSE0000664000175000017500000000272513136130420017320 0ustar jonathanjonathan00000000000000Copyright (c) 2014, Jonathan Slenders All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the {organization} nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. prompt_toolkit-1.0.15/setup.cfg0000664000175000017500000000004613136335632020142 0ustar jonathanjonathan00000000000000[egg_info] tag_build = tag_date = 0 prompt_toolkit-1.0.15/README.rst0000664000175000017500000001545613136130420020007 0ustar jonathanjonathan00000000000000Python Prompt Toolkit ===================== |Build Status| |PyPI| ``prompt_toolkit`` is a library for building powerful interactive command lines and terminal applications in Python. Read the `documentation on readthedocs `_. Ptpython ******** `ptpython `_ is an interactive Python Shell, build on top of prompt_toolkit. .. image :: https://github.com/jonathanslenders/python-prompt-toolkit/raw/master/docs/images/ptpython.png prompt_toolkit features *********************** ``prompt_toolkit`` could be a replacement for `GNU readline `_, but it can be much more than that. Some features: - Pure Python. - Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.) - Multi-line input editing. - Advanced code completion. - Both Emacs and Vi key bindings. (Similar to readline.) - Even some advanced Vi functionality, like named registers and digraphs. - Reverse and forward incremental search. - Runs on all Python versions from 2.6 up to 3.5. - Works well with Unicode double width characters. (Chinese input.) - Selecting text for copy/paste. (Both Emacs and Vi style.) - Support for `bracketed paste `_. - Mouse support for cursor positioning and scrolling. - Auto suggestions. (Like `fish shell `_.) - Multiple input buffers. - No global state. - Lightweight, the only dependencies are Pygments, six and wcwidth. - Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems. - And much more... Feel free to create tickets for bugs and feature requests, and create pull requests if you have nice patches that you would like to share with others. About Windows support ********************* ``prompt_toolkit`` is cross platform, and everything that you build on top should run fine on both Unix and Windows systems. On Windows, it uses a different event loop (``WaitForMultipleObjects`` instead of ``select``), and another input and output system. (Win32 APIs instead of pseudo-terminals and VT100.) It's worth noting that the implementation is a "best effort of what is possible". Both Unix and Windows terminals have their limitations. But in general, the Unix experience will still be a little better. For Windows, it's recommended to use either `cmder `_ or `conemu `_. Installation ************ :: pip install prompt_toolkit For Conda, do: :: conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit Getting started *************** The most simple example of the library would look like this: .. code:: python from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ') print('You said: %s' % answer) For more complex examples, have a look in the ``examples`` directory. All examples are chosen to demonstrate only one thing. Also, don't be afraid to look at the source code. The implementation of the ``prompt`` function could be a good start. Note for Python 2: all strings are expected to be unicode strings. So, either put a small ``u`` in front of every string or put ``from __future__ import unicode_literals`` at the start of the above example. Projects using prompt_toolkit ***************************** Shells: - `ptpython `_: Python REPL - `ptpdb `_: Python debugger (pdb replacement) - `pgcli `_: Postgres client. - `mycli `_: MySql client. - `wharfee `_: A Docker command line. - `xonsh `_: A Python-ish, BASHwards-compatible shell. - `saws `_: A Supercharged AWS Command Line Interface. - `cycli `_: A Command Line Interface for Cypher. - `crash `_: Crate command line client. - `vcli `_: Vertica client. - `aws-shell `_: An integrated shell for working with the AWS CLI. - `softlayer-python `_: A command-line interface to manage various SoftLayer products and services. - `ipython `_: The IPython REPL - `click-repl `_: Subcommand REPL for click apps. - `haxor-news `_: A Hacker News CLI. - `gitsome `_: A Git/Shell Autocompleter with GitHub Integration. - `http-prompt `_: An interactive command-line HTTP client. - `coconut `_: Functional programming in Python. - `Ergonomica `_: A Bash alternative written in Python. - `Kube-shell `_: Kubernetes shell: An integrated shell for working with the Kubernetes CLI Full screen applications: - `pymux `_: A terminal multiplexer (like tmux) in pure Python. - `pyvim `_: A Vim clone in pure Python. (Want your own project to be listed here? Please create a GitHub issue.) Philosophy ********** The source code of ``prompt_toolkit`` should be readable, concise and efficient. We prefer short functions focussing each on one task and for which the input and output types are clearly specified. We mostly prefer composition over inheritance, because inheritance can result in too much functionality in the same object. We prefer immutable objects where possible (objects don't change after initialisation). Reusability is important. We absolutely refrain from having a changing global state, it should be possible to have multiple independent instances of the same code in the same process. The architecture should be layered: the lower levels operate on primitive operations and data structures giving -- when correctly combined -- all the possible flexibility; while at the higher level, there should be a simpler API, ready-to-use and sufficient for most use cases. Thinking about algorithms and efficiency is important, but avoid premature optimization. Special thanks to ***************** - `Pygments `_: Syntax highlighter. - `wcwidth `_: Determine columns needed for a wide characters. .. |Build Status| image:: https://api.travis-ci.org/jonathanslenders/python-prompt-toolkit.svg?branch=master :target: https://travis-ci.org/jonathanslenders/python-prompt-toolkit# .. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg :target: https://pypi.python.org/pypi/prompt-toolkit/ :alt: Latest Version