From 8e256bd37373f98580ba1ef1d3fcfd7976802238 2018-06-07 20:12:28 From: Jonathan Slenders Date: 2018-06-07 20:12:28 Subject: [PATCH] Upgrade to prompt_toolkit 2.0 --- diff --git a/IPython/terminal/debugger.py b/IPython/terminal/debugger.py index fa8753c..c6cf248 100644 --- a/IPython/terminal/debugger.py +++ b/IPython/terminal/debugger.py @@ -8,15 +8,14 @@ from .ptutils import IPythonPTCompleter from .shortcuts import suspend_to_bg, cursor_in_leading_ws from prompt_toolkit.enums import DEFAULT_BUFFER -from prompt_toolkit.filters import (Condition, HasFocus, HasSelection, - ViInsertMode, EmacsInsertMode) -from prompt_toolkit.keys import Keys -from prompt_toolkit.key_binding.manager import KeyBindingManager +from prompt_toolkit.filters import (Condition, has_focus, has_selection, + vi_insert_mode, emacs_insert_mode) +from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline -from prompt_toolkit.token import Token -from prompt_toolkit.shortcuts import create_prompt_application -from prompt_toolkit.interface import CommandLineInterface +from pygments.token import Token +from prompt_toolkit.shortcuts.prompt import PromptSession from prompt_toolkit.enums import EditingMode +from prompt_toolkit.formatted_text import PygmentsTokens class TerminalPdb(Pdb): @@ -26,46 +25,40 @@ class TerminalPdb(Pdb): self.pt_init() def pt_init(self): - def get_prompt_tokens(cli): + def get_prompt_tokens(): return [(Token.Prompt, self.prompt)] - def patch_stdout(**kwargs): - return self.pt_cli.patch_stdout_context(**kwargs) - if self._ptcomp is None: compl = IPCompleter(shell=self.shell, namespace={}, global_namespace={}, parent=self.shell, ) - self._ptcomp = IPythonPTCompleter(compl, patch_stdout=patch_stdout) + self._ptcomp = IPythonPTCompleter(compl) - kbmanager = KeyBindingManager.for_prompt() - supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP')) - kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend - )(suspend_to_bg) + kb = KeyBindings() + supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP')) + kb.add('c-z', filter=supports_suspend)(suspend_to_bg) if self.shell.display_completions == 'readlinelike': - kbmanager.registry.add_binding(Keys.ControlI, - filter=(HasFocus(DEFAULT_BUFFER) - & ~HasSelection() - & ViInsertMode() | EmacsInsertMode() - & ~cursor_in_leading_ws - ))(display_completions_like_readline) - multicolumn = (self.shell.display_completions == 'multicolumn') - - self._pt_app = create_prompt_application( + kb.add('tab', filter=(has_focus(DEFAULT_BUFFER) + & ~has_selection + & vi_insert_mode | emacs_insert_mode + & ~cursor_in_leading_ws + ))(display_completions_like_readline) + + self.pt_app = PromptSession( + message=(lambda: PygmentsTokens(get_prompt_tokens())), editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()), - key_bindings_registry=kbmanager.registry, + key_bindings=kb, history=self.shell.debugger_history, - completer= self._ptcomp, + completer=self._ptcomp, enable_history_search=True, mouse_support=self.shell.mouse_support, - get_prompt_tokens=get_prompt_tokens, - display_completions_in_columns=multicolumn, - style=self.shell.style + complete_style=self.shell.pt_complete_style, + style=self.shell.style, + inputhook=self.shell.inputhook, ) - self.pt_cli = CommandLineInterface(self._pt_app, eventloop=self.shell._eventloop) def cmdloop(self, intro=None): """Repeatedly issue a prompt, accept input, parse an initial prefix @@ -92,7 +85,7 @@ class TerminalPdb(Pdb): self._ptcomp.ipy_completer.namespace = self.curframe_locals self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals try: - line = self.pt_cli.run(reset_current_buffer=True).text + line = self.pt_app.prompt() # reset_current_buffer=True) except EOFError: line = 'EOF' line = self.precmd(line) diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index fcad8ed..d319b5a 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -18,12 +18,15 @@ from traitlets import ( from prompt_toolkit.document import Document from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode from prompt_toolkit.filters import (HasFocus, Condition, IsDone) +from prompt_toolkit.formatted_text import PygmentsTokens from prompt_toolkit.history import InMemoryHistory -from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output -from prompt_toolkit.interface import CommandLineInterface -from prompt_toolkit.key_binding.manager import KeyBindingManager +from prompt_toolkit.shortcuts import PromptSession, CompleteStyle +from prompt_toolkit.output.defaults import create_output +from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor -from prompt_toolkit.styles import PygmentsStyle, DynamicStyle +from prompt_toolkit.patch_stdout import patch_stdout +from prompt_toolkit.styles import DynamicStyle, merge_styles +from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict from pygments.styles import get_style_by_name from pygments.style import Style @@ -34,7 +37,7 @@ from .magics import TerminalMagics from .pt_inputhooks import get_inputhook_name_and_func from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook from .ptutils import IPythonPTCompleter, IPythonPTLexer -from .shortcuts import register_ipython_shortcuts +from .shortcuts import create_ipython_shortcuts DISPLAY_BANNER_DEPRECATED = object() @@ -91,12 +94,11 @@ class TerminalInteractiveShell(InteractiveShell): 'to reserve for the completion menu' ).tag(config=True) - def _space_for_menu_changed(self, old, new): - self._update_layout() +# def _space_for_menu_changed(self, old, new): +# self._update_layout() - pt_cli = None + pt_app = None debugger_history = None - _pt_app = None simple_prompt = Bool(_use_simple_prompt, help="""Use `raw_input` for the REPL, without completion and prompt colors. @@ -167,9 +169,9 @@ class TerminalInteractiveShell(InteractiveShell): def _prompts_default(self): return self.prompts_class(self) - @observe('prompts') - def _(self, change): - self._update_layout() +# @observe('prompts') +# def _(self, change): +# self._update_layout() @default('displayhook_class') def _displayhook_class_default(self): @@ -243,10 +245,7 @@ class TerminalInteractiveShell(InteractiveShell): return # Set up keyboard shortcuts - kbmanager = KeyBindingManager.for_prompt( - enable_open_in_editor=self.extra_open_editor_shortcuts, - ) - register_ipython_shortcuts(kbmanager.registry, self) + key_bindings = create_ipython_shortcuts(self) # Pre-populate history from IPython's history database history = InMemoryHistory() @@ -256,7 +255,7 @@ class TerminalInteractiveShell(InteractiveShell): # Ignore blank lines and consecutive duplicates cell = cell.rstrip() if cell and (cell != last_cell): - history.append(cell) + history.append_string(cell) last_cell = cell self._style = self._make_style_from_name_or_cls(self.highlighting_style) @@ -264,24 +263,18 @@ class TerminalInteractiveShell(InteractiveShell): editing_mode = getattr(EditingMode, self.editing_mode.upper()) - def patch_stdout(**kwargs): - return self.pt_cli.patch_stdout_context(**kwargs) - - self._pt_app = create_prompt_application( + self.pt_app = PromptSession( editing_mode=editing_mode, - key_bindings_registry=kbmanager.registry, + key_bindings=key_bindings, history=history, - completer=IPythonPTCompleter(shell=self, - patch_stdout=patch_stdout), - enable_history_search=self.enable_history_search, + completer=IPythonPTCompleter(shell=self), + enable_history_search = self.enable_history_search, style=self.style, + include_default_pygments_style=False, mouse_support=self.mouse_support, - **self._layout_options() - ) - self._eventloop = create_eventloop(self.inputhook) - self.pt_cli = CommandLineInterface( - self._pt_app, eventloop=self._eventloop, - output=create_output(true_color=self.true_color)) + enable_open_in_editor=self.extra_open_editor_shortcuts, + color_depth=(ColorDepth.TRUE_COLOR if self.true_color else None), + **self._extra_prompt_options()) def _make_style_from_name_or_cls(self, name_or_cls): """ @@ -342,44 +335,60 @@ class TerminalInteractiveShell(InteractiveShell): Token.OutPromptNum: '#ff0000 bold', } style_overrides.update(self.highlighting_style_overrides) - style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls, - style_dict=style_overrides) + style = merge_styles([ + style_from_pygments_cls(style_cls), + style_from_pygments_dict(style_overrides), + ]) return style - def _layout_options(self): + @property + def pt_complete_style(self): + return { + 'multicolumn': CompleteStyle.MULTI_COLUMN, + 'column': CompleteStyle.COLUMN, + 'readlinelike': CompleteStyle.READLINE_LIKE, + }[self.display_completions], + + def _extra_prompt_options(self): """ Return the current layout option for the current Terminal InteractiveShell """ + def get_message(): + return PygmentsTokens(self.prompts.in_prompt_tokens()) + return { + 'complete_in_thread': False, 'lexer':IPythonPTLexer(), 'reserve_space_for_menu':self.space_for_menu, - 'get_prompt_tokens':self.prompts.in_prompt_tokens, - 'get_continuation_tokens':self.prompts.continuation_prompt_tokens, - 'multiline':True, - 'display_completions_in_columns': (self.display_completions == 'multicolumn'), + 'message': get_message, + 'prompt_continuation': ( + lambda width, lineno, is_soft_wrap: + PygmentsTokens(self.prompts.continuation_prompt_tokens(width))), + 'multiline': True, + 'complete_style': self.pt_complete_style, # Highlight matching brackets, but only when this setting is # enabled, and only when the DEFAULT_BUFFER has the focus. - 'extra_input_processors': [ConditionalProcessor( + 'input_processors': [ConditionalProcessor( processor=HighlightMatchingBracketProcessor(chars='[](){}'), filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & - Condition(lambda cli: self.highlight_matching_brackets))], + Condition(lambda: self.highlight_matching_brackets))], } - def _update_layout(self): - """ - Ask for a re computation of the application layout, if for example , - some configuration options have changed. - """ - if self._pt_app: - self._pt_app.layout = create_prompt_layout(**self._layout_options()) - def prompt_for_code(self): - with self.pt_cli.patch_stdout_context(raw=True): - document = self.pt_cli.run( - pre_run=self.pre_prompt, reset_current_buffer=True) - return document.text + if self.rl_next_input: + default = self.rl_next_input + self.rl_next_input = None + else: + default = '' + + with patch_stdout(raw=True): + text = self.pt_app.prompt( + default=default, +# pre_run=self.pre_prompt,# reset_current_buffer=True, + **self._extra_prompt_options()) + return text def enable_win_unicode_console(self): if sys.version_info >= (3, 6): @@ -439,22 +448,6 @@ class TerminalInteractiveShell(InteractiveShell): rl_next_input = None - def pre_prompt(self): - if self.rl_next_input: - # We can't set the buffer here, because it will be reset just after - # this. Adding a callable to pre_run_callables does what we need - # after the buffer is reset. - s = self.rl_next_input - def set_doc(): - self.pt_cli.application.buffer.document = Document(s) - if hasattr(self.pt_cli, 'pre_run_callables'): - self.pt_cli.pre_run_callables.append(set_doc) - else: - # Older version of prompt_toolkit; it's OK to set the document - # directly here. - set_doc() - self.rl_next_input = None - def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): if display_banner is not DISPLAY_BANNER_DEPRECATED: @@ -517,8 +510,8 @@ class TerminalInteractiveShell(InteractiveShell): return tokens = self.prompts.rewrite_prompt_tokens() - if self.pt_cli: - self.pt_cli.print_tokens(tokens) + if self.pt_app: + self.pt_app.print_tokens(tokens) # XXX print(cmd) else: prompt = ''.join(s for t, s in tokens) @@ -533,7 +526,7 @@ class TerminalInteractiveShell(InteractiveShell): elif self._prompts_before: self.prompts = self._prompts_before self._prompts_before = None - self._update_layout() +# self._update_layout() InteractiveShellABC.register(TerminalInteractiveShell) diff --git a/IPython/terminal/prompts.py b/IPython/terminal/prompts.py index 2208852..443a3a0 100644 --- a/IPython/terminal/prompts.py +++ b/IPython/terminal/prompts.py @@ -5,13 +5,15 @@ import sys from IPython.core.displayhook import DisplayHook -from prompt_toolkit.layout.utils import token_list_width +from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens +from prompt_toolkit.shortcuts import print_formatted_text + class Prompts(object): def __init__(self, shell): self.shell = shell - def in_prompt_tokens(self, cli=None): + def in_prompt_tokens(self): return [ (Token.Prompt, 'In ['), (Token.PromptNum, str(self.shell.execution_count)), @@ -19,9 +21,9 @@ class Prompts(object): ] def _width(self): - return token_list_width(self.in_prompt_tokens()) + return fragment_list_width(self.in_prompt_tokens()) - def continuation_prompt_tokens(self, cli=None, width=None): + def continuation_prompt_tokens(self, width=None): if width is None: width = self._width() return [ @@ -42,12 +44,12 @@ class Prompts(object): ] class ClassicPrompts(Prompts): - def in_prompt_tokens(self, cli=None): + def in_prompt_tokens(self): return [ (Token.Prompt, '>>> '), ] - def continuation_prompt_tokens(self, cli=None, width=None): + def continuation_prompt_tokens(self, width=None): return [ (Token.Prompt, '... ') ] @@ -73,7 +75,8 @@ class RichPromptDisplayHook(DisplayHook): # Ask for a newline before multiline output self.prompt_end_newline = False - if self.shell.pt_cli: - self.shell.pt_cli.print_tokens(tokens) + if self.shell.pt_app: + print_formatted_text( + PygmentsTokens(tokens), style=self.shell.pt_app.app.style) else: sys.stdout.write(prompt_txt) diff --git a/IPython/terminal/ptutils.py b/IPython/terminal/ptutils.py index 94875c4..f736752 100644 --- a/IPython/terminal/ptutils.py +++ b/IPython/terminal/ptutils.py @@ -14,8 +14,9 @@ from IPython.core.completer import ( provisionalcompleter, cursor_to_position, _deduplicate_completions) from prompt_toolkit.completion import Completer, Completion -from prompt_toolkit.layout.lexers import Lexer -from prompt_toolkit.layout.lexers import PygmentsLexer +from prompt_toolkit.lexers import Lexer +from prompt_toolkit.lexers import PygmentsLexer +from prompt_toolkit.patch_stdout import patch_stdout import pygments.lexers as pygments_lexers @@ -52,14 +53,11 @@ def _adjust_completion_text_based_on_context(text, body, offset): class IPythonPTCompleter(Completer): """Adaptor to provide IPython completions to prompt_toolkit""" - def __init__(self, ipy_completer=None, shell=None, patch_stdout=None): + def __init__(self, ipy_completer=None, shell=None): if shell is None and ipy_completer is None: raise TypeError("Please pass shell=an InteractiveShell instance.") self._ipy_completer = ipy_completer self.shell = shell - if patch_stdout is None: - raise TypeError("Please pass patch_stdout") - self.patch_stdout = patch_stdout @property def ipy_completer(self): @@ -75,7 +73,7 @@ class IPythonPTCompleter(Completer): # is imported). This context manager ensures that doesn't interfere with # the prompt. - with self.patch_stdout(), provisionalcompleter(): + with patch_stdout(), provisionalcompleter(): body = document.text cursor_row = document.cursor_position_row cursor_col = document.cursor_position_col @@ -143,7 +141,7 @@ class IPythonPTLexer(Lexer): 'latex': PygmentsLexer(l.TexLexer), } - def lex_document(self, cli, document): + def lex_document(self, document): text = document.text.lstrip() lexer = self.python_lexer @@ -157,4 +155,4 @@ class IPythonPTLexer(Lexer): lexer = l break - return lexer.lex_document(cli, document) + return lexer.lex_document(document) diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py index a96338d..e3c4364 100644 --- a/IPython/terminal/shortcuts.py +++ b/IPython/terminal/shortcuts.py @@ -12,91 +12,79 @@ import sys from typing import Callable +from prompt_toolkit.application.current import get_app from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER -from prompt_toolkit.filters import (HasFocus, HasSelection, Condition, - ViInsertMode, EmacsInsertMode, HasCompletions) -from prompt_toolkit.filters.cli import ViMode, ViNavigationMode -from prompt_toolkit.keys import Keys +from prompt_toolkit.filters import (has_focus, has_selection, Condition, + vi_insert_mode, emacs_insert_mode, has_completions, vi_mode) from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline +from prompt_toolkit.key_binding import KeyBindings from IPython.utils.decorators import undoc @undoc @Condition -def cursor_in_leading_ws(cli): - before = cli.application.buffer.document.current_line_before_cursor +def cursor_in_leading_ws(): + before = get_app().current_buffer.document.current_line_before_cursor return (not before) or before.isspace() -def register_ipython_shortcuts(registry, shell): + +def create_ipython_shortcuts(shell): """Set up the prompt_toolkit keyboard shortcuts for IPython""" - insert_mode = ViInsertMode() | EmacsInsertMode() + + kb = KeyBindings() + insert_mode = vi_insert_mode | emacs_insert_mode if getattr(shell, 'handle_return', None): return_handler = shell.handle_return(shell) else: return_handler = newline_or_execute_outer(shell) - # Ctrl+J == Enter, seemingly - registry.add_binding(Keys.ControlJ, - filter=(HasFocus(DEFAULT_BUFFER) - & ~HasSelection() - & insert_mode + kb.add('enter', filter=(has_focus(DEFAULT_BUFFER) + & ~has_selection + & insert_mode ))(return_handler) - registry.add_binding(Keys.ControlBackslash)(force_exit) + kb.add('c-\\')(force_exit) - registry.add_binding(Keys.ControlP, - filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER) - ))(previous_history_or_previous_completion) + kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) + )(previous_history_or_previous_completion) - registry.add_binding(Keys.ControlN, - filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER) - ))(next_history_or_next_completion) + kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) + )(next_history_or_next_completion) - registry.add_binding(Keys.ControlG, - filter=(HasFocus(DEFAULT_BUFFER) & HasCompletions() - ))(dismiss_completion) + kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions) + )(dismiss_completion) - registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER) - )(reset_buffer) + kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer) - registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER) - )(reset_search_buffer) + kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer) - supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP')) - registry.add_binding(Keys.ControlZ, filter=supports_suspend - )(suspend_to_bg) + supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP')) + kb.add('c-z', filter=supports_suspend)(suspend_to_bg) # Ctrl+I == Tab - registry.add_binding(Keys.ControlI, - filter=(HasFocus(DEFAULT_BUFFER) - & ~HasSelection() - & insert_mode - & cursor_in_leading_ws + kb.add('tab', filter=(has_focus(DEFAULT_BUFFER) + & ~has_selection + & insert_mode + & cursor_in_leading_ws ))(indent_buffer) - registry.add_binding(Keys.ControlO, - filter=(HasFocus(DEFAULT_BUFFER) - & EmacsInsertMode()))(newline_autoindent_outer(shell.input_splitter)) + kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) + & emacs_insert_mode))(newline_autoindent_outer(shell.input_splitter)) - registry.add_binding(Keys.F2, - filter=HasFocus(DEFAULT_BUFFER) - )(open_input_in_editor) + kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor) if shell.display_completions == 'readlinelike': - registry.add_binding(Keys.ControlI, - filter=(HasFocus(DEFAULT_BUFFER) - & ~HasSelection() - & insert_mode - & ~cursor_in_leading_ws - ))(display_completions_like_readline) + kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER) + & ~has_selection + & insert_mode + & ~cursor_in_leading_ws + ))(display_completions_like_readline) if sys.platform == 'win32': - registry.add_binding(Keys.ControlV, - filter=( - HasFocus( - DEFAULT_BUFFER) & ~ViMode() - ))(win_paste) + kb.add('c-v', filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste) + + return kb def newline_or_execute_outer(shell): @@ -127,8 +115,8 @@ def newline_or_execute_outer(shell): b.insert_text('\n' + (' ' * (indent or 0))) return - if (status != 'incomplete') and b.accept_action.is_returnable: - b.accept_action.validate_and_handle(event.cli, b) + if (status != 'incomplete') and b.accept_handler: + b.validate_and_handle() else: b.insert_text('\n' + (' ' * (indent or 0))) return newline_or_execute @@ -170,10 +158,10 @@ def reset_search_buffer(event): if event.current_buffer.document.text: event.current_buffer.reset() else: - event.cli.push_focus(DEFAULT_BUFFER) + event.app.layout.focus(DEFAULT_BUFFER) def suspend_to_bg(event): - event.cli.suspend_to_background() + event.app.suspend_to_background() def force_exit(event): """ @@ -232,8 +220,8 @@ def newline_autoindent_outer(inputsplitter) -> Callable[..., None]: def open_input_in_editor(event): - event.cli.current_buffer.tempfile_suffix = ".py" - event.cli.current_buffer.open_in_editor(event.cli) + event.app.current_buffer.tempfile_suffix = ".py" + event.app.current_buffer.open_in_editor() if sys.platform == 'win32': diff --git a/setup.py b/setup.py index 2c8aca8..bd72088 100755 --- a/setup.py +++ b/setup.py @@ -190,7 +190,7 @@ install_requires = [ 'pickleshare', 'simplegeneric>0.8', 'traitlets>=4.2', - 'prompt_toolkit>=1.0.15,<2.0.0', + 'prompt_toolkit>=2.0.0,<2.1.0', 'pygments', 'backcall', ]