##// END OF EJS Templates
Merge pull request #12337 from meeseeksmachine/auto-backport-of-pr-12335-on-7.x
Matthias Bussonnier -
r25759:7dc6d565 merge
parent child Browse files
Show More
@@ -1,641 +1,645 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2
2
3 import asyncio
3 import asyncio
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 input
11 from IPython.utils.py3compat import input
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
13 from IPython.utils.process import abbrev_cwd
13 from IPython.utils.process import abbrev_cwd
14 from traitlets import (
14 from traitlets import (
15 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
15 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
16 Any, validate
16 Any, validate
17 )
17 )
18
18
19 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
19 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
20 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
20 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
21 from prompt_toolkit.formatted_text import PygmentsTokens
21 from prompt_toolkit.formatted_text import PygmentsTokens
22 from prompt_toolkit.history import InMemoryHistory
22 from prompt_toolkit.history import InMemoryHistory
23 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
24 from prompt_toolkit.output import ColorDepth
24 from prompt_toolkit.output import ColorDepth
25 from prompt_toolkit.patch_stdout import patch_stdout
25 from prompt_toolkit.patch_stdout import patch_stdout
26 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
26 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
27 from prompt_toolkit.styles import DynamicStyle, merge_styles
27 from prompt_toolkit.styles import DynamicStyle, merge_styles
28 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
28 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
29 from prompt_toolkit import __version__ as ptk_version
29 from prompt_toolkit import __version__ as ptk_version
30
30
31 from pygments.styles import get_style_by_name
31 from pygments.styles import get_style_by_name
32 from pygments.style import Style
32 from pygments.style import Style
33 from pygments.token import Token
33 from pygments.token import Token
34
34
35 from .debugger import TerminalPdb, Pdb
35 from .debugger import TerminalPdb, Pdb
36 from .magics import TerminalMagics
36 from .magics import TerminalMagics
37 from .pt_inputhooks import get_inputhook_name_and_func
37 from .pt_inputhooks import get_inputhook_name_and_func
38 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
38 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
39 from .ptutils import IPythonPTCompleter, IPythonPTLexer
39 from .ptutils import IPythonPTCompleter, IPythonPTLexer
40 from .shortcuts import create_ipython_shortcuts
40 from .shortcuts import create_ipython_shortcuts
41
41
42 DISPLAY_BANNER_DEPRECATED = object()
42 DISPLAY_BANNER_DEPRECATED = object()
43 PTK3 = ptk_version.startswith('3.')
43 PTK3 = ptk_version.startswith('3.')
44
44
45
45
46 class _NoStyle(Style): pass
46 class _NoStyle(Style): pass
47
47
48
48
49
49
50 _style_overrides_light_bg = {
50 _style_overrides_light_bg = {
51 Token.Prompt: '#0000ff',
51 Token.Prompt: '#0000ff',
52 Token.PromptNum: '#0000ee bold',
52 Token.PromptNum: '#0000ee bold',
53 Token.OutPrompt: '#cc0000',
53 Token.OutPrompt: '#cc0000',
54 Token.OutPromptNum: '#bb0000 bold',
54 Token.OutPromptNum: '#bb0000 bold',
55 }
55 }
56
56
57 _style_overrides_linux = {
57 _style_overrides_linux = {
58 Token.Prompt: '#00cc00',
58 Token.Prompt: '#00cc00',
59 Token.PromptNum: '#00bb00 bold',
59 Token.PromptNum: '#00bb00 bold',
60 Token.OutPrompt: '#cc0000',
60 Token.OutPrompt: '#cc0000',
61 Token.OutPromptNum: '#bb0000 bold',
61 Token.OutPromptNum: '#bb0000 bold',
62 }
62 }
63
63
64 def get_default_editor():
64 def get_default_editor():
65 try:
65 try:
66 return os.environ['EDITOR']
66 return os.environ['EDITOR']
67 except KeyError:
67 except KeyError:
68 pass
68 pass
69 except UnicodeError:
69 except UnicodeError:
70 warn("$EDITOR environment variable is not pure ASCII. Using platform "
70 warn("$EDITOR environment variable is not pure ASCII. Using platform "
71 "default editor.")
71 "default editor.")
72
72
73 if os.name == 'posix':
73 if os.name == 'posix':
74 return 'vi' # the only one guaranteed to be there!
74 return 'vi' # the only one guaranteed to be there!
75 else:
75 else:
76 return 'notepad' # same in Windows!
76 return 'notepad' # same in Windows!
77
77
78 # conservatively check for tty
78 # conservatively check for tty
79 # overridden streams can result in things like:
79 # overridden streams can result in things like:
80 # - sys.stdin = None
80 # - sys.stdin = None
81 # - no isatty method
81 # - no isatty method
82 for _name in ('stdin', 'stdout', 'stderr'):
82 for _name in ('stdin', 'stdout', 'stderr'):
83 _stream = getattr(sys, _name)
83 _stream = getattr(sys, _name)
84 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
84 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
85 _is_tty = False
85 _is_tty = False
86 break
86 break
87 else:
87 else:
88 _is_tty = True
88 _is_tty = True
89
89
90
90
91 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
91 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
92
92
93 def black_reformat_handler(text_before_cursor):
93 def black_reformat_handler(text_before_cursor):
94 import black
94 import black
95 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
95 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
96 if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'):
96 if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'):
97 formatted_text = formatted_text[:-1]
97 formatted_text = formatted_text[:-1]
98 return formatted_text
98 return formatted_text
99
99
100
100
101 class TerminalInteractiveShell(InteractiveShell):
101 class TerminalInteractiveShell(InteractiveShell):
102 mime_renderers = Dict().tag(config=True)
102 mime_renderers = Dict().tag(config=True)
103
103
104 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
104 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
105 'to reserve for the completion menu'
105 'to reserve for the tab completion menu, '
106 'search history, ...etc, the height of '
107 'these menus will at most this value. '
108 'Increase it is you prefer long and skinny '
109 'menus, decrease for short and wide.'
106 ).tag(config=True)
110 ).tag(config=True)
107
111
108 pt_app = None
112 pt_app = None
109 debugger_history = None
113 debugger_history = None
110
114
111 simple_prompt = Bool(_use_simple_prompt,
115 simple_prompt = Bool(_use_simple_prompt,
112 help="""Use `raw_input` for the REPL, without completion and prompt colors.
116 help="""Use `raw_input` for the REPL, without completion and prompt colors.
113
117
114 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
118 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
115 IPython own testing machinery, and emacs inferior-shell integration through elpy.
119 IPython own testing machinery, and emacs inferior-shell integration through elpy.
116
120
117 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
121 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
118 environment variable is set, or the current terminal is not a tty."""
122 environment variable is set, or the current terminal is not a tty."""
119 ).tag(config=True)
123 ).tag(config=True)
120
124
121 @property
125 @property
122 def debugger_cls(self):
126 def debugger_cls(self):
123 return Pdb if self.simple_prompt else TerminalPdb
127 return Pdb if self.simple_prompt else TerminalPdb
124
128
125 confirm_exit = Bool(True,
129 confirm_exit = Bool(True,
126 help="""
130 help="""
127 Set to confirm when you try to exit IPython with an EOF (Control-D
131 Set to confirm when you try to exit IPython with an EOF (Control-D
128 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
132 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
129 you can force a direct exit without any confirmation.""",
133 you can force a direct exit without any confirmation.""",
130 ).tag(config=True)
134 ).tag(config=True)
131
135
132 editing_mode = Unicode('emacs',
136 editing_mode = Unicode('emacs',
133 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
137 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
134 ).tag(config=True)
138 ).tag(config=True)
135
139
136 autoformatter = Unicode(None,
140 autoformatter = Unicode(None,
137 help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`",
141 help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`",
138 allow_none=True
142 allow_none=True
139 ).tag(config=True)
143 ).tag(config=True)
140
144
141 mouse_support = Bool(False,
145 mouse_support = Bool(False,
142 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
146 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
143 ).tag(config=True)
147 ).tag(config=True)
144
148
145 # We don't load the list of styles for the help string, because loading
149 # We don't load the list of styles for the help string, because loading
146 # Pygments plugins takes time and can cause unexpected errors.
150 # Pygments plugins takes time and can cause unexpected errors.
147 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
151 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
148 help="""The name or class of a Pygments style to use for syntax
152 help="""The name or class of a Pygments style to use for syntax
149 highlighting. To see available styles, run `pygmentize -L styles`."""
153 highlighting. To see available styles, run `pygmentize -L styles`."""
150 ).tag(config=True)
154 ).tag(config=True)
151
155
152 @validate('editing_mode')
156 @validate('editing_mode')
153 def _validate_editing_mode(self, proposal):
157 def _validate_editing_mode(self, proposal):
154 if proposal['value'].lower() == 'vim':
158 if proposal['value'].lower() == 'vim':
155 proposal['value']= 'vi'
159 proposal['value']= 'vi'
156 elif proposal['value'].lower() == 'default':
160 elif proposal['value'].lower() == 'default':
157 proposal['value']= 'emacs'
161 proposal['value']= 'emacs'
158
162
159 if hasattr(EditingMode, proposal['value'].upper()):
163 if hasattr(EditingMode, proposal['value'].upper()):
160 return proposal['value'].lower()
164 return proposal['value'].lower()
161
165
162 return self.editing_mode
166 return self.editing_mode
163
167
164
168
165 @observe('editing_mode')
169 @observe('editing_mode')
166 def _editing_mode(self, change):
170 def _editing_mode(self, change):
167 u_mode = change.new.upper()
171 u_mode = change.new.upper()
168 if self.pt_app:
172 if self.pt_app:
169 self.pt_app.editing_mode = u_mode
173 self.pt_app.editing_mode = u_mode
170
174
171 @observe('autoformatter')
175 @observe('autoformatter')
172 def _autoformatter_changed(self, change):
176 def _autoformatter_changed(self, change):
173 formatter = change.new
177 formatter = change.new
174 if formatter is None:
178 if formatter is None:
175 self.reformat_handler = lambda x:x
179 self.reformat_handler = lambda x:x
176 elif formatter == 'black':
180 elif formatter == 'black':
177 self.reformat_handler = black_reformat_handler
181 self.reformat_handler = black_reformat_handler
178 else:
182 else:
179 raise ValueError
183 raise ValueError
180
184
181 @observe('highlighting_style')
185 @observe('highlighting_style')
182 @observe('colors')
186 @observe('colors')
183 def _highlighting_style_changed(self, change):
187 def _highlighting_style_changed(self, change):
184 self.refresh_style()
188 self.refresh_style()
185
189
186 def refresh_style(self):
190 def refresh_style(self):
187 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
191 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
188
192
189
193
190 highlighting_style_overrides = Dict(
194 highlighting_style_overrides = Dict(
191 help="Override highlighting format for specific tokens"
195 help="Override highlighting format for specific tokens"
192 ).tag(config=True)
196 ).tag(config=True)
193
197
194 true_color = Bool(False,
198 true_color = Bool(False,
195 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
199 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
196 "If your terminal supports true color, the following command "
200 "If your terminal supports true color, the following command "
197 "should print 'TRUECOLOR' in orange: "
201 "should print 'TRUECOLOR' in orange: "
198 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
202 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
199 ).tag(config=True)
203 ).tag(config=True)
200
204
201 editor = Unicode(get_default_editor(),
205 editor = Unicode(get_default_editor(),
202 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
206 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
203 ).tag(config=True)
207 ).tag(config=True)
204
208
205 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
209 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
206
210
207 prompts = Instance(Prompts)
211 prompts = Instance(Prompts)
208
212
209 @default('prompts')
213 @default('prompts')
210 def _prompts_default(self):
214 def _prompts_default(self):
211 return self.prompts_class(self)
215 return self.prompts_class(self)
212
216
213 # @observe('prompts')
217 # @observe('prompts')
214 # def _(self, change):
218 # def _(self, change):
215 # self._update_layout()
219 # self._update_layout()
216
220
217 @default('displayhook_class')
221 @default('displayhook_class')
218 def _displayhook_class_default(self):
222 def _displayhook_class_default(self):
219 return RichPromptDisplayHook
223 return RichPromptDisplayHook
220
224
221 term_title = Bool(True,
225 term_title = Bool(True,
222 help="Automatically set the terminal title"
226 help="Automatically set the terminal title"
223 ).tag(config=True)
227 ).tag(config=True)
224
228
225 term_title_format = Unicode("IPython: {cwd}",
229 term_title_format = Unicode("IPython: {cwd}",
226 help="Customize the terminal title format. This is a python format string. " +
230 help="Customize the terminal title format. This is a python format string. " +
227 "Available substitutions are: {cwd}."
231 "Available substitutions are: {cwd}."
228 ).tag(config=True)
232 ).tag(config=True)
229
233
230 display_completions = Enum(('column', 'multicolumn','readlinelike'),
234 display_completions = Enum(('column', 'multicolumn','readlinelike'),
231 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
235 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
232 "'readlinelike'. These options are for `prompt_toolkit`, see "
236 "'readlinelike'. These options are for `prompt_toolkit`, see "
233 "`prompt_toolkit` documentation for more information."
237 "`prompt_toolkit` documentation for more information."
234 ),
238 ),
235 default_value='multicolumn').tag(config=True)
239 default_value='multicolumn').tag(config=True)
236
240
237 highlight_matching_brackets = Bool(True,
241 highlight_matching_brackets = Bool(True,
238 help="Highlight matching brackets.",
242 help="Highlight matching brackets.",
239 ).tag(config=True)
243 ).tag(config=True)
240
244
241 extra_open_editor_shortcuts = Bool(False,
245 extra_open_editor_shortcuts = Bool(False,
242 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
246 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
243 "This is in addition to the F2 binding, which is always enabled."
247 "This is in addition to the F2 binding, which is always enabled."
244 ).tag(config=True)
248 ).tag(config=True)
245
249
246 handle_return = Any(None,
250 handle_return = Any(None,
247 help="Provide an alternative handler to be called when the user presses "
251 help="Provide an alternative handler to be called when the user presses "
248 "Return. This is an advanced option intended for debugging, which "
252 "Return. This is an advanced option intended for debugging, which "
249 "may be changed or removed in later releases."
253 "may be changed or removed in later releases."
250 ).tag(config=True)
254 ).tag(config=True)
251
255
252 enable_history_search = Bool(True,
256 enable_history_search = Bool(True,
253 help="Allows to enable/disable the prompt toolkit history search"
257 help="Allows to enable/disable the prompt toolkit history search"
254 ).tag(config=True)
258 ).tag(config=True)
255
259
256 prompt_includes_vi_mode = Bool(True,
260 prompt_includes_vi_mode = Bool(True,
257 help="Display the current vi mode (when using vi editing mode)."
261 help="Display the current vi mode (when using vi editing mode)."
258 ).tag(config=True)
262 ).tag(config=True)
259
263
260 @observe('term_title')
264 @observe('term_title')
261 def init_term_title(self, change=None):
265 def init_term_title(self, change=None):
262 # Enable or disable the terminal title.
266 # Enable or disable the terminal title.
263 if self.term_title:
267 if self.term_title:
264 toggle_set_term_title(True)
268 toggle_set_term_title(True)
265 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
269 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
266 else:
270 else:
267 toggle_set_term_title(False)
271 toggle_set_term_title(False)
268
272
269 def restore_term_title(self):
273 def restore_term_title(self):
270 if self.term_title:
274 if self.term_title:
271 restore_term_title()
275 restore_term_title()
272
276
273 def init_display_formatter(self):
277 def init_display_formatter(self):
274 super(TerminalInteractiveShell, self).init_display_formatter()
278 super(TerminalInteractiveShell, self).init_display_formatter()
275 # terminal only supports plain text
279 # terminal only supports plain text
276 self.display_formatter.active_types = ['text/plain']
280 self.display_formatter.active_types = ['text/plain']
277 # disable `_ipython_display_`
281 # disable `_ipython_display_`
278 self.display_formatter.ipython_display_formatter.enabled = False
282 self.display_formatter.ipython_display_formatter.enabled = False
279
283
280 def init_prompt_toolkit_cli(self):
284 def init_prompt_toolkit_cli(self):
281 if self.simple_prompt:
285 if self.simple_prompt:
282 # Fall back to plain non-interactive output for tests.
286 # Fall back to plain non-interactive output for tests.
283 # This is very limited.
287 # This is very limited.
284 def prompt():
288 def prompt():
285 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
289 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
286 lines = [input(prompt_text)]
290 lines = [input(prompt_text)]
287 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
291 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
288 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
292 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
289 lines.append( input(prompt_continuation) )
293 lines.append( input(prompt_continuation) )
290 return '\n'.join(lines)
294 return '\n'.join(lines)
291 self.prompt_for_code = prompt
295 self.prompt_for_code = prompt
292 return
296 return
293
297
294 # Set up keyboard shortcuts
298 # Set up keyboard shortcuts
295 key_bindings = create_ipython_shortcuts(self)
299 key_bindings = create_ipython_shortcuts(self)
296
300
297 # Pre-populate history from IPython's history database
301 # Pre-populate history from IPython's history database
298 history = InMemoryHistory()
302 history = InMemoryHistory()
299 last_cell = u""
303 last_cell = u""
300 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
304 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
301 include_latest=True):
305 include_latest=True):
302 # Ignore blank lines and consecutive duplicates
306 # Ignore blank lines and consecutive duplicates
303 cell = cell.rstrip()
307 cell = cell.rstrip()
304 if cell and (cell != last_cell):
308 if cell and (cell != last_cell):
305 history.append_string(cell)
309 history.append_string(cell)
306 last_cell = cell
310 last_cell = cell
307
311
308 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
312 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
309 self.style = DynamicStyle(lambda: self._style)
313 self.style = DynamicStyle(lambda: self._style)
310
314
311 editing_mode = getattr(EditingMode, self.editing_mode.upper())
315 editing_mode = getattr(EditingMode, self.editing_mode.upper())
312
316
313 self.pt_loop = asyncio.new_event_loop()
317 self.pt_loop = asyncio.new_event_loop()
314 self.pt_app = PromptSession(
318 self.pt_app = PromptSession(
315 editing_mode=editing_mode,
319 editing_mode=editing_mode,
316 key_bindings=key_bindings,
320 key_bindings=key_bindings,
317 history=history,
321 history=history,
318 completer=IPythonPTCompleter(shell=self),
322 completer=IPythonPTCompleter(shell=self),
319 enable_history_search = self.enable_history_search,
323 enable_history_search = self.enable_history_search,
320 style=self.style,
324 style=self.style,
321 include_default_pygments_style=False,
325 include_default_pygments_style=False,
322 mouse_support=self.mouse_support,
326 mouse_support=self.mouse_support,
323 enable_open_in_editor=self.extra_open_editor_shortcuts,
327 enable_open_in_editor=self.extra_open_editor_shortcuts,
324 color_depth=self.color_depth,
328 color_depth=self.color_depth,
325 tempfile_suffix=".py",
329 tempfile_suffix=".py",
326 **self._extra_prompt_options())
330 **self._extra_prompt_options())
327
331
328 def _make_style_from_name_or_cls(self, name_or_cls):
332 def _make_style_from_name_or_cls(self, name_or_cls):
329 """
333 """
330 Small wrapper that make an IPython compatible style from a style name
334 Small wrapper that make an IPython compatible style from a style name
331
335
332 We need that to add style for prompt ... etc.
336 We need that to add style for prompt ... etc.
333 """
337 """
334 style_overrides = {}
338 style_overrides = {}
335 if name_or_cls == 'legacy':
339 if name_or_cls == 'legacy':
336 legacy = self.colors.lower()
340 legacy = self.colors.lower()
337 if legacy == 'linux':
341 if legacy == 'linux':
338 style_cls = get_style_by_name('monokai')
342 style_cls = get_style_by_name('monokai')
339 style_overrides = _style_overrides_linux
343 style_overrides = _style_overrides_linux
340 elif legacy == 'lightbg':
344 elif legacy == 'lightbg':
341 style_overrides = _style_overrides_light_bg
345 style_overrides = _style_overrides_light_bg
342 style_cls = get_style_by_name('pastie')
346 style_cls = get_style_by_name('pastie')
343 elif legacy == 'neutral':
347 elif legacy == 'neutral':
344 # The default theme needs to be visible on both a dark background
348 # The default theme needs to be visible on both a dark background
345 # and a light background, because we can't tell what the terminal
349 # and a light background, because we can't tell what the terminal
346 # looks like. These tweaks to the default theme help with that.
350 # looks like. These tweaks to the default theme help with that.
347 style_cls = get_style_by_name('default')
351 style_cls = get_style_by_name('default')
348 style_overrides.update({
352 style_overrides.update({
349 Token.Number: '#007700',
353 Token.Number: '#007700',
350 Token.Operator: 'noinherit',
354 Token.Operator: 'noinherit',
351 Token.String: '#BB6622',
355 Token.String: '#BB6622',
352 Token.Name.Function: '#2080D0',
356 Token.Name.Function: '#2080D0',
353 Token.Name.Class: 'bold #2080D0',
357 Token.Name.Class: 'bold #2080D0',
354 Token.Name.Namespace: 'bold #2080D0',
358 Token.Name.Namespace: 'bold #2080D0',
355 Token.Prompt: '#009900',
359 Token.Prompt: '#009900',
356 Token.PromptNum: '#ansibrightgreen bold',
360 Token.PromptNum: '#ansibrightgreen bold',
357 Token.OutPrompt: '#990000',
361 Token.OutPrompt: '#990000',
358 Token.OutPromptNum: '#ansibrightred bold',
362 Token.OutPromptNum: '#ansibrightred bold',
359 })
363 })
360
364
361 # Hack: Due to limited color support on the Windows console
365 # Hack: Due to limited color support on the Windows console
362 # the prompt colors will be wrong without this
366 # the prompt colors will be wrong without this
363 if os.name == 'nt':
367 if os.name == 'nt':
364 style_overrides.update({
368 style_overrides.update({
365 Token.Prompt: '#ansidarkgreen',
369 Token.Prompt: '#ansidarkgreen',
366 Token.PromptNum: '#ansigreen bold',
370 Token.PromptNum: '#ansigreen bold',
367 Token.OutPrompt: '#ansidarkred',
371 Token.OutPrompt: '#ansidarkred',
368 Token.OutPromptNum: '#ansired bold',
372 Token.OutPromptNum: '#ansired bold',
369 })
373 })
370 elif legacy =='nocolor':
374 elif legacy =='nocolor':
371 style_cls=_NoStyle
375 style_cls=_NoStyle
372 style_overrides = {}
376 style_overrides = {}
373 else :
377 else :
374 raise ValueError('Got unknown colors: ', legacy)
378 raise ValueError('Got unknown colors: ', legacy)
375 else :
379 else :
376 if isinstance(name_or_cls, str):
380 if isinstance(name_or_cls, str):
377 style_cls = get_style_by_name(name_or_cls)
381 style_cls = get_style_by_name(name_or_cls)
378 else:
382 else:
379 style_cls = name_or_cls
383 style_cls = name_or_cls
380 style_overrides = {
384 style_overrides = {
381 Token.Prompt: '#009900',
385 Token.Prompt: '#009900',
382 Token.PromptNum: '#ansibrightgreen bold',
386 Token.PromptNum: '#ansibrightgreen bold',
383 Token.OutPrompt: '#990000',
387 Token.OutPrompt: '#990000',
384 Token.OutPromptNum: '#ansibrightred bold',
388 Token.OutPromptNum: '#ansibrightred bold',
385 }
389 }
386 style_overrides.update(self.highlighting_style_overrides)
390 style_overrides.update(self.highlighting_style_overrides)
387 style = merge_styles([
391 style = merge_styles([
388 style_from_pygments_cls(style_cls),
392 style_from_pygments_cls(style_cls),
389 style_from_pygments_dict(style_overrides),
393 style_from_pygments_dict(style_overrides),
390 ])
394 ])
391
395
392 return style
396 return style
393
397
394 @property
398 @property
395 def pt_complete_style(self):
399 def pt_complete_style(self):
396 return {
400 return {
397 'multicolumn': CompleteStyle.MULTI_COLUMN,
401 'multicolumn': CompleteStyle.MULTI_COLUMN,
398 'column': CompleteStyle.COLUMN,
402 'column': CompleteStyle.COLUMN,
399 'readlinelike': CompleteStyle.READLINE_LIKE,
403 'readlinelike': CompleteStyle.READLINE_LIKE,
400 }[self.display_completions]
404 }[self.display_completions]
401
405
402 @property
406 @property
403 def color_depth(self):
407 def color_depth(self):
404 return (ColorDepth.TRUE_COLOR if self.true_color else None)
408 return (ColorDepth.TRUE_COLOR if self.true_color else None)
405
409
406 def _extra_prompt_options(self):
410 def _extra_prompt_options(self):
407 """
411 """
408 Return the current layout option for the current Terminal InteractiveShell
412 Return the current layout option for the current Terminal InteractiveShell
409 """
413 """
410 def get_message():
414 def get_message():
411 return PygmentsTokens(self.prompts.in_prompt_tokens())
415 return PygmentsTokens(self.prompts.in_prompt_tokens())
412
416
413 if self.editing_mode == 'emacs':
417 if self.editing_mode == 'emacs':
414 # with emacs mode the prompt is (usually) static, so we call only
418 # with emacs mode the prompt is (usually) static, so we call only
415 # the function once. With VI mode it can toggle between [ins] and
419 # the function once. With VI mode it can toggle between [ins] and
416 # [nor] so we can't precompute.
420 # [nor] so we can't precompute.
417 # here I'm going to favor the default keybinding which almost
421 # here I'm going to favor the default keybinding which almost
418 # everybody uses to decrease CPU usage.
422 # everybody uses to decrease CPU usage.
419 # if we have issues with users with custom Prompts we can see how to
423 # if we have issues with users with custom Prompts we can see how to
420 # work around this.
424 # work around this.
421 get_message = get_message()
425 get_message = get_message()
422
426
423 options = {
427 options = {
424 'complete_in_thread': False,
428 'complete_in_thread': False,
425 'lexer':IPythonPTLexer(),
429 'lexer':IPythonPTLexer(),
426 'reserve_space_for_menu':self.space_for_menu,
430 'reserve_space_for_menu':self.space_for_menu,
427 'message': get_message,
431 'message': get_message,
428 'prompt_continuation': (
432 'prompt_continuation': (
429 lambda width, lineno, is_soft_wrap:
433 lambda width, lineno, is_soft_wrap:
430 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
434 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
431 'multiline': True,
435 'multiline': True,
432 'complete_style': self.pt_complete_style,
436 'complete_style': self.pt_complete_style,
433
437
434 # Highlight matching brackets, but only when this setting is
438 # Highlight matching brackets, but only when this setting is
435 # enabled, and only when the DEFAULT_BUFFER has the focus.
439 # enabled, and only when the DEFAULT_BUFFER has the focus.
436 'input_processors': [ConditionalProcessor(
440 'input_processors': [ConditionalProcessor(
437 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
441 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
438 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
442 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
439 Condition(lambda: self.highlight_matching_brackets))],
443 Condition(lambda: self.highlight_matching_brackets))],
440 }
444 }
441 if not PTK3:
445 if not PTK3:
442 options['inputhook'] = self.inputhook
446 options['inputhook'] = self.inputhook
443
447
444 return options
448 return options
445
449
446 def prompt_for_code(self):
450 def prompt_for_code(self):
447 if self.rl_next_input:
451 if self.rl_next_input:
448 default = self.rl_next_input
452 default = self.rl_next_input
449 self.rl_next_input = None
453 self.rl_next_input = None
450 else:
454 else:
451 default = ''
455 default = ''
452
456
453 # In order to make sure that asyncio code written in the
457 # In order to make sure that asyncio code written in the
454 # interactive shell doesn't interfere with the prompt, we run the
458 # interactive shell doesn't interfere with the prompt, we run the
455 # prompt in a different event loop.
459 # prompt in a different event loop.
456 # If we don't do this, people could spawn coroutine with a
460 # If we don't do this, people could spawn coroutine with a
457 # while/true inside which will freeze the prompt.
461 # while/true inside which will freeze the prompt.
458
462
459 try:
463 try:
460 old_loop = asyncio.get_event_loop()
464 old_loop = asyncio.get_event_loop()
461 except RuntimeError:
465 except RuntimeError:
462 # This happens when the user used `asyncio.run()`.
466 # This happens when the user used `asyncio.run()`.
463 old_loop = None
467 old_loop = None
464
468
465 asyncio.set_event_loop(self.pt_loop)
469 asyncio.set_event_loop(self.pt_loop)
466 try:
470 try:
467 with patch_stdout(raw=True):
471 with patch_stdout(raw=True):
468 text = self.pt_app.prompt(
472 text = self.pt_app.prompt(
469 default=default,
473 default=default,
470 **self._extra_prompt_options())
474 **self._extra_prompt_options())
471 finally:
475 finally:
472 # Restore the original event loop.
476 # Restore the original event loop.
473 asyncio.set_event_loop(old_loop)
477 asyncio.set_event_loop(old_loop)
474
478
475 return text
479 return text
476
480
477 def enable_win_unicode_console(self):
481 def enable_win_unicode_console(self):
478 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
482 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
479 # console by default, so WUC shouldn't be needed.
483 # console by default, so WUC shouldn't be needed.
480 from warnings import warn
484 from warnings import warn
481 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
485 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
482 DeprecationWarning,
486 DeprecationWarning,
483 stacklevel=2)
487 stacklevel=2)
484
488
485 def init_io(self):
489 def init_io(self):
486 if sys.platform not in {'win32', 'cli'}:
490 if sys.platform not in {'win32', 'cli'}:
487 return
491 return
488
492
489 import colorama
493 import colorama
490 colorama.init()
494 colorama.init()
491
495
492 # For some reason we make these wrappers around stdout/stderr.
496 # For some reason we make these wrappers around stdout/stderr.
493 # For now, we need to reset them so all output gets coloured.
497 # For now, we need to reset them so all output gets coloured.
494 # https://github.com/ipython/ipython/issues/8669
498 # https://github.com/ipython/ipython/issues/8669
495 # io.std* are deprecated, but don't show our own deprecation warnings
499 # io.std* are deprecated, but don't show our own deprecation warnings
496 # during initialization of the deprecated API.
500 # during initialization of the deprecated API.
497 with warnings.catch_warnings():
501 with warnings.catch_warnings():
498 warnings.simplefilter('ignore', DeprecationWarning)
502 warnings.simplefilter('ignore', DeprecationWarning)
499 io.stdout = io.IOStream(sys.stdout)
503 io.stdout = io.IOStream(sys.stdout)
500 io.stderr = io.IOStream(sys.stderr)
504 io.stderr = io.IOStream(sys.stderr)
501
505
502 def init_magics(self):
506 def init_magics(self):
503 super(TerminalInteractiveShell, self).init_magics()
507 super(TerminalInteractiveShell, self).init_magics()
504 self.register_magics(TerminalMagics)
508 self.register_magics(TerminalMagics)
505
509
506 def init_alias(self):
510 def init_alias(self):
507 # The parent class defines aliases that can be safely used with any
511 # The parent class defines aliases that can be safely used with any
508 # frontend.
512 # frontend.
509 super(TerminalInteractiveShell, self).init_alias()
513 super(TerminalInteractiveShell, self).init_alias()
510
514
511 # Now define aliases that only make sense on the terminal, because they
515 # Now define aliases that only make sense on the terminal, because they
512 # need direct access to the console in a way that we can't emulate in
516 # need direct access to the console in a way that we can't emulate in
513 # GUI or web frontend
517 # GUI or web frontend
514 if os.name == 'posix':
518 if os.name == 'posix':
515 for cmd in ('clear', 'more', 'less', 'man'):
519 for cmd in ('clear', 'more', 'less', 'man'):
516 self.alias_manager.soft_define_alias(cmd, cmd)
520 self.alias_manager.soft_define_alias(cmd, cmd)
517
521
518
522
519 def __init__(self, *args, **kwargs):
523 def __init__(self, *args, **kwargs):
520 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
524 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
521 self.init_prompt_toolkit_cli()
525 self.init_prompt_toolkit_cli()
522 self.init_term_title()
526 self.init_term_title()
523 self.keep_running = True
527 self.keep_running = True
524
528
525 self.debugger_history = InMemoryHistory()
529 self.debugger_history = InMemoryHistory()
526
530
527 def ask_exit(self):
531 def ask_exit(self):
528 self.keep_running = False
532 self.keep_running = False
529
533
530 rl_next_input = None
534 rl_next_input = None
531
535
532 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
536 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
533
537
534 if display_banner is not DISPLAY_BANNER_DEPRECATED:
538 if display_banner is not DISPLAY_BANNER_DEPRECATED:
535 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
539 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
536
540
537 self.keep_running = True
541 self.keep_running = True
538 while self.keep_running:
542 while self.keep_running:
539 print(self.separate_in, end='')
543 print(self.separate_in, end='')
540
544
541 try:
545 try:
542 code = self.prompt_for_code()
546 code = self.prompt_for_code()
543 except EOFError:
547 except EOFError:
544 if (not self.confirm_exit) \
548 if (not self.confirm_exit) \
545 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
549 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
546 self.ask_exit()
550 self.ask_exit()
547
551
548 else:
552 else:
549 if code:
553 if code:
550 self.run_cell(code, store_history=True)
554 self.run_cell(code, store_history=True)
551
555
552 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
556 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
553 # An extra layer of protection in case someone mashing Ctrl-C breaks
557 # An extra layer of protection in case someone mashing Ctrl-C breaks
554 # out of our internal code.
558 # out of our internal code.
555 if display_banner is not DISPLAY_BANNER_DEPRECATED:
559 if display_banner is not DISPLAY_BANNER_DEPRECATED:
556 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
560 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
557 while True:
561 while True:
558 try:
562 try:
559 self.interact()
563 self.interact()
560 break
564 break
561 except KeyboardInterrupt as e:
565 except KeyboardInterrupt as e:
562 print("\n%s escaped interact()\n" % type(e).__name__)
566 print("\n%s escaped interact()\n" % type(e).__name__)
563 finally:
567 finally:
564 # An interrupt during the eventloop will mess up the
568 # An interrupt during the eventloop will mess up the
565 # internal state of the prompt_toolkit library.
569 # internal state of the prompt_toolkit library.
566 # Stopping the eventloop fixes this, see
570 # Stopping the eventloop fixes this, see
567 # https://github.com/ipython/ipython/pull/9867
571 # https://github.com/ipython/ipython/pull/9867
568 if hasattr(self, '_eventloop'):
572 if hasattr(self, '_eventloop'):
569 self._eventloop.stop()
573 self._eventloop.stop()
570
574
571 self.restore_term_title()
575 self.restore_term_title()
572
576
573
577
574 _inputhook = None
578 _inputhook = None
575 def inputhook(self, context):
579 def inputhook(self, context):
576 if self._inputhook is not None:
580 if self._inputhook is not None:
577 self._inputhook(context)
581 self._inputhook(context)
578
582
579 active_eventloop = None
583 active_eventloop = None
580 def enable_gui(self, gui=None):
584 def enable_gui(self, gui=None):
581 if gui and (gui != 'inline') :
585 if gui and (gui != 'inline') :
582 self.active_eventloop, self._inputhook =\
586 self.active_eventloop, self._inputhook =\
583 get_inputhook_name_and_func(gui)
587 get_inputhook_name_and_func(gui)
584 else:
588 else:
585 self.active_eventloop = self._inputhook = None
589 self.active_eventloop = self._inputhook = None
586
590
587 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
591 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
588 # this inputhook.
592 # this inputhook.
589 if PTK3:
593 if PTK3:
590 import asyncio
594 import asyncio
591 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
595 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
592
596
593 if gui == 'asyncio':
597 if gui == 'asyncio':
594 # When we integrate the asyncio event loop, run the UI in the
598 # When we integrate the asyncio event loop, run the UI in the
595 # same event loop as the rest of the code. don't use an actual
599 # same event loop as the rest of the code. don't use an actual
596 # input hook. (Asyncio is not made for nesting event loops.)
600 # input hook. (Asyncio is not made for nesting event loops.)
597 self.pt_loop = asyncio.get_event_loop()
601 self.pt_loop = asyncio.get_event_loop()
598
602
599 elif self._inputhook:
603 elif self._inputhook:
600 # If an inputhook was set, create a new asyncio event loop with
604 # If an inputhook was set, create a new asyncio event loop with
601 # this inputhook for the prompt.
605 # this inputhook for the prompt.
602 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
606 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
603 else:
607 else:
604 # When there's no inputhook, run the prompt in a separate
608 # When there's no inputhook, run the prompt in a separate
605 # asyncio event loop.
609 # asyncio event loop.
606 self.pt_loop = asyncio.new_event_loop()
610 self.pt_loop = asyncio.new_event_loop()
607
611
608 # Run !system commands directly, not through pipes, so terminal programs
612 # Run !system commands directly, not through pipes, so terminal programs
609 # work correctly.
613 # work correctly.
610 system = InteractiveShell.system_raw
614 system = InteractiveShell.system_raw
611
615
612 def auto_rewrite_input(self, cmd):
616 def auto_rewrite_input(self, cmd):
613 """Overridden from the parent class to use fancy rewriting prompt"""
617 """Overridden from the parent class to use fancy rewriting prompt"""
614 if not self.show_rewritten_input:
618 if not self.show_rewritten_input:
615 return
619 return
616
620
617 tokens = self.prompts.rewrite_prompt_tokens()
621 tokens = self.prompts.rewrite_prompt_tokens()
618 if self.pt_app:
622 if self.pt_app:
619 print_formatted_text(PygmentsTokens(tokens), end='',
623 print_formatted_text(PygmentsTokens(tokens), end='',
620 style=self.pt_app.app.style)
624 style=self.pt_app.app.style)
621 print(cmd)
625 print(cmd)
622 else:
626 else:
623 prompt = ''.join(s for t, s in tokens)
627 prompt = ''.join(s for t, s in tokens)
624 print(prompt, cmd, sep='')
628 print(prompt, cmd, sep='')
625
629
626 _prompts_before = None
630 _prompts_before = None
627 def switch_doctest_mode(self, mode):
631 def switch_doctest_mode(self, mode):
628 """Switch prompts to classic for %doctest_mode"""
632 """Switch prompts to classic for %doctest_mode"""
629 if mode:
633 if mode:
630 self._prompts_before = self.prompts
634 self._prompts_before = self.prompts
631 self.prompts = ClassicPrompts(self)
635 self.prompts = ClassicPrompts(self)
632 elif self._prompts_before:
636 elif self._prompts_before:
633 self.prompts = self._prompts_before
637 self.prompts = self._prompts_before
634 self._prompts_before = None
638 self._prompts_before = None
635 # self._update_layout()
639 # self._update_layout()
636
640
637
641
638 InteractiveShellABC.register(TerminalInteractiveShell)
642 InteractiveShellABC.register(TerminalInteractiveShell)
639
643
640 if __name__ == '__main__':
644 if __name__ == '__main__':
641 TerminalInteractiveShell.instance().interact()
645 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now