Skip to content

Commit

Permalink
Merge pull request #11177 from takluyver/prompt-toolkit-2
Browse files Browse the repository at this point in the history
Upgrade to prompt_toolkit 2.0
  • Loading branch information
minrk committed Jun 13, 2018
2 parents a7de851 + bf676a5 commit 4235328
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 186 deletions.
57 changes: 25 additions & 32 deletions IPython/terminal/debugger.py
Expand Up @@ -8,15 +8,14 @@
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):
Expand All @@ -26,46 +25,40 @@ def __init__(self, *args, **kwargs):
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
Expand All @@ -92,7 +85,7 @@ def cmdloop(self, intro=None):
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)
Expand Down
137 changes: 63 additions & 74 deletions IPython/terminal/interactiveshell.py
Expand Up @@ -15,15 +15,16 @@
Any,
)

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.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
from prompt_toolkit.output import ColorDepth
from prompt_toolkit.patch_stdout import patch_stdout
from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
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
Expand All @@ -34,7 +35,7 @@
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()

Expand Down Expand Up @@ -88,15 +89,11 @@ def get_default_editor():

class TerminalInteractiveShell(InteractiveShell):
space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
'to reserve for the completion menu'
'to reserve for the completion menu'
).tag(config=True)

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.
Expand Down Expand Up @@ -167,9 +164,9 @@ def refresh_style(self):
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):
Expand Down Expand Up @@ -243,10 +240,7 @@ def prompt():
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()
Expand All @@ -256,32 +250,26 @@ def prompt():
# 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)
self.style = DynamicStyle(lambda: self._style)

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):
"""
Expand Down Expand Up @@ -342,44 +330,60 @@ def _make_style_from_name_or_cls(self, name_or_cls):
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):
Expand Down Expand Up @@ -439,22 +443,6 @@ def ask_exit(self):

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:
Expand Down Expand Up @@ -517,8 +505,9 @@ def auto_rewrite_input(self, cmd):
return

tokens = self.prompts.rewrite_prompt_tokens()
if self.pt_cli:
self.pt_cli.print_tokens(tokens)
if self.pt_app:
print_formatted_text(PygmentsTokens(tokens), end='',
style=self.pt_app.app.style)
print(cmd)
else:
prompt = ''.join(s for t, s in tokens)
Expand All @@ -533,7 +522,7 @@ def switch_doctest_mode(self, mode):
elif self._prompts_before:
self.prompts = self._prompts_before
self._prompts_before = None
self._update_layout()
# self._update_layout()


InteractiveShellABC.register(TerminalInteractiveShell)
Expand Down
20 changes: 12 additions & 8 deletions IPython/terminal/prompts.py
Expand Up @@ -5,23 +5,25 @@

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)),
(Token.Prompt, ']: '),
]

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 [
Expand All @@ -42,12 +44,12 @@ def out_prompt_tokens(self):
]

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, '... ')
]
Expand All @@ -73,7 +75,9 @@ def write_output_prompt(self):
# 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, end='',
)
else:
sys.stdout.write(prompt_txt)

0 comments on commit 4235328

Please sign in to comment.