##// END OF EJS Templates
Backport PR #10231: Fix set_next_input with prompt_toolkit 1.0.10...
Min RK -
Show More
@@ -1,524 +1,536 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2 from __future__ import print_function
2 from __future__ import print_function
3
3
4 import os
4 import os
5 import sys
5 import sys
6 import warnings
6 import warnings
7 from warnings import warn
7 from warnings import warn
8
8
9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 from IPython.utils import io
10 from IPython.utils import io
11 from IPython.utils.py3compat import PY3, cast_unicode_py2, input, string_types
11 from IPython.utils.py3compat import PY3, cast_unicode_py2, input, string_types
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title
13 from IPython.utils.process import abbrev_cwd
13 from IPython.utils.process import abbrev_cwd
14 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union
14 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union
15
15
16 from prompt_toolkit.document import Document
16 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
17 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
17 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
18 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
18 from prompt_toolkit.history import InMemoryHistory
19 from prompt_toolkit.history import InMemoryHistory
19 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
20 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
20 from prompt_toolkit.interface import CommandLineInterface
21 from prompt_toolkit.interface import CommandLineInterface
21 from prompt_toolkit.key_binding.manager import KeyBindingManager
22 from prompt_toolkit.key_binding.manager import KeyBindingManager
22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
24 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
24
25
25 from pygments.styles import get_style_by_name, get_all_styles
26 from pygments.styles import get_style_by_name, get_all_styles
26 from pygments.style import Style
27 from pygments.style import Style
27 from pygments.token import Token
28 from pygments.token import Token
28
29
29 from .debugger import TerminalPdb, Pdb
30 from .debugger import TerminalPdb, Pdb
30 from .magics import TerminalMagics
31 from .magics import TerminalMagics
31 from .pt_inputhooks import get_inputhook_name_and_func
32 from .pt_inputhooks import get_inputhook_name_and_func
32 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
33 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
33 from .ptutils import IPythonPTCompleter, IPythonPTLexer
34 from .ptutils import IPythonPTCompleter, IPythonPTLexer
34 from .shortcuts import register_ipython_shortcuts
35 from .shortcuts import register_ipython_shortcuts
35
36
36 DISPLAY_BANNER_DEPRECATED = object()
37 DISPLAY_BANNER_DEPRECATED = object()
37
38
38
39
39 from pygments.style import Style
40 from pygments.style import Style
40
41
41 class _NoStyle(Style): pass
42 class _NoStyle(Style): pass
42
43
43
44
44
45
45 _style_overrides_light_bg = {
46 _style_overrides_light_bg = {
46 Token.Prompt: '#0000ff',
47 Token.Prompt: '#0000ff',
47 Token.PromptNum: '#0000ee bold',
48 Token.PromptNum: '#0000ee bold',
48 Token.OutPrompt: '#cc0000',
49 Token.OutPrompt: '#cc0000',
49 Token.OutPromptNum: '#bb0000 bold',
50 Token.OutPromptNum: '#bb0000 bold',
50 }
51 }
51
52
52 _style_overrides_linux = {
53 _style_overrides_linux = {
53 Token.Prompt: '#00cc00',
54 Token.Prompt: '#00cc00',
54 Token.PromptNum: '#00bb00 bold',
55 Token.PromptNum: '#00bb00 bold',
55 Token.OutPrompt: '#cc0000',
56 Token.OutPrompt: '#cc0000',
56 Token.OutPromptNum: '#bb0000 bold',
57 Token.OutPromptNum: '#bb0000 bold',
57 }
58 }
58
59
59
60
60
61
61 def get_default_editor():
62 def get_default_editor():
62 try:
63 try:
63 ed = os.environ['EDITOR']
64 ed = os.environ['EDITOR']
64 if not PY3:
65 if not PY3:
65 ed = ed.decode()
66 ed = ed.decode()
66 return ed
67 return ed
67 except KeyError:
68 except KeyError:
68 pass
69 pass
69 except UnicodeError:
70 except UnicodeError:
70 warn("$EDITOR environment variable is not pure ASCII. Using platform "
71 warn("$EDITOR environment variable is not pure ASCII. Using platform "
71 "default editor.")
72 "default editor.")
72
73
73 if os.name == 'posix':
74 if os.name == 'posix':
74 return 'vi' # the only one guaranteed to be there!
75 return 'vi' # the only one guaranteed to be there!
75 else:
76 else:
76 return 'notepad' # same in Windows!
77 return 'notepad' # same in Windows!
77
78
78 # conservatively check for tty
79 # conservatively check for tty
79 # overridden streams can result in things like:
80 # overridden streams can result in things like:
80 # - sys.stdin = None
81 # - sys.stdin = None
81 # - no isatty method
82 # - no isatty method
82 for _name in ('stdin', 'stdout', 'stderr'):
83 for _name in ('stdin', 'stdout', 'stderr'):
83 _stream = getattr(sys, _name)
84 _stream = getattr(sys, _name)
84 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
85 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
85 _is_tty = False
86 _is_tty = False
86 break
87 break
87 else:
88 else:
88 _is_tty = True
89 _is_tty = True
89
90
90
91
91 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
92 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
92
93
93 class TerminalInteractiveShell(InteractiveShell):
94 class TerminalInteractiveShell(InteractiveShell):
94 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
95 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
95 'to reserve for the completion menu'
96 'to reserve for the completion menu'
96 ).tag(config=True)
97 ).tag(config=True)
97
98
98 def _space_for_menu_changed(self, old, new):
99 def _space_for_menu_changed(self, old, new):
99 self._update_layout()
100 self._update_layout()
100
101
101 pt_cli = None
102 pt_cli = None
102 debugger_history = None
103 debugger_history = None
103 _pt_app = None
104 _pt_app = None
104
105
105 simple_prompt = Bool(_use_simple_prompt,
106 simple_prompt = Bool(_use_simple_prompt,
106 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
107 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
107
108
108 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
109 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
109 IPython own testing machinery, and emacs inferior-shell integration through elpy.
110 IPython own testing machinery, and emacs inferior-shell integration through elpy.
110
111
111 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
112 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
112 environment variable is set, or the current terminal is not a tty.
113 environment variable is set, or the current terminal is not a tty.
113
114
114 """
115 """
115 ).tag(config=True)
116 ).tag(config=True)
116
117
117 @property
118 @property
118 def debugger_cls(self):
119 def debugger_cls(self):
119 return Pdb if self.simple_prompt else TerminalPdb
120 return Pdb if self.simple_prompt else TerminalPdb
120
121
121 confirm_exit = Bool(True,
122 confirm_exit = Bool(True,
122 help="""
123 help="""
123 Set to confirm when you try to exit IPython with an EOF (Control-D
124 Set to confirm when you try to exit IPython with an EOF (Control-D
124 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
125 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
125 you can force a direct exit without any confirmation.""",
126 you can force a direct exit without any confirmation.""",
126 ).tag(config=True)
127 ).tag(config=True)
127
128
128 editing_mode = Unicode('emacs',
129 editing_mode = Unicode('emacs',
129 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
130 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
130 ).tag(config=True)
131 ).tag(config=True)
131
132
132 mouse_support = Bool(False,
133 mouse_support = Bool(False,
133 help="Enable mouse support in the prompt"
134 help="Enable mouse support in the prompt"
134 ).tag(config=True)
135 ).tag(config=True)
135
136
136 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
137 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
137 help="""The name or class of a Pygments style to use for syntax
138 help="""The name or class of a Pygments style to use for syntax
138 highlighting: \n %s""" % ', '.join(get_all_styles())
139 highlighting: \n %s""" % ', '.join(get_all_styles())
139 ).tag(config=True)
140 ).tag(config=True)
140
141
141
142
142 @observe('highlighting_style')
143 @observe('highlighting_style')
143 @observe('colors')
144 @observe('colors')
144 def _highlighting_style_changed(self, change):
145 def _highlighting_style_changed(self, change):
145 self.refresh_style()
146 self.refresh_style()
146
147
147 def refresh_style(self):
148 def refresh_style(self):
148 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
149 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
149
150
150
151
151 highlighting_style_overrides = Dict(
152 highlighting_style_overrides = Dict(
152 help="Override highlighting format for specific tokens"
153 help="Override highlighting format for specific tokens"
153 ).tag(config=True)
154 ).tag(config=True)
154
155
155 true_color = Bool(False,
156 true_color = Bool(False,
156 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
157 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
157 "If your terminal supports true color, the following command "
158 "If your terminal supports true color, the following command "
158 "should print 'TRUECOLOR' in orange: "
159 "should print 'TRUECOLOR' in orange: "
159 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
160 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
160 ).tag(config=True)
161 ).tag(config=True)
161
162
162 editor = Unicode(get_default_editor(),
163 editor = Unicode(get_default_editor(),
163 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
164 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
164 ).tag(config=True)
165 ).tag(config=True)
165
166
166 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
167 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
167
168
168 prompts = Instance(Prompts)
169 prompts = Instance(Prompts)
169
170
170 @default('prompts')
171 @default('prompts')
171 def _prompts_default(self):
172 def _prompts_default(self):
172 return self.prompts_class(self)
173 return self.prompts_class(self)
173
174
174 @observe('prompts')
175 @observe('prompts')
175 def _(self, change):
176 def _(self, change):
176 self._update_layout()
177 self._update_layout()
177
178
178 @default('displayhook_class')
179 @default('displayhook_class')
179 def _displayhook_class_default(self):
180 def _displayhook_class_default(self):
180 return RichPromptDisplayHook
181 return RichPromptDisplayHook
181
182
182 term_title = Bool(True,
183 term_title = Bool(True,
183 help="Automatically set the terminal title"
184 help="Automatically set the terminal title"
184 ).tag(config=True)
185 ).tag(config=True)
185
186
186 display_completions = Enum(('column', 'multicolumn','readlinelike'),
187 display_completions = Enum(('column', 'multicolumn','readlinelike'),
187 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
188 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
188 "'readlinelike'. These options are for `prompt_toolkit`, see "
189 "'readlinelike'. These options are for `prompt_toolkit`, see "
189 "`prompt_toolkit` documentation for more information."
190 "`prompt_toolkit` documentation for more information."
190 ),
191 ),
191 default_value='multicolumn').tag(config=True)
192 default_value='multicolumn').tag(config=True)
192
193
193 highlight_matching_brackets = Bool(True,
194 highlight_matching_brackets = Bool(True,
194 help="Highlight matching brackets .",
195 help="Highlight matching brackets .",
195 ).tag(config=True)
196 ).tag(config=True)
196
197
197 @observe('term_title')
198 @observe('term_title')
198 def init_term_title(self, change=None):
199 def init_term_title(self, change=None):
199 # Enable or disable the terminal title.
200 # Enable or disable the terminal title.
200 if self.term_title:
201 if self.term_title:
201 toggle_set_term_title(True)
202 toggle_set_term_title(True)
202 set_term_title('IPython: ' + abbrev_cwd())
203 set_term_title('IPython: ' + abbrev_cwd())
203 else:
204 else:
204 toggle_set_term_title(False)
205 toggle_set_term_title(False)
205
206
206 def init_display_formatter(self):
207 def init_display_formatter(self):
207 super(TerminalInteractiveShell, self).init_display_formatter()
208 super(TerminalInteractiveShell, self).init_display_formatter()
208 # terminal only supports plain text
209 # terminal only supports plain text
209 self.display_formatter.active_types = ['text/plain']
210 self.display_formatter.active_types = ['text/plain']
210 # disable `_ipython_display_`
211 # disable `_ipython_display_`
211 self.display_formatter.ipython_display_formatter.enabled = False
212 self.display_formatter.ipython_display_formatter.enabled = False
212
213
213 def init_prompt_toolkit_cli(self):
214 def init_prompt_toolkit_cli(self):
214 if self.simple_prompt:
215 if self.simple_prompt:
215 # Fall back to plain non-interactive output for tests.
216 # Fall back to plain non-interactive output for tests.
216 # This is very limited, and only accepts a single line.
217 # This is very limited, and only accepts a single line.
217 def prompt():
218 def prompt():
218 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
219 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
219 self.prompt_for_code = prompt
220 self.prompt_for_code = prompt
220 return
221 return
221
222
222 # Set up keyboard shortcuts
223 # Set up keyboard shortcuts
223 kbmanager = KeyBindingManager.for_prompt()
224 kbmanager = KeyBindingManager.for_prompt()
224 register_ipython_shortcuts(kbmanager.registry, self)
225 register_ipython_shortcuts(kbmanager.registry, self)
225
226
226 # Pre-populate history from IPython's history database
227 # Pre-populate history from IPython's history database
227 history = InMemoryHistory()
228 history = InMemoryHistory()
228 last_cell = u""
229 last_cell = u""
229 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
230 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
230 include_latest=True):
231 include_latest=True):
231 # Ignore blank lines and consecutive duplicates
232 # Ignore blank lines and consecutive duplicates
232 cell = cell.rstrip()
233 cell = cell.rstrip()
233 if cell and (cell != last_cell):
234 if cell and (cell != last_cell):
234 history.append(cell)
235 history.append(cell)
235 last_cell = cell
236 last_cell = cell
236
237
237 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
238 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
238 style = DynamicStyle(lambda: self._style)
239 style = DynamicStyle(lambda: self._style)
239
240
240 editing_mode = getattr(EditingMode, self.editing_mode.upper())
241 editing_mode = getattr(EditingMode, self.editing_mode.upper())
241
242
242 def patch_stdout(**kwargs):
243 def patch_stdout(**kwargs):
243 return self.pt_cli.patch_stdout_context(**kwargs)
244 return self.pt_cli.patch_stdout_context(**kwargs)
244
245
245 self._pt_app = create_prompt_application(
246 self._pt_app = create_prompt_application(
246 editing_mode=editing_mode,
247 editing_mode=editing_mode,
247 key_bindings_registry=kbmanager.registry,
248 key_bindings_registry=kbmanager.registry,
248 history=history,
249 history=history,
249 completer=IPythonPTCompleter(shell=self,
250 completer=IPythonPTCompleter(shell=self,
250 patch_stdout=patch_stdout),
251 patch_stdout=patch_stdout),
251 enable_history_search=True,
252 enable_history_search=True,
252 style=style,
253 style=style,
253 mouse_support=self.mouse_support,
254 mouse_support=self.mouse_support,
254 **self._layout_options()
255 **self._layout_options()
255 )
256 )
256 self._eventloop = create_eventloop(self.inputhook)
257 self._eventloop = create_eventloop(self.inputhook)
257 self.pt_cli = CommandLineInterface(
258 self.pt_cli = CommandLineInterface(
258 self._pt_app, eventloop=self._eventloop,
259 self._pt_app, eventloop=self._eventloop,
259 output=create_output(true_color=self.true_color))
260 output=create_output(true_color=self.true_color))
260
261
261 def _make_style_from_name_or_cls(self, name_or_cls):
262 def _make_style_from_name_or_cls(self, name_or_cls):
262 """
263 """
263 Small wrapper that make an IPython compatible style from a style name
264 Small wrapper that make an IPython compatible style from a style name
264
265
265 We need that to add style for prompt ... etc.
266 We need that to add style for prompt ... etc.
266 """
267 """
267 style_overrides = {}
268 style_overrides = {}
268 if name_or_cls == 'legacy':
269 if name_or_cls == 'legacy':
269 legacy = self.colors.lower()
270 legacy = self.colors.lower()
270 if legacy == 'linux':
271 if legacy == 'linux':
271 style_cls = get_style_by_name('monokai')
272 style_cls = get_style_by_name('monokai')
272 style_overrides = _style_overrides_linux
273 style_overrides = _style_overrides_linux
273 elif legacy == 'lightbg':
274 elif legacy == 'lightbg':
274 style_overrides = _style_overrides_light_bg
275 style_overrides = _style_overrides_light_bg
275 style_cls = get_style_by_name('pastie')
276 style_cls = get_style_by_name('pastie')
276 elif legacy == 'neutral':
277 elif legacy == 'neutral':
277 # The default theme needs to be visible on both a dark background
278 # The default theme needs to be visible on both a dark background
278 # and a light background, because we can't tell what the terminal
279 # and a light background, because we can't tell what the terminal
279 # looks like. These tweaks to the default theme help with that.
280 # looks like. These tweaks to the default theme help with that.
280 style_cls = get_style_by_name('default')
281 style_cls = get_style_by_name('default')
281 style_overrides.update({
282 style_overrides.update({
282 Token.Number: '#007700',
283 Token.Number: '#007700',
283 Token.Operator: 'noinherit',
284 Token.Operator: 'noinherit',
284 Token.String: '#BB6622',
285 Token.String: '#BB6622',
285 Token.Name.Function: '#2080D0',
286 Token.Name.Function: '#2080D0',
286 Token.Name.Class: 'bold #2080D0',
287 Token.Name.Class: 'bold #2080D0',
287 Token.Name.Namespace: 'bold #2080D0',
288 Token.Name.Namespace: 'bold #2080D0',
288 Token.Prompt: '#009900',
289 Token.Prompt: '#009900',
289 Token.PromptNum: '#00ff00 bold',
290 Token.PromptNum: '#00ff00 bold',
290 Token.OutPrompt: '#990000',
291 Token.OutPrompt: '#990000',
291 Token.OutPromptNum: '#ff0000 bold',
292 Token.OutPromptNum: '#ff0000 bold',
292 })
293 })
293
294
294 # Hack: Due to limited color support on the Windows console
295 # Hack: Due to limited color support on the Windows console
295 # the prompt colors will be wrong without this
296 # the prompt colors will be wrong without this
296 if os.name == 'nt':
297 if os.name == 'nt':
297 style_overrides.update({
298 style_overrides.update({
298 Token.Prompt: '#ansidarkgreen',
299 Token.Prompt: '#ansidarkgreen',
299 Token.PromptNum: '#ansigreen bold',
300 Token.PromptNum: '#ansigreen bold',
300 Token.OutPrompt: '#ansidarkred',
301 Token.OutPrompt: '#ansidarkred',
301 Token.OutPromptNum: '#ansired bold',
302 Token.OutPromptNum: '#ansired bold',
302 })
303 })
303 elif legacy =='nocolor':
304 elif legacy =='nocolor':
304 style_cls=_NoStyle
305 style_cls=_NoStyle
305 style_overrides = {}
306 style_overrides = {}
306 else :
307 else :
307 raise ValueError('Got unknown colors: ', legacy)
308 raise ValueError('Got unknown colors: ', legacy)
308 else :
309 else :
309 if isinstance(name_or_cls, string_types):
310 if isinstance(name_or_cls, string_types):
310 style_cls = get_style_by_name(name_or_cls)
311 style_cls = get_style_by_name(name_or_cls)
311 else:
312 else:
312 style_cls = name_or_cls
313 style_cls = name_or_cls
313 style_overrides = {
314 style_overrides = {
314 Token.Prompt: '#009900',
315 Token.Prompt: '#009900',
315 Token.PromptNum: '#00ff00 bold',
316 Token.PromptNum: '#00ff00 bold',
316 Token.OutPrompt: '#990000',
317 Token.OutPrompt: '#990000',
317 Token.OutPromptNum: '#ff0000 bold',
318 Token.OutPromptNum: '#ff0000 bold',
318 }
319 }
319 style_overrides.update(self.highlighting_style_overrides)
320 style_overrides.update(self.highlighting_style_overrides)
320 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
321 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
321 style_dict=style_overrides)
322 style_dict=style_overrides)
322
323
323 return style
324 return style
324
325
325 def _layout_options(self):
326 def _layout_options(self):
326 """
327 """
327 Return the current layout option for the current Terminal InteractiveShell
328 Return the current layout option for the current Terminal InteractiveShell
328 """
329 """
329 return {
330 return {
330 'lexer':IPythonPTLexer(),
331 'lexer':IPythonPTLexer(),
331 'reserve_space_for_menu':self.space_for_menu,
332 'reserve_space_for_menu':self.space_for_menu,
332 'get_prompt_tokens':self.prompts.in_prompt_tokens,
333 'get_prompt_tokens':self.prompts.in_prompt_tokens,
333 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
334 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
334 'multiline':True,
335 'multiline':True,
335 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
336 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
336
337
337 # Highlight matching brackets, but only when this setting is
338 # Highlight matching brackets, but only when this setting is
338 # enabled, and only when the DEFAULT_BUFFER has the focus.
339 # enabled, and only when the DEFAULT_BUFFER has the focus.
339 'extra_input_processors': [ConditionalProcessor(
340 'extra_input_processors': [ConditionalProcessor(
340 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
341 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
341 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
342 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
342 Condition(lambda cli: self.highlight_matching_brackets))],
343 Condition(lambda cli: self.highlight_matching_brackets))],
343 }
344 }
344
345
345 def _update_layout(self):
346 def _update_layout(self):
346 """
347 """
347 Ask for a re computation of the application layout, if for example ,
348 Ask for a re computation of the application layout, if for example ,
348 some configuration options have changed.
349 some configuration options have changed.
349 """
350 """
350 if self._pt_app:
351 if self._pt_app:
351 self._pt_app.layout = create_prompt_layout(**self._layout_options())
352 self._pt_app.layout = create_prompt_layout(**self._layout_options())
352
353
353 def prompt_for_code(self):
354 def prompt_for_code(self):
354 document = self.pt_cli.run(
355 document = self.pt_cli.run(
355 pre_run=self.pre_prompt, reset_current_buffer=True)
356 pre_run=self.pre_prompt, reset_current_buffer=True)
356 return document.text
357 return document.text
357
358
358 def enable_win_unicode_console(self):
359 def enable_win_unicode_console(self):
359 if sys.version_info >= (3, 6):
360 if sys.version_info >= (3, 6):
360 # Since PEP 528, Python uses the unicode APIs for the Windows
361 # Since PEP 528, Python uses the unicode APIs for the Windows
361 # console by default, so WUC shouldn't be needed.
362 # console by default, so WUC shouldn't be needed.
362 return
363 return
363
364
364 import win_unicode_console
365 import win_unicode_console
365
366
366 if PY3:
367 if PY3:
367 win_unicode_console.enable()
368 win_unicode_console.enable()
368 else:
369 else:
369 # https://github.com/ipython/ipython/issues/9768
370 # https://github.com/ipython/ipython/issues/9768
370 from win_unicode_console.streams import (TextStreamWrapper,
371 from win_unicode_console.streams import (TextStreamWrapper,
371 stdout_text_transcoded, stderr_text_transcoded)
372 stdout_text_transcoded, stderr_text_transcoded)
372
373
373 class LenientStrStreamWrapper(TextStreamWrapper):
374 class LenientStrStreamWrapper(TextStreamWrapper):
374 def write(self, s):
375 def write(self, s):
375 if isinstance(s, bytes):
376 if isinstance(s, bytes):
376 s = s.decode(self.encoding, 'replace')
377 s = s.decode(self.encoding, 'replace')
377
378
378 self.base.write(s)
379 self.base.write(s)
379
380
380 stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded)
381 stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded)
381 stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded)
382 stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded)
382
383
383 win_unicode_console.enable(stdout=stdout_text_str,
384 win_unicode_console.enable(stdout=stdout_text_str,
384 stderr=stderr_text_str)
385 stderr=stderr_text_str)
385
386
386 def init_io(self):
387 def init_io(self):
387 if sys.platform not in {'win32', 'cli'}:
388 if sys.platform not in {'win32', 'cli'}:
388 return
389 return
389
390
390 self.enable_win_unicode_console()
391 self.enable_win_unicode_console()
391
392
392 import colorama
393 import colorama
393 colorama.init()
394 colorama.init()
394
395
395 # For some reason we make these wrappers around stdout/stderr.
396 # For some reason we make these wrappers around stdout/stderr.
396 # For now, we need to reset them so all output gets coloured.
397 # For now, we need to reset them so all output gets coloured.
397 # https://github.com/ipython/ipython/issues/8669
398 # https://github.com/ipython/ipython/issues/8669
398 # io.std* are deprecated, but don't show our own deprecation warnings
399 # io.std* are deprecated, but don't show our own deprecation warnings
399 # during initialization of the deprecated API.
400 # during initialization of the deprecated API.
400 with warnings.catch_warnings():
401 with warnings.catch_warnings():
401 warnings.simplefilter('ignore', DeprecationWarning)
402 warnings.simplefilter('ignore', DeprecationWarning)
402 io.stdout = io.IOStream(sys.stdout)
403 io.stdout = io.IOStream(sys.stdout)
403 io.stderr = io.IOStream(sys.stderr)
404 io.stderr = io.IOStream(sys.stderr)
404
405
405 def init_magics(self):
406 def init_magics(self):
406 super(TerminalInteractiveShell, self).init_magics()
407 super(TerminalInteractiveShell, self).init_magics()
407 self.register_magics(TerminalMagics)
408 self.register_magics(TerminalMagics)
408
409
409 def init_alias(self):
410 def init_alias(self):
410 # The parent class defines aliases that can be safely used with any
411 # The parent class defines aliases that can be safely used with any
411 # frontend.
412 # frontend.
412 super(TerminalInteractiveShell, self).init_alias()
413 super(TerminalInteractiveShell, self).init_alias()
413
414
414 # Now define aliases that only make sense on the terminal, because they
415 # Now define aliases that only make sense on the terminal, because they
415 # need direct access to the console in a way that we can't emulate in
416 # need direct access to the console in a way that we can't emulate in
416 # GUI or web frontend
417 # GUI or web frontend
417 if os.name == 'posix':
418 if os.name == 'posix':
418 for cmd in ['clear', 'more', 'less', 'man']:
419 for cmd in ['clear', 'more', 'less', 'man']:
419 self.alias_manager.soft_define_alias(cmd, cmd)
420 self.alias_manager.soft_define_alias(cmd, cmd)
420
421
421
422
422 def __init__(self, *args, **kwargs):
423 def __init__(self, *args, **kwargs):
423 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
424 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
424 self.init_prompt_toolkit_cli()
425 self.init_prompt_toolkit_cli()
425 self.init_term_title()
426 self.init_term_title()
426 self.keep_running = True
427 self.keep_running = True
427
428
428 self.debugger_history = InMemoryHistory()
429 self.debugger_history = InMemoryHistory()
429
430
430 def ask_exit(self):
431 def ask_exit(self):
431 self.keep_running = False
432 self.keep_running = False
432
433
433 rl_next_input = None
434 rl_next_input = None
434
435
435 def pre_prompt(self):
436 def pre_prompt(self):
436 if self.rl_next_input:
437 if self.rl_next_input:
437 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
438 # We can't set the buffer here, because it will be reset just after
439 # this. Adding a callable to pre_run_callables does what we need
440 # after the buffer is reset.
441 s = cast_unicode_py2(self.rl_next_input)
442 def set_doc():
443 self.pt_cli.application.buffer.document = Document(s)
444 if hasattr(self.pt_cli, 'pre_run_callables'):
445 self.pt_cli.pre_run_callables.append(set_doc)
446 else:
447 # Older version of prompt_toolkit; it's OK to set the document
448 # directly here.
449 set_doc()
438 self.rl_next_input = None
450 self.rl_next_input = None
439
451
440 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
452 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
441
453
442 if display_banner is not DISPLAY_BANNER_DEPRECATED:
454 if display_banner is not DISPLAY_BANNER_DEPRECATED:
443 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
455 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
444
456
445 self.keep_running = True
457 self.keep_running = True
446 while self.keep_running:
458 while self.keep_running:
447 print(self.separate_in, end='')
459 print(self.separate_in, end='')
448
460
449 try:
461 try:
450 code = self.prompt_for_code()
462 code = self.prompt_for_code()
451 except EOFError:
463 except EOFError:
452 if (not self.confirm_exit) \
464 if (not self.confirm_exit) \
453 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
465 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
454 self.ask_exit()
466 self.ask_exit()
455
467
456 else:
468 else:
457 if code:
469 if code:
458 self.run_cell(code, store_history=True)
470 self.run_cell(code, store_history=True)
459
471
460 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
472 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
461 # An extra layer of protection in case someone mashing Ctrl-C breaks
473 # An extra layer of protection in case someone mashing Ctrl-C breaks
462 # out of our internal code.
474 # out of our internal code.
463 if display_banner is not DISPLAY_BANNER_DEPRECATED:
475 if display_banner is not DISPLAY_BANNER_DEPRECATED:
464 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
476 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
465 while True:
477 while True:
466 try:
478 try:
467 self.interact()
479 self.interact()
468 break
480 break
469 except KeyboardInterrupt as e:
481 except KeyboardInterrupt as e:
470 print("\n%s escaped interact()\n" % type(e).__name__)
482 print("\n%s escaped interact()\n" % type(e).__name__)
471 finally:
483 finally:
472 # An interrupt during the eventloop will mess up the
484 # An interrupt during the eventloop will mess up the
473 # internal state of the prompt_toolkit library.
485 # internal state of the prompt_toolkit library.
474 # Stopping the eventloop fixes this, see
486 # Stopping the eventloop fixes this, see
475 # https://github.com/ipython/ipython/pull/9867
487 # https://github.com/ipython/ipython/pull/9867
476 if hasattr(self, '_eventloop'):
488 if hasattr(self, '_eventloop'):
477 self._eventloop.stop()
489 self._eventloop.stop()
478
490
479 _inputhook = None
491 _inputhook = None
480 def inputhook(self, context):
492 def inputhook(self, context):
481 if self._inputhook is not None:
493 if self._inputhook is not None:
482 self._inputhook(context)
494 self._inputhook(context)
483
495
484 active_eventloop = None
496 active_eventloop = None
485 def enable_gui(self, gui=None):
497 def enable_gui(self, gui=None):
486 if gui:
498 if gui:
487 self.active_eventloop, self._inputhook =\
499 self.active_eventloop, self._inputhook =\
488 get_inputhook_name_and_func(gui)
500 get_inputhook_name_and_func(gui)
489 else:
501 else:
490 self.active_eventloop = self._inputhook = None
502 self.active_eventloop = self._inputhook = None
491
503
492 # Run !system commands directly, not through pipes, so terminal programs
504 # Run !system commands directly, not through pipes, so terminal programs
493 # work correctly.
505 # work correctly.
494 system = InteractiveShell.system_raw
506 system = InteractiveShell.system_raw
495
507
496 def auto_rewrite_input(self, cmd):
508 def auto_rewrite_input(self, cmd):
497 """Overridden from the parent class to use fancy rewriting prompt"""
509 """Overridden from the parent class to use fancy rewriting prompt"""
498 if not self.show_rewritten_input:
510 if not self.show_rewritten_input:
499 return
511 return
500
512
501 tokens = self.prompts.rewrite_prompt_tokens()
513 tokens = self.prompts.rewrite_prompt_tokens()
502 if self.pt_cli:
514 if self.pt_cli:
503 self.pt_cli.print_tokens(tokens)
515 self.pt_cli.print_tokens(tokens)
504 print(cmd)
516 print(cmd)
505 else:
517 else:
506 prompt = ''.join(s for t, s in tokens)
518 prompt = ''.join(s for t, s in tokens)
507 print(prompt, cmd, sep='')
519 print(prompt, cmd, sep='')
508
520
509 _prompts_before = None
521 _prompts_before = None
510 def switch_doctest_mode(self, mode):
522 def switch_doctest_mode(self, mode):
511 """Switch prompts to classic for %doctest_mode"""
523 """Switch prompts to classic for %doctest_mode"""
512 if mode:
524 if mode:
513 self._prompts_before = self.prompts
525 self._prompts_before = self.prompts
514 self.prompts = ClassicPrompts(self)
526 self.prompts = ClassicPrompts(self)
515 elif self._prompts_before:
527 elif self._prompts_before:
516 self.prompts = self._prompts_before
528 self.prompts = self._prompts_before
517 self._prompts_before = None
529 self._prompts_before = None
518 self._update_layout()
530 self._update_layout()
519
531
520
532
521 InteractiveShellABC.register(TerminalInteractiveShell)
533 InteractiveShellABC.register(TerminalInteractiveShell)
522
534
523 if __name__ == '__main__':
535 if __name__ == '__main__':
524 TerminalInteractiveShell.instance().interact()
536 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now