##// END OF EJS Templates
and reorder import
M Bussonnier -
Show More
@@ -1,1023 +1,1023
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2
2
3 import os
3 import os
4 import sys
4 import sys
5 import inspect
5 import inspect
6 from warnings import warn
6 from warnings import warn
7 from typing import Union as UnionType, Optional
7 from typing import Union as UnionType, Optional
8
8
9 from IPython.core.async_helpers import get_asyncio_loop
9 from IPython.core.async_helpers import get_asyncio_loop
10 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
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 Any,
15 Bool,
16 Bool,
16 Unicode,
17 Dict,
17 Dict,
18 Enum,
19 Float,
20 Instance,
18 Integer,
21 Integer,
19 List,
22 List,
20 observe,
21 Instance,
22 Type,
23 Type,
23 default,
24 Unicode,
24 Enum,
25 Union,
25 Union,
26 Any,
26 default,
27 observe,
27 validate,
28 validate,
28 Float,
29 )
29 )
30
30
31 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
31 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
32 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
32 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
33 from prompt_toolkit.filters import HasFocus, Condition, IsDone
33 from prompt_toolkit.filters import HasFocus, Condition, IsDone
34 from prompt_toolkit.formatted_text import PygmentsTokens
34 from prompt_toolkit.formatted_text import PygmentsTokens
35 from prompt_toolkit.history import History
35 from prompt_toolkit.history import History
36 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
36 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
37 from prompt_toolkit.output import ColorDepth
37 from prompt_toolkit.output import ColorDepth
38 from prompt_toolkit.patch_stdout import patch_stdout
38 from prompt_toolkit.patch_stdout import patch_stdout
39 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
39 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
40 from prompt_toolkit.styles import DynamicStyle, merge_styles
40 from prompt_toolkit.styles import DynamicStyle, merge_styles
41 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
41 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
42 from prompt_toolkit import __version__ as ptk_version
42 from prompt_toolkit import __version__ as ptk_version
43
43
44 from pygments.styles import get_style_by_name
44 from pygments.styles import get_style_by_name
45 from pygments.style import Style
45 from pygments.style import Style
46 from pygments.token import Token
46 from pygments.token import Token
47
47
48 from .debugger import TerminalPdb, Pdb
48 from .debugger import TerminalPdb, Pdb
49 from .magics import TerminalMagics
49 from .magics import TerminalMagics
50 from .pt_inputhooks import get_inputhook_name_and_func
50 from .pt_inputhooks import get_inputhook_name_and_func
51 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
51 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
52 from .ptutils import IPythonPTCompleter, IPythonPTLexer
52 from .ptutils import IPythonPTCompleter, IPythonPTLexer
53 from .shortcuts import (
53 from .shortcuts import (
54 KEY_BINDINGS,
54 KEY_BINDINGS,
55 UNASSIGNED_ALLOWED_COMMANDS,
55 UNASSIGNED_ALLOWED_COMMANDS,
56 create_ipython_shortcuts,
56 create_ipython_shortcuts,
57 create_identifier,
57 create_identifier,
58 RuntimeBinding,
58 RuntimeBinding,
59 add_binding,
59 add_binding,
60 )
60 )
61 from .shortcuts.filters import KEYBINDING_FILTERS, filter_from_string
61 from .shortcuts.filters import KEYBINDING_FILTERS, filter_from_string
62 from .shortcuts.auto_suggest import (
62 from .shortcuts.auto_suggest import (
63 NavigableAutoSuggestFromHistory,
63 NavigableAutoSuggestFromHistory,
64 AppendAutoSuggestionInAnyLine,
64 AppendAutoSuggestionInAnyLine,
65 )
65 )
66
66
67 PTK3 = ptk_version.startswith('3.')
67 PTK3 = ptk_version.startswith('3.')
68
68
69
69
70 class _NoStyle(Style):
70 class _NoStyle(Style):
71 pass
71 pass
72
72
73
73
74 _style_overrides_light_bg = {
74 _style_overrides_light_bg = {
75 Token.Prompt: '#ansibrightblue',
75 Token.Prompt: '#ansibrightblue',
76 Token.PromptNum: '#ansiblue bold',
76 Token.PromptNum: '#ansiblue bold',
77 Token.OutPrompt: '#ansibrightred',
77 Token.OutPrompt: '#ansibrightred',
78 Token.OutPromptNum: '#ansired bold',
78 Token.OutPromptNum: '#ansired bold',
79 }
79 }
80
80
81 _style_overrides_linux = {
81 _style_overrides_linux = {
82 Token.Prompt: '#ansibrightgreen',
82 Token.Prompt: '#ansibrightgreen',
83 Token.PromptNum: '#ansigreen bold',
83 Token.PromptNum: '#ansigreen bold',
84 Token.OutPrompt: '#ansibrightred',
84 Token.OutPrompt: '#ansibrightred',
85 Token.OutPromptNum: '#ansired bold',
85 Token.OutPromptNum: '#ansired bold',
86 }
86 }
87
87
88
88
89 def _backward_compat_continuation_prompt_tokens(method, width: int, *, lineno: int):
89 def _backward_compat_continuation_prompt_tokens(method, width: int, *, lineno: int):
90 """
90 """
91 Sagemath use custom prompt and we broke them in 8.19.
91 Sagemath use custom prompt and we broke them in 8.19.
92 """
92 """
93 sig = inspect.signature(method)
93 sig = inspect.signature(method)
94 if "lineno" in inspect.signature(method).parameters or any(
94 if "lineno" in inspect.signature(method).parameters or any(
95 [p.kind == p.VAR_KEYWORD for p in sig.parameters.values()]
95 [p.kind == p.VAR_KEYWORD for p in sig.parameters.values()]
96 ):
96 ):
97 return method(width, lineno=lineno)
97 return method(width, lineno=lineno)
98 else:
98 else:
99 return method(width)
99 return method(width)
100
100
101
101
102 def get_default_editor():
102 def get_default_editor():
103 try:
103 try:
104 return os.environ['EDITOR']
104 return os.environ['EDITOR']
105 except KeyError:
105 except KeyError:
106 pass
106 pass
107 except UnicodeError:
107 except UnicodeError:
108 warn("$EDITOR environment variable is not pure ASCII. Using platform "
108 warn("$EDITOR environment variable is not pure ASCII. Using platform "
109 "default editor.")
109 "default editor.")
110
110
111 if os.name == 'posix':
111 if os.name == 'posix':
112 return 'vi' # the only one guaranteed to be there!
112 return 'vi' # the only one guaranteed to be there!
113 else:
113 else:
114 return "notepad" # same in Windows!
114 return "notepad" # same in Windows!
115
115
116
116
117 # conservatively check for tty
117 # conservatively check for tty
118 # overridden streams can result in things like:
118 # overridden streams can result in things like:
119 # - sys.stdin = None
119 # - sys.stdin = None
120 # - no isatty method
120 # - no isatty method
121 for _name in ('stdin', 'stdout', 'stderr'):
121 for _name in ('stdin', 'stdout', 'stderr'):
122 _stream = getattr(sys, _name)
122 _stream = getattr(sys, _name)
123 try:
123 try:
124 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty():
124 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty():
125 _is_tty = False
125 _is_tty = False
126 break
126 break
127 except ValueError:
127 except ValueError:
128 # stream is closed
128 # stream is closed
129 _is_tty = False
129 _is_tty = False
130 break
130 break
131 else:
131 else:
132 _is_tty = True
132 _is_tty = True
133
133
134
134
135 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
135 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
136
136
137 def black_reformat_handler(text_before_cursor):
137 def black_reformat_handler(text_before_cursor):
138 """
138 """
139 We do not need to protect against error,
139 We do not need to protect against error,
140 this is taken care at a higher level where any reformat error is ignored.
140 this is taken care at a higher level where any reformat error is ignored.
141 Indeed we may call reformatting on incomplete code.
141 Indeed we may call reformatting on incomplete code.
142 """
142 """
143 import black
143 import black
144
144
145 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
145 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
146 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
146 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
147 formatted_text = formatted_text[:-1]
147 formatted_text = formatted_text[:-1]
148 return formatted_text
148 return formatted_text
149
149
150
150
151 def yapf_reformat_handler(text_before_cursor):
151 def yapf_reformat_handler(text_before_cursor):
152 from yapf.yapflib import file_resources
152 from yapf.yapflib import file_resources
153 from yapf.yapflib import yapf_api
153 from yapf.yapflib import yapf_api
154
154
155 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
155 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
156 formatted_text, was_formatted = yapf_api.FormatCode(
156 formatted_text, was_formatted = yapf_api.FormatCode(
157 text_before_cursor, style_config=style_config
157 text_before_cursor, style_config=style_config
158 )
158 )
159 if was_formatted:
159 if was_formatted:
160 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
160 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
161 formatted_text = formatted_text[:-1]
161 formatted_text = formatted_text[:-1]
162 return formatted_text
162 return formatted_text
163 else:
163 else:
164 return text_before_cursor
164 return text_before_cursor
165
165
166
166
167 class PtkHistoryAdapter(History):
167 class PtkHistoryAdapter(History):
168 """
168 """
169 Prompt toolkit has it's own way of handling history, Where it assumes it can
169 Prompt toolkit has it's own way of handling history, Where it assumes it can
170 Push/pull from history.
170 Push/pull from history.
171
171
172 """
172 """
173
173
174 def __init__(self, shell):
174 def __init__(self, shell):
175 super().__init__()
175 super().__init__()
176 self.shell = shell
176 self.shell = shell
177 self._refresh()
177 self._refresh()
178
178
179 def append_string(self, string):
179 def append_string(self, string):
180 # we rely on sql for that.
180 # we rely on sql for that.
181 self._loaded = False
181 self._loaded = False
182 self._refresh()
182 self._refresh()
183
183
184 def _refresh(self):
184 def _refresh(self):
185 if not self._loaded:
185 if not self._loaded:
186 self._loaded_strings = list(self.load_history_strings())
186 self._loaded_strings = list(self.load_history_strings())
187
187
188 def load_history_strings(self):
188 def load_history_strings(self):
189 last_cell = ""
189 last_cell = ""
190 res = []
190 res = []
191 for __, ___, cell in self.shell.history_manager.get_tail(
191 for __, ___, cell in self.shell.history_manager.get_tail(
192 self.shell.history_load_length, include_latest=True
192 self.shell.history_load_length, include_latest=True
193 ):
193 ):
194 # Ignore blank lines and consecutive duplicates
194 # Ignore blank lines and consecutive duplicates
195 cell = cell.rstrip()
195 cell = cell.rstrip()
196 if cell and (cell != last_cell):
196 if cell and (cell != last_cell):
197 res.append(cell)
197 res.append(cell)
198 last_cell = cell
198 last_cell = cell
199 yield from res[::-1]
199 yield from res[::-1]
200
200
201 def store_string(self, string: str) -> None:
201 def store_string(self, string: str) -> None:
202 pass
202 pass
203
203
204 class TerminalInteractiveShell(InteractiveShell):
204 class TerminalInteractiveShell(InteractiveShell):
205 mime_renderers = Dict().tag(config=True)
205 mime_renderers = Dict().tag(config=True)
206
206
207 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
207 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
208 'to reserve for the tab completion menu, '
208 'to reserve for the tab completion menu, '
209 'search history, ...etc, the height of '
209 'search history, ...etc, the height of '
210 'these menus will at most this value. '
210 'these menus will at most this value. '
211 'Increase it is you prefer long and skinny '
211 'Increase it is you prefer long and skinny '
212 'menus, decrease for short and wide.'
212 'menus, decrease for short and wide.'
213 ).tag(config=True)
213 ).tag(config=True)
214
214
215 pt_app: UnionType[PromptSession, None] = None
215 pt_app: UnionType[PromptSession, None] = None
216 auto_suggest: UnionType[
216 auto_suggest: UnionType[
217 AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None
217 AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None
218 ] = None
218 ] = None
219 debugger_history = None
219 debugger_history = None
220
220
221 debugger_history_file = Unicode(
221 debugger_history_file = Unicode(
222 "~/.pdbhistory", help="File in which to store and read history"
222 "~/.pdbhistory", help="File in which to store and read history"
223 ).tag(config=True)
223 ).tag(config=True)
224
224
225 simple_prompt = Bool(_use_simple_prompt,
225 simple_prompt = Bool(_use_simple_prompt,
226 help="""Use `raw_input` for the REPL, without completion and prompt colors.
226 help="""Use `raw_input` for the REPL, without completion and prompt colors.
227
227
228 Useful when controlling IPython as a subprocess, and piping
228 Useful when controlling IPython as a subprocess, and piping
229 STDIN/OUT/ERR. Known usage are: IPython's own testing machinery,
229 STDIN/OUT/ERR. Known usage are: IPython's own testing machinery,
230 and emacs' inferior-python subprocess (assuming you have set
230 and emacs' inferior-python subprocess (assuming you have set
231 `python-shell-interpreter` to "ipython") available through the
231 `python-shell-interpreter` to "ipython") available through the
232 built-in `M-x run-python` and third party packages such as elpy.
232 built-in `M-x run-python` and third party packages such as elpy.
233
233
234 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
234 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
235 environment variable is set, or the current terminal is not a tty.
235 environment variable is set, or the current terminal is not a tty.
236 Thus the Default value reported in --help-all, or config will often
236 Thus the Default value reported in --help-all, or config will often
237 be incorrectly reported.
237 be incorrectly reported.
238 """,
238 """,
239 ).tag(config=True)
239 ).tag(config=True)
240
240
241 @property
241 @property
242 def debugger_cls(self):
242 def debugger_cls(self):
243 return Pdb if self.simple_prompt else TerminalPdb
243 return Pdb if self.simple_prompt else TerminalPdb
244
244
245 confirm_exit = Bool(True,
245 confirm_exit = Bool(True,
246 help="""
246 help="""
247 Set to confirm when you try to exit IPython with an EOF (Control-D
247 Set to confirm when you try to exit IPython with an EOF (Control-D
248 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
248 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
249 you can force a direct exit without any confirmation.""",
249 you can force a direct exit without any confirmation.""",
250 ).tag(config=True)
250 ).tag(config=True)
251
251
252 editing_mode = Unicode('emacs',
252 editing_mode = Unicode('emacs',
253 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
253 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
254 ).tag(config=True)
254 ).tag(config=True)
255
255
256 emacs_bindings_in_vi_insert_mode = Bool(
256 emacs_bindings_in_vi_insert_mode = Bool(
257 True,
257 True,
258 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
258 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
259 ).tag(config=True)
259 ).tag(config=True)
260
260
261 modal_cursor = Bool(
261 modal_cursor = Bool(
262 True,
262 True,
263 help="""
263 help="""
264 Cursor shape changes depending on vi mode: beam in vi insert mode,
264 Cursor shape changes depending on vi mode: beam in vi insert mode,
265 block in nav mode, underscore in replace mode.""",
265 block in nav mode, underscore in replace mode.""",
266 ).tag(config=True)
266 ).tag(config=True)
267
267
268 ttimeoutlen = Float(
268 ttimeoutlen = Float(
269 0.01,
269 0.01,
270 help="""The time in milliseconds that is waited for a key code
270 help="""The time in milliseconds that is waited for a key code
271 to complete.""",
271 to complete.""",
272 ).tag(config=True)
272 ).tag(config=True)
273
273
274 timeoutlen = Float(
274 timeoutlen = Float(
275 0.5,
275 0.5,
276 help="""The time in milliseconds that is waited for a mapped key
276 help="""The time in milliseconds that is waited for a mapped key
277 sequence to complete.""",
277 sequence to complete.""",
278 ).tag(config=True)
278 ).tag(config=True)
279
279
280 autoformatter = Unicode(
280 autoformatter = Unicode(
281 None,
281 None,
282 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
282 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
283 allow_none=True
283 allow_none=True
284 ).tag(config=True)
284 ).tag(config=True)
285
285
286 auto_match = Bool(
286 auto_match = Bool(
287 False,
287 False,
288 help="""
288 help="""
289 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
289 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
290 Brackets: (), [], {}
290 Brackets: (), [], {}
291 Quotes: '', \"\"
291 Quotes: '', \"\"
292 """,
292 """,
293 ).tag(config=True)
293 ).tag(config=True)
294
294
295 mouse_support = Bool(False,
295 mouse_support = Bool(False,
296 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
296 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
297 ).tag(config=True)
297 ).tag(config=True)
298
298
299 # We don't load the list of styles for the help string, because loading
299 # We don't load the list of styles for the help string, because loading
300 # Pygments plugins takes time and can cause unexpected errors.
300 # Pygments plugins takes time and can cause unexpected errors.
301 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
301 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
302 help="""The name or class of a Pygments style to use for syntax
302 help="""The name or class of a Pygments style to use for syntax
303 highlighting. To see available styles, run `pygmentize -L styles`."""
303 highlighting. To see available styles, run `pygmentize -L styles`."""
304 ).tag(config=True)
304 ).tag(config=True)
305
305
306 @validate('editing_mode')
306 @validate('editing_mode')
307 def _validate_editing_mode(self, proposal):
307 def _validate_editing_mode(self, proposal):
308 if proposal['value'].lower() == 'vim':
308 if proposal['value'].lower() == 'vim':
309 proposal['value']= 'vi'
309 proposal['value']= 'vi'
310 elif proposal['value'].lower() == 'default':
310 elif proposal['value'].lower() == 'default':
311 proposal['value']= 'emacs'
311 proposal['value']= 'emacs'
312
312
313 if hasattr(EditingMode, proposal['value'].upper()):
313 if hasattr(EditingMode, proposal['value'].upper()):
314 return proposal['value'].lower()
314 return proposal['value'].lower()
315
315
316 return self.editing_mode
316 return self.editing_mode
317
317
318 @observe('editing_mode')
318 @observe('editing_mode')
319 def _editing_mode(self, change):
319 def _editing_mode(self, change):
320 if self.pt_app:
320 if self.pt_app:
321 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
321 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
322
322
323 def _set_formatter(self, formatter):
323 def _set_formatter(self, formatter):
324 if formatter is None:
324 if formatter is None:
325 self.reformat_handler = lambda x:x
325 self.reformat_handler = lambda x:x
326 elif formatter == 'black':
326 elif formatter == 'black':
327 self.reformat_handler = black_reformat_handler
327 self.reformat_handler = black_reformat_handler
328 elif formatter == "yapf":
328 elif formatter == "yapf":
329 self.reformat_handler = yapf_reformat_handler
329 self.reformat_handler = yapf_reformat_handler
330 else:
330 else:
331 raise ValueError
331 raise ValueError
332
332
333 @observe("autoformatter")
333 @observe("autoformatter")
334 def _autoformatter_changed(self, change):
334 def _autoformatter_changed(self, change):
335 formatter = change.new
335 formatter = change.new
336 self._set_formatter(formatter)
336 self._set_formatter(formatter)
337
337
338 @observe('highlighting_style')
338 @observe('highlighting_style')
339 @observe('colors')
339 @observe('colors')
340 def _highlighting_style_changed(self, change):
340 def _highlighting_style_changed(self, change):
341 self.refresh_style()
341 self.refresh_style()
342
342
343 def refresh_style(self):
343 def refresh_style(self):
344 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
344 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
345
345
346 highlighting_style_overrides = Dict(
346 highlighting_style_overrides = Dict(
347 help="Override highlighting format for specific tokens"
347 help="Override highlighting format for specific tokens"
348 ).tag(config=True)
348 ).tag(config=True)
349
349
350 true_color = Bool(False,
350 true_color = Bool(False,
351 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
351 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
352 If your terminal supports true color, the following command should
352 If your terminal supports true color, the following command should
353 print ``TRUECOLOR`` in orange::
353 print ``TRUECOLOR`` in orange::
354
354
355 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
355 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
356 """,
356 """,
357 ).tag(config=True)
357 ).tag(config=True)
358
358
359 editor = Unicode(get_default_editor(),
359 editor = Unicode(get_default_editor(),
360 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
360 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
361 ).tag(config=True)
361 ).tag(config=True)
362
362
363 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
363 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
364
364
365 prompts = Instance(Prompts)
365 prompts = Instance(Prompts)
366
366
367 @default('prompts')
367 @default('prompts')
368 def _prompts_default(self):
368 def _prompts_default(self):
369 return self.prompts_class(self)
369 return self.prompts_class(self)
370
370
371 # @observe('prompts')
371 # @observe('prompts')
372 # def _(self, change):
372 # def _(self, change):
373 # self._update_layout()
373 # self._update_layout()
374
374
375 @default('displayhook_class')
375 @default('displayhook_class')
376 def _displayhook_class_default(self):
376 def _displayhook_class_default(self):
377 return RichPromptDisplayHook
377 return RichPromptDisplayHook
378
378
379 term_title = Bool(True,
379 term_title = Bool(True,
380 help="Automatically set the terminal title"
380 help="Automatically set the terminal title"
381 ).tag(config=True)
381 ).tag(config=True)
382
382
383 term_title_format = Unicode("IPython: {cwd}",
383 term_title_format = Unicode("IPython: {cwd}",
384 help="Customize the terminal title format. This is a python format string. " +
384 help="Customize the terminal title format. This is a python format string. " +
385 "Available substitutions are: {cwd}."
385 "Available substitutions are: {cwd}."
386 ).tag(config=True)
386 ).tag(config=True)
387
387
388 display_completions = Enum(('column', 'multicolumn','readlinelike'),
388 display_completions = Enum(('column', 'multicolumn','readlinelike'),
389 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
389 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
390 "'readlinelike'. These options are for `prompt_toolkit`, see "
390 "'readlinelike'. These options are for `prompt_toolkit`, see "
391 "`prompt_toolkit` documentation for more information."
391 "`prompt_toolkit` documentation for more information."
392 ),
392 ),
393 default_value='multicolumn').tag(config=True)
393 default_value='multicolumn').tag(config=True)
394
394
395 highlight_matching_brackets = Bool(True,
395 highlight_matching_brackets = Bool(True,
396 help="Highlight matching brackets.",
396 help="Highlight matching brackets.",
397 ).tag(config=True)
397 ).tag(config=True)
398
398
399 extra_open_editor_shortcuts = Bool(False,
399 extra_open_editor_shortcuts = Bool(False,
400 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
400 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
401 "This is in addition to the F2 binding, which is always enabled."
401 "This is in addition to the F2 binding, which is always enabled."
402 ).tag(config=True)
402 ).tag(config=True)
403
403
404 handle_return = Any(None,
404 handle_return = Any(None,
405 help="Provide an alternative handler to be called when the user presses "
405 help="Provide an alternative handler to be called when the user presses "
406 "Return. This is an advanced option intended for debugging, which "
406 "Return. This is an advanced option intended for debugging, which "
407 "may be changed or removed in later releases."
407 "may be changed or removed in later releases."
408 ).tag(config=True)
408 ).tag(config=True)
409
409
410 enable_history_search = Bool(True,
410 enable_history_search = Bool(True,
411 help="Allows to enable/disable the prompt toolkit history search"
411 help="Allows to enable/disable the prompt toolkit history search"
412 ).tag(config=True)
412 ).tag(config=True)
413
413
414 autosuggestions_provider = Unicode(
414 autosuggestions_provider = Unicode(
415 "NavigableAutoSuggestFromHistory",
415 "NavigableAutoSuggestFromHistory",
416 help="Specifies from which source automatic suggestions are provided. "
416 help="Specifies from which source automatic suggestions are provided. "
417 "Can be set to ``'NavigableAutoSuggestFromHistory'`` (:kbd:`up` and "
417 "Can be set to ``'NavigableAutoSuggestFromHistory'`` (:kbd:`up` and "
418 ":kbd:`down` swap suggestions), ``'AutoSuggestFromHistory'``, "
418 ":kbd:`down` swap suggestions), ``'AutoSuggestFromHistory'``, "
419 " or ``None`` to disable automatic suggestions. "
419 " or ``None`` to disable automatic suggestions. "
420 "Default is `'NavigableAutoSuggestFromHistory`'.",
420 "Default is `'NavigableAutoSuggestFromHistory`'.",
421 allow_none=True,
421 allow_none=True,
422 ).tag(config=True)
422 ).tag(config=True)
423
423
424 def _set_autosuggestions(self, provider):
424 def _set_autosuggestions(self, provider):
425 # disconnect old handler
425 # disconnect old handler
426 if self.auto_suggest and isinstance(
426 if self.auto_suggest and isinstance(
427 self.auto_suggest, NavigableAutoSuggestFromHistory
427 self.auto_suggest, NavigableAutoSuggestFromHistory
428 ):
428 ):
429 self.auto_suggest.disconnect()
429 self.auto_suggest.disconnect()
430 if provider is None:
430 if provider is None:
431 self.auto_suggest = None
431 self.auto_suggest = None
432 elif provider == "AutoSuggestFromHistory":
432 elif provider == "AutoSuggestFromHistory":
433 self.auto_suggest = AutoSuggestFromHistory()
433 self.auto_suggest = AutoSuggestFromHistory()
434 elif provider == "NavigableAutoSuggestFromHistory":
434 elif provider == "NavigableAutoSuggestFromHistory":
435 self.auto_suggest = NavigableAutoSuggestFromHistory()
435 self.auto_suggest = NavigableAutoSuggestFromHistory()
436 else:
436 else:
437 raise ValueError("No valid provider.")
437 raise ValueError("No valid provider.")
438 if self.pt_app:
438 if self.pt_app:
439 self.pt_app.auto_suggest = self.auto_suggest
439 self.pt_app.auto_suggest = self.auto_suggest
440
440
441 @observe("autosuggestions_provider")
441 @observe("autosuggestions_provider")
442 def _autosuggestions_provider_changed(self, change):
442 def _autosuggestions_provider_changed(self, change):
443 provider = change.new
443 provider = change.new
444 self._set_autosuggestions(provider)
444 self._set_autosuggestions(provider)
445
445
446 shortcuts = List(
446 shortcuts = List(
447 trait=Dict(
447 trait=Dict(
448 key_trait=Enum(
448 key_trait=Enum(
449 [
449 [
450 "command",
450 "command",
451 "match_keys",
451 "match_keys",
452 "match_filter",
452 "match_filter",
453 "new_keys",
453 "new_keys",
454 "new_filter",
454 "new_filter",
455 "create",
455 "create",
456 ]
456 ]
457 ),
457 ),
458 per_key_traits={
458 per_key_traits={
459 "command": Unicode(),
459 "command": Unicode(),
460 "match_keys": List(Unicode()),
460 "match_keys": List(Unicode()),
461 "match_filter": Unicode(),
461 "match_filter": Unicode(),
462 "new_keys": List(Unicode()),
462 "new_keys": List(Unicode()),
463 "new_filter": Unicode(),
463 "new_filter": Unicode(),
464 "create": Bool(False),
464 "create": Bool(False),
465 },
465 },
466 ),
466 ),
467 help="""Add, disable or modifying shortcuts.
467 help="""Add, disable or modifying shortcuts.
468
468
469 Each entry on the list should be a dictionary with ``command`` key
469 Each entry on the list should be a dictionary with ``command`` key
470 identifying the target function executed by the shortcut and at least
470 identifying the target function executed by the shortcut and at least
471 one of the following:
471 one of the following:
472
472
473 - ``match_keys``: list of keys used to match an existing shortcut,
473 - ``match_keys``: list of keys used to match an existing shortcut,
474 - ``match_filter``: shortcut filter used to match an existing shortcut,
474 - ``match_filter``: shortcut filter used to match an existing shortcut,
475 - ``new_keys``: list of keys to set,
475 - ``new_keys``: list of keys to set,
476 - ``new_filter``: a new shortcut filter to set
476 - ``new_filter``: a new shortcut filter to set
477
477
478 The filters have to be composed of pre-defined verbs and joined by one
478 The filters have to be composed of pre-defined verbs and joined by one
479 of the following conjunctions: ``&`` (and), ``|`` (or), ``~`` (not).
479 of the following conjunctions: ``&`` (and), ``|`` (or), ``~`` (not).
480 The pre-defined verbs are:
480 The pre-defined verbs are:
481
481
482 {}
482 {}
483
483
484
484
485 To disable a shortcut set ``new_keys`` to an empty list.
485 To disable a shortcut set ``new_keys`` to an empty list.
486 To add a shortcut add key ``create`` with value ``True``.
486 To add a shortcut add key ``create`` with value ``True``.
487
487
488 When modifying/disabling shortcuts, ``match_keys``/``match_filter`` can
488 When modifying/disabling shortcuts, ``match_keys``/``match_filter`` can
489 be omitted if the provided specification uniquely identifies a shortcut
489 be omitted if the provided specification uniquely identifies a shortcut
490 to be modified/disabled. When modifying a shortcut ``new_filter`` or
490 to be modified/disabled. When modifying a shortcut ``new_filter`` or
491 ``new_keys`` can be omitted which will result in reuse of the existing
491 ``new_keys`` can be omitted which will result in reuse of the existing
492 filter/keys.
492 filter/keys.
493
493
494 Only shortcuts defined in IPython (and not default prompt-toolkit
494 Only shortcuts defined in IPython (and not default prompt-toolkit
495 shortcuts) can be modified or disabled. The full list of shortcuts,
495 shortcuts) can be modified or disabled. The full list of shortcuts,
496 command identifiers and filters is available under
496 command identifiers and filters is available under
497 :ref:`terminal-shortcuts-list`.
497 :ref:`terminal-shortcuts-list`.
498 """.format(
498 """.format(
499 "\n ".join([f"- `{k}`" for k in KEYBINDING_FILTERS])
499 "\n ".join([f"- `{k}`" for k in KEYBINDING_FILTERS])
500 ),
500 ),
501 ).tag(config=True)
501 ).tag(config=True)
502
502
503 @observe("shortcuts")
503 @observe("shortcuts")
504 def _shortcuts_changed(self, change):
504 def _shortcuts_changed(self, change):
505 if self.pt_app:
505 if self.pt_app:
506 self.pt_app.key_bindings = self._merge_shortcuts(user_shortcuts=change.new)
506 self.pt_app.key_bindings = self._merge_shortcuts(user_shortcuts=change.new)
507
507
508 def _merge_shortcuts(self, user_shortcuts):
508 def _merge_shortcuts(self, user_shortcuts):
509 # rebuild the bindings list from scratch
509 # rebuild the bindings list from scratch
510 key_bindings = create_ipython_shortcuts(self)
510 key_bindings = create_ipython_shortcuts(self)
511
511
512 # for now we only allow adding shortcuts for a specific set of
512 # for now we only allow adding shortcuts for a specific set of
513 # commands; this is a security precution.
513 # commands; this is a security precution.
514 allowed_commands = {
514 allowed_commands = {
515 create_identifier(binding.command): binding.command
515 create_identifier(binding.command): binding.command
516 for binding in KEY_BINDINGS
516 for binding in KEY_BINDINGS
517 }
517 }
518 allowed_commands.update(
518 allowed_commands.update(
519 {
519 {
520 create_identifier(command): command
520 create_identifier(command): command
521 for command in UNASSIGNED_ALLOWED_COMMANDS
521 for command in UNASSIGNED_ALLOWED_COMMANDS
522 }
522 }
523 )
523 )
524 shortcuts_to_skip = []
524 shortcuts_to_skip = []
525 shortcuts_to_add = []
525 shortcuts_to_add = []
526
526
527 for shortcut in user_shortcuts:
527 for shortcut in user_shortcuts:
528 command_id = shortcut["command"]
528 command_id = shortcut["command"]
529 if command_id not in allowed_commands:
529 if command_id not in allowed_commands:
530 allowed_commands = "\n - ".join(allowed_commands)
530 allowed_commands = "\n - ".join(allowed_commands)
531 raise ValueError(
531 raise ValueError(
532 f"{command_id} is not a known shortcut command."
532 f"{command_id} is not a known shortcut command."
533 f" Allowed commands are: \n - {allowed_commands}"
533 f" Allowed commands are: \n - {allowed_commands}"
534 )
534 )
535 old_keys = shortcut.get("match_keys", None)
535 old_keys = shortcut.get("match_keys", None)
536 old_filter = (
536 old_filter = (
537 filter_from_string(shortcut["match_filter"])
537 filter_from_string(shortcut["match_filter"])
538 if "match_filter" in shortcut
538 if "match_filter" in shortcut
539 else None
539 else None
540 )
540 )
541 matching = [
541 matching = [
542 binding
542 binding
543 for binding in KEY_BINDINGS
543 for binding in KEY_BINDINGS
544 if (
544 if (
545 (old_filter is None or binding.filter == old_filter)
545 (old_filter is None or binding.filter == old_filter)
546 and (old_keys is None or [k for k in binding.keys] == old_keys)
546 and (old_keys is None or [k for k in binding.keys] == old_keys)
547 and create_identifier(binding.command) == command_id
547 and create_identifier(binding.command) == command_id
548 )
548 )
549 ]
549 ]
550
550
551 new_keys = shortcut.get("new_keys", None)
551 new_keys = shortcut.get("new_keys", None)
552 new_filter = shortcut.get("new_filter", None)
552 new_filter = shortcut.get("new_filter", None)
553
553
554 command = allowed_commands[command_id]
554 command = allowed_commands[command_id]
555
555
556 creating_new = shortcut.get("create", False)
556 creating_new = shortcut.get("create", False)
557 modifying_existing = not creating_new and (
557 modifying_existing = not creating_new and (
558 new_keys is not None or new_filter
558 new_keys is not None or new_filter
559 )
559 )
560
560
561 if creating_new and new_keys == []:
561 if creating_new and new_keys == []:
562 raise ValueError("Cannot add a shortcut without keys")
562 raise ValueError("Cannot add a shortcut without keys")
563
563
564 if modifying_existing:
564 if modifying_existing:
565 specification = {
565 specification = {
566 key: shortcut[key]
566 key: shortcut[key]
567 for key in ["command", "filter"]
567 for key in ["command", "filter"]
568 if key in shortcut
568 if key in shortcut
569 }
569 }
570 if len(matching) == 0:
570 if len(matching) == 0:
571 raise ValueError(
571 raise ValueError(
572 f"No shortcuts matching {specification} found in {KEY_BINDINGS}"
572 f"No shortcuts matching {specification} found in {KEY_BINDINGS}"
573 )
573 )
574 elif len(matching) > 1:
574 elif len(matching) > 1:
575 raise ValueError(
575 raise ValueError(
576 f"Multiple shortcuts matching {specification} found,"
576 f"Multiple shortcuts matching {specification} found,"
577 f" please add keys/filter to select one of: {matching}"
577 f" please add keys/filter to select one of: {matching}"
578 )
578 )
579
579
580 matched = matching[0]
580 matched = matching[0]
581 old_filter = matched.filter
581 old_filter = matched.filter
582 old_keys = list(matched.keys)
582 old_keys = list(matched.keys)
583 shortcuts_to_skip.append(
583 shortcuts_to_skip.append(
584 RuntimeBinding(
584 RuntimeBinding(
585 command,
585 command,
586 keys=old_keys,
586 keys=old_keys,
587 filter=old_filter,
587 filter=old_filter,
588 )
588 )
589 )
589 )
590
590
591 if new_keys != []:
591 if new_keys != []:
592 shortcuts_to_add.append(
592 shortcuts_to_add.append(
593 RuntimeBinding(
593 RuntimeBinding(
594 command,
594 command,
595 keys=new_keys or old_keys,
595 keys=new_keys or old_keys,
596 filter=(
596 filter=(
597 filter_from_string(new_filter)
597 filter_from_string(new_filter)
598 if new_filter is not None
598 if new_filter is not None
599 else (
599 else (
600 old_filter
600 old_filter
601 if old_filter is not None
601 if old_filter is not None
602 else filter_from_string("always")
602 else filter_from_string("always")
603 )
603 )
604 ),
604 ),
605 )
605 )
606 )
606 )
607
607
608 # rebuild the bindings list from scratch
608 # rebuild the bindings list from scratch
609 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
609 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
610 for binding in shortcuts_to_add:
610 for binding in shortcuts_to_add:
611 add_binding(key_bindings, binding)
611 add_binding(key_bindings, binding)
612
612
613 return key_bindings
613 return key_bindings
614
614
615 prompt_includes_vi_mode = Bool(True,
615 prompt_includes_vi_mode = Bool(True,
616 help="Display the current vi mode (when using vi editing mode)."
616 help="Display the current vi mode (when using vi editing mode)."
617 ).tag(config=True)
617 ).tag(config=True)
618
618
619 prompt_line_number_format = Unicode(
619 prompt_line_number_format = Unicode(
620 "",
620 "",
621 help="The format for line numbering, will be passed `line` (int, 1 based)"
621 help="The format for line numbering, will be passed `line` (int, 1 based)"
622 " the current line number and `rel_line` the relative line number."
622 " the current line number and `rel_line` the relative line number."
623 " for example to display both you can use the following template string :"
623 " for example to display both you can use the following template string :"
624 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '"
624 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '"
625 " This will display the current line number, with leading space and a width of at least 4"
625 " This will display the current line number, with leading space and a width of at least 4"
626 " character, as well as the relative line number 0 padded and always with a + or - sign."
626 " character, as well as the relative line number 0 padded and always with a + or - sign."
627 " Note that when using Emacs mode the prompt of the first line may not update.",
627 " Note that when using Emacs mode the prompt of the first line may not update.",
628 ).tag(config=True)
628 ).tag(config=True)
629
629
630 @observe('term_title')
630 @observe('term_title')
631 def init_term_title(self, change=None):
631 def init_term_title(self, change=None):
632 # Enable or disable the terminal title.
632 # Enable or disable the terminal title.
633 if self.term_title and _is_tty:
633 if self.term_title and _is_tty:
634 toggle_set_term_title(True)
634 toggle_set_term_title(True)
635 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
635 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
636 else:
636 else:
637 toggle_set_term_title(False)
637 toggle_set_term_title(False)
638
638
639 def restore_term_title(self):
639 def restore_term_title(self):
640 if self.term_title and _is_tty:
640 if self.term_title and _is_tty:
641 restore_term_title()
641 restore_term_title()
642
642
643 def init_display_formatter(self):
643 def init_display_formatter(self):
644 super(TerminalInteractiveShell, self).init_display_formatter()
644 super(TerminalInteractiveShell, self).init_display_formatter()
645 # terminal only supports plain text
645 # terminal only supports plain text
646 self.display_formatter.active_types = ["text/plain"]
646 self.display_formatter.active_types = ["text/plain"]
647
647
648 def init_prompt_toolkit_cli(self):
648 def init_prompt_toolkit_cli(self):
649 if self.simple_prompt:
649 if self.simple_prompt:
650 # Fall back to plain non-interactive output for tests.
650 # Fall back to plain non-interactive output for tests.
651 # This is very limited.
651 # This is very limited.
652 def prompt():
652 def prompt():
653 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
653 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
654 lines = [input(prompt_text)]
654 lines = [input(prompt_text)]
655 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
655 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
656 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
656 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
657 lines.append( input(prompt_continuation) )
657 lines.append( input(prompt_continuation) )
658 return '\n'.join(lines)
658 return '\n'.join(lines)
659 self.prompt_for_code = prompt
659 self.prompt_for_code = prompt
660 return
660 return
661
661
662 # Set up keyboard shortcuts
662 # Set up keyboard shortcuts
663 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
663 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
664
664
665 # Pre-populate history from IPython's history database
665 # Pre-populate history from IPython's history database
666 history = PtkHistoryAdapter(self)
666 history = PtkHistoryAdapter(self)
667
667
668 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
668 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
669 self.style = DynamicStyle(lambda: self._style)
669 self.style = DynamicStyle(lambda: self._style)
670
670
671 editing_mode = getattr(EditingMode, self.editing_mode.upper())
671 editing_mode = getattr(EditingMode, self.editing_mode.upper())
672
672
673 self._use_asyncio_inputhook = False
673 self._use_asyncio_inputhook = False
674 self.pt_app = PromptSession(
674 self.pt_app = PromptSession(
675 auto_suggest=self.auto_suggest,
675 auto_suggest=self.auto_suggest,
676 editing_mode=editing_mode,
676 editing_mode=editing_mode,
677 key_bindings=key_bindings,
677 key_bindings=key_bindings,
678 history=history,
678 history=history,
679 completer=IPythonPTCompleter(shell=self),
679 completer=IPythonPTCompleter(shell=self),
680 enable_history_search=self.enable_history_search,
680 enable_history_search=self.enable_history_search,
681 style=self.style,
681 style=self.style,
682 include_default_pygments_style=False,
682 include_default_pygments_style=False,
683 mouse_support=self.mouse_support,
683 mouse_support=self.mouse_support,
684 enable_open_in_editor=self.extra_open_editor_shortcuts,
684 enable_open_in_editor=self.extra_open_editor_shortcuts,
685 color_depth=self.color_depth,
685 color_depth=self.color_depth,
686 tempfile_suffix=".py",
686 tempfile_suffix=".py",
687 **self._extra_prompt_options(),
687 **self._extra_prompt_options(),
688 )
688 )
689 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
689 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
690 self.auto_suggest.connect(self.pt_app)
690 self.auto_suggest.connect(self.pt_app)
691
691
692 def _make_style_from_name_or_cls(self, name_or_cls):
692 def _make_style_from_name_or_cls(self, name_or_cls):
693 """
693 """
694 Small wrapper that make an IPython compatible style from a style name
694 Small wrapper that make an IPython compatible style from a style name
695
695
696 We need that to add style for prompt ... etc.
696 We need that to add style for prompt ... etc.
697 """
697 """
698 style_overrides = {}
698 style_overrides = {}
699 if name_or_cls == 'legacy':
699 if name_or_cls == 'legacy':
700 legacy = self.colors.lower()
700 legacy = self.colors.lower()
701 if legacy == 'linux':
701 if legacy == 'linux':
702 style_cls = get_style_by_name('monokai')
702 style_cls = get_style_by_name('monokai')
703 style_overrides = _style_overrides_linux
703 style_overrides = _style_overrides_linux
704 elif legacy == 'lightbg':
704 elif legacy == 'lightbg':
705 style_overrides = _style_overrides_light_bg
705 style_overrides = _style_overrides_light_bg
706 style_cls = get_style_by_name('pastie')
706 style_cls = get_style_by_name('pastie')
707 elif legacy == 'neutral':
707 elif legacy == 'neutral':
708 # The default theme needs to be visible on both a dark background
708 # The default theme needs to be visible on both a dark background
709 # and a light background, because we can't tell what the terminal
709 # and a light background, because we can't tell what the terminal
710 # looks like. These tweaks to the default theme help with that.
710 # looks like. These tweaks to the default theme help with that.
711 style_cls = get_style_by_name('default')
711 style_cls = get_style_by_name('default')
712 style_overrides.update({
712 style_overrides.update({
713 Token.Number: '#ansigreen',
713 Token.Number: '#ansigreen',
714 Token.Operator: 'noinherit',
714 Token.Operator: 'noinherit',
715 Token.String: '#ansiyellow',
715 Token.String: '#ansiyellow',
716 Token.Name.Function: '#ansiblue',
716 Token.Name.Function: '#ansiblue',
717 Token.Name.Class: 'bold #ansiblue',
717 Token.Name.Class: 'bold #ansiblue',
718 Token.Name.Namespace: 'bold #ansiblue',
718 Token.Name.Namespace: 'bold #ansiblue',
719 Token.Name.Variable.Magic: '#ansiblue',
719 Token.Name.Variable.Magic: '#ansiblue',
720 Token.Prompt: '#ansigreen',
720 Token.Prompt: '#ansigreen',
721 Token.PromptNum: '#ansibrightgreen bold',
721 Token.PromptNum: '#ansibrightgreen bold',
722 Token.OutPrompt: '#ansired',
722 Token.OutPrompt: '#ansired',
723 Token.OutPromptNum: '#ansibrightred bold',
723 Token.OutPromptNum: '#ansibrightred bold',
724 })
724 })
725
725
726 # Hack: Due to limited color support on the Windows console
726 # Hack: Due to limited color support on the Windows console
727 # the prompt colors will be wrong without this
727 # the prompt colors will be wrong without this
728 if os.name == 'nt':
728 if os.name == 'nt':
729 style_overrides.update({
729 style_overrides.update({
730 Token.Prompt: '#ansidarkgreen',
730 Token.Prompt: '#ansidarkgreen',
731 Token.PromptNum: '#ansigreen bold',
731 Token.PromptNum: '#ansigreen bold',
732 Token.OutPrompt: '#ansidarkred',
732 Token.OutPrompt: '#ansidarkred',
733 Token.OutPromptNum: '#ansired bold',
733 Token.OutPromptNum: '#ansired bold',
734 })
734 })
735 elif legacy =='nocolor':
735 elif legacy =='nocolor':
736 style_cls=_NoStyle
736 style_cls=_NoStyle
737 style_overrides = {}
737 style_overrides = {}
738 else :
738 else :
739 raise ValueError('Got unknown colors: ', legacy)
739 raise ValueError('Got unknown colors: ', legacy)
740 else :
740 else :
741 if isinstance(name_or_cls, str):
741 if isinstance(name_or_cls, str):
742 style_cls = get_style_by_name(name_or_cls)
742 style_cls = get_style_by_name(name_or_cls)
743 else:
743 else:
744 style_cls = name_or_cls
744 style_cls = name_or_cls
745 style_overrides = {
745 style_overrides = {
746 Token.Prompt: '#ansigreen',
746 Token.Prompt: '#ansigreen',
747 Token.PromptNum: '#ansibrightgreen bold',
747 Token.PromptNum: '#ansibrightgreen bold',
748 Token.OutPrompt: '#ansired',
748 Token.OutPrompt: '#ansired',
749 Token.OutPromptNum: '#ansibrightred bold',
749 Token.OutPromptNum: '#ansibrightred bold',
750 }
750 }
751 style_overrides.update(self.highlighting_style_overrides)
751 style_overrides.update(self.highlighting_style_overrides)
752 style = merge_styles([
752 style = merge_styles([
753 style_from_pygments_cls(style_cls),
753 style_from_pygments_cls(style_cls),
754 style_from_pygments_dict(style_overrides),
754 style_from_pygments_dict(style_overrides),
755 ])
755 ])
756
756
757 return style
757 return style
758
758
759 @property
759 @property
760 def pt_complete_style(self):
760 def pt_complete_style(self):
761 return {
761 return {
762 'multicolumn': CompleteStyle.MULTI_COLUMN,
762 'multicolumn': CompleteStyle.MULTI_COLUMN,
763 'column': CompleteStyle.COLUMN,
763 'column': CompleteStyle.COLUMN,
764 'readlinelike': CompleteStyle.READLINE_LIKE,
764 'readlinelike': CompleteStyle.READLINE_LIKE,
765 }[self.display_completions]
765 }[self.display_completions]
766
766
767 @property
767 @property
768 def color_depth(self):
768 def color_depth(self):
769 return (ColorDepth.TRUE_COLOR if self.true_color else None)
769 return (ColorDepth.TRUE_COLOR if self.true_color else None)
770
770
771 def _extra_prompt_options(self):
771 def _extra_prompt_options(self):
772 """
772 """
773 Return the current layout option for the current Terminal InteractiveShell
773 Return the current layout option for the current Terminal InteractiveShell
774 """
774 """
775 def get_message():
775 def get_message():
776 return PygmentsTokens(self.prompts.in_prompt_tokens())
776 return PygmentsTokens(self.prompts.in_prompt_tokens())
777
777
778 if self.editing_mode == "emacs" and self.prompt_line_number_format == "":
778 if self.editing_mode == "emacs" and self.prompt_line_number_format == "":
779 # with emacs mode the prompt is (usually) static, so we call only
779 # with emacs mode the prompt is (usually) static, so we call only
780 # the function once. With VI mode it can toggle between [ins] and
780 # the function once. With VI mode it can toggle between [ins] and
781 # [nor] so we can't precompute.
781 # [nor] so we can't precompute.
782 # here I'm going to favor the default keybinding which almost
782 # here I'm going to favor the default keybinding which almost
783 # everybody uses to decrease CPU usage.
783 # everybody uses to decrease CPU usage.
784 # if we have issues with users with custom Prompts we can see how to
784 # if we have issues with users with custom Prompts we can see how to
785 # work around this.
785 # work around this.
786 get_message = get_message()
786 get_message = get_message()
787
787
788 options = {
788 options = {
789 "complete_in_thread": False,
789 "complete_in_thread": False,
790 "lexer": IPythonPTLexer(),
790 "lexer": IPythonPTLexer(),
791 "reserve_space_for_menu": self.space_for_menu,
791 "reserve_space_for_menu": self.space_for_menu,
792 "message": get_message,
792 "message": get_message,
793 "prompt_continuation": (
793 "prompt_continuation": (
794 lambda width, lineno, is_soft_wrap: PygmentsTokens(
794 lambda width, lineno, is_soft_wrap: PygmentsTokens(
795 _backward_compat_continuation_prompt_tokens(
795 _backward_compat_continuation_prompt_tokens(
796 self.prompts.continuation_prompt_tokens, width, lineno=lineno
796 self.prompts.continuation_prompt_tokens, width, lineno=lineno
797 )
797 )
798 )
798 )
799 ),
799 ),
800 "multiline": True,
800 "multiline": True,
801 "complete_style": self.pt_complete_style,
801 "complete_style": self.pt_complete_style,
802 "input_processors": [
802 "input_processors": [
803 # Highlight matching brackets, but only when this setting is
803 # Highlight matching brackets, but only when this setting is
804 # enabled, and only when the DEFAULT_BUFFER has the focus.
804 # enabled, and only when the DEFAULT_BUFFER has the focus.
805 ConditionalProcessor(
805 ConditionalProcessor(
806 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
806 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
807 filter=HasFocus(DEFAULT_BUFFER)
807 filter=HasFocus(DEFAULT_BUFFER)
808 & ~IsDone()
808 & ~IsDone()
809 & Condition(lambda: self.highlight_matching_brackets),
809 & Condition(lambda: self.highlight_matching_brackets),
810 ),
810 ),
811 # Show auto-suggestion in lines other than the last line.
811 # Show auto-suggestion in lines other than the last line.
812 ConditionalProcessor(
812 ConditionalProcessor(
813 processor=AppendAutoSuggestionInAnyLine(),
813 processor=AppendAutoSuggestionInAnyLine(),
814 filter=HasFocus(DEFAULT_BUFFER)
814 filter=HasFocus(DEFAULT_BUFFER)
815 & ~IsDone()
815 & ~IsDone()
816 & Condition(
816 & Condition(
817 lambda: isinstance(
817 lambda: isinstance(
818 self.auto_suggest, NavigableAutoSuggestFromHistory
818 self.auto_suggest, NavigableAutoSuggestFromHistory
819 )
819 )
820 ),
820 ),
821 ),
821 ),
822 ],
822 ],
823 }
823 }
824 if not PTK3:
824 if not PTK3:
825 options['inputhook'] = self.inputhook
825 options['inputhook'] = self.inputhook
826
826
827 return options
827 return options
828
828
829 def prompt_for_code(self):
829 def prompt_for_code(self):
830 if self.rl_next_input:
830 if self.rl_next_input:
831 default = self.rl_next_input
831 default = self.rl_next_input
832 self.rl_next_input = None
832 self.rl_next_input = None
833 else:
833 else:
834 default = ''
834 default = ''
835
835
836 # In order to make sure that asyncio code written in the
836 # In order to make sure that asyncio code written in the
837 # interactive shell doesn't interfere with the prompt, we run the
837 # interactive shell doesn't interfere with the prompt, we run the
838 # prompt in a different event loop.
838 # prompt in a different event loop.
839 # If we don't do this, people could spawn coroutine with a
839 # If we don't do this, people could spawn coroutine with a
840 # while/true inside which will freeze the prompt.
840 # while/true inside which will freeze the prompt.
841
841
842 with patch_stdout(raw=True):
842 with patch_stdout(raw=True):
843 if self._use_asyncio_inputhook:
843 if self._use_asyncio_inputhook:
844 # When we integrate the asyncio event loop, run the UI in the
844 # When we integrate the asyncio event loop, run the UI in the
845 # same event loop as the rest of the code. don't use an actual
845 # same event loop as the rest of the code. don't use an actual
846 # input hook. (Asyncio is not made for nesting event loops.)
846 # input hook. (Asyncio is not made for nesting event loops.)
847 asyncio_loop = get_asyncio_loop()
847 asyncio_loop = get_asyncio_loop()
848 text = asyncio_loop.run_until_complete(
848 text = asyncio_loop.run_until_complete(
849 self.pt_app.prompt_async(
849 self.pt_app.prompt_async(
850 default=default, **self._extra_prompt_options()
850 default=default, **self._extra_prompt_options()
851 )
851 )
852 )
852 )
853 else:
853 else:
854 text = self.pt_app.prompt(
854 text = self.pt_app.prompt(
855 default=default,
855 default=default,
856 inputhook=self._inputhook,
856 inputhook=self._inputhook,
857 **self._extra_prompt_options(),
857 **self._extra_prompt_options(),
858 )
858 )
859
859
860 return text
860 return text
861
861
862 def init_io(self):
862 def init_io(self):
863 if sys.platform not in {'win32', 'cli'}:
863 if sys.platform not in {'win32', 'cli'}:
864 return
864 return
865
865
866 import colorama
866 import colorama
867 colorama.init()
867 colorama.init()
868
868
869 def init_magics(self):
869 def init_magics(self):
870 super(TerminalInteractiveShell, self).init_magics()
870 super(TerminalInteractiveShell, self).init_magics()
871 self.register_magics(TerminalMagics)
871 self.register_magics(TerminalMagics)
872
872
873 def init_alias(self):
873 def init_alias(self):
874 # The parent class defines aliases that can be safely used with any
874 # The parent class defines aliases that can be safely used with any
875 # frontend.
875 # frontend.
876 super(TerminalInteractiveShell, self).init_alias()
876 super(TerminalInteractiveShell, self).init_alias()
877
877
878 # Now define aliases that only make sense on the terminal, because they
878 # Now define aliases that only make sense on the terminal, because they
879 # need direct access to the console in a way that we can't emulate in
879 # need direct access to the console in a way that we can't emulate in
880 # GUI or web frontend
880 # GUI or web frontend
881 if os.name == 'posix':
881 if os.name == 'posix':
882 for cmd in ('clear', 'more', 'less', 'man'):
882 for cmd in ('clear', 'more', 'less', 'man'):
883 self.alias_manager.soft_define_alias(cmd, cmd)
883 self.alias_manager.soft_define_alias(cmd, cmd)
884
884
885 def __init__(self, *args, **kwargs) -> None:
885 def __init__(self, *args, **kwargs) -> None:
886 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
886 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
887 self._set_autosuggestions(self.autosuggestions_provider)
887 self._set_autosuggestions(self.autosuggestions_provider)
888 self.init_prompt_toolkit_cli()
888 self.init_prompt_toolkit_cli()
889 self.init_term_title()
889 self.init_term_title()
890 self.keep_running = True
890 self.keep_running = True
891 self._set_formatter(self.autoformatter)
891 self._set_formatter(self.autoformatter)
892
892
893 def ask_exit(self):
893 def ask_exit(self):
894 self.keep_running = False
894 self.keep_running = False
895
895
896 rl_next_input = None
896 rl_next_input = None
897
897
898 def interact(self):
898 def interact(self):
899 self.keep_running = True
899 self.keep_running = True
900 while self.keep_running:
900 while self.keep_running:
901 print(self.separate_in, end='')
901 print(self.separate_in, end='')
902
902
903 try:
903 try:
904 code = self.prompt_for_code()
904 code = self.prompt_for_code()
905 except EOFError:
905 except EOFError:
906 if (not self.confirm_exit) \
906 if (not self.confirm_exit) \
907 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
907 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
908 self.ask_exit()
908 self.ask_exit()
909
909
910 else:
910 else:
911 if code:
911 if code:
912 self.run_cell(code, store_history=True)
912 self.run_cell(code, store_history=True)
913
913
914 def mainloop(self):
914 def mainloop(self):
915 # An extra layer of protection in case someone mashing Ctrl-C breaks
915 # An extra layer of protection in case someone mashing Ctrl-C breaks
916 # out of our internal code.
916 # out of our internal code.
917 while True:
917 while True:
918 try:
918 try:
919 self.interact()
919 self.interact()
920 break
920 break
921 except KeyboardInterrupt as e:
921 except KeyboardInterrupt as e:
922 print("\n%s escaped interact()\n" % type(e).__name__)
922 print("\n%s escaped interact()\n" % type(e).__name__)
923 finally:
923 finally:
924 # An interrupt during the eventloop will mess up the
924 # An interrupt during the eventloop will mess up the
925 # internal state of the prompt_toolkit library.
925 # internal state of the prompt_toolkit library.
926 # Stopping the eventloop fixes this, see
926 # Stopping the eventloop fixes this, see
927 # https://github.com/ipython/ipython/pull/9867
927 # https://github.com/ipython/ipython/pull/9867
928 if hasattr(self, '_eventloop'):
928 if hasattr(self, '_eventloop'):
929 self._eventloop.stop()
929 self._eventloop.stop()
930
930
931 self.restore_term_title()
931 self.restore_term_title()
932
932
933 # try to call some at-exit operation optimistically as some things can't
933 # try to call some at-exit operation optimistically as some things can't
934 # be done during interpreter shutdown. this is technically inaccurate as
934 # be done during interpreter shutdown. this is technically inaccurate as
935 # this make mainlool not re-callable, but that should be a rare if not
935 # this make mainlool not re-callable, but that should be a rare if not
936 # in existent use case.
936 # in existent use case.
937
937
938 self._atexit_once()
938 self._atexit_once()
939
939
940 _inputhook = None
940 _inputhook = None
941 def inputhook(self, context):
941 def inputhook(self, context):
942 if self._inputhook is not None:
942 if self._inputhook is not None:
943 self._inputhook(context)
943 self._inputhook(context)
944
944
945 active_eventloop: Optional[str] = None
945 active_eventloop: Optional[str] = None
946
946
947 def enable_gui(self, gui: Optional[str] = None) -> None:
947 def enable_gui(self, gui: Optional[str] = None) -> None:
948 if gui:
948 if gui:
949 from ..core.pylabtools import _convert_gui_from_matplotlib
949 from ..core.pylabtools import _convert_gui_from_matplotlib
950
950
951 gui = _convert_gui_from_matplotlib(gui)
951 gui = _convert_gui_from_matplotlib(gui)
952
952
953 if self.simple_prompt is True and gui is not None:
953 if self.simple_prompt is True and gui is not None:
954 print(
954 print(
955 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.'
955 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.'
956 )
956 )
957 print(
957 print(
958 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`."
958 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`."
959 )
959 )
960 return
960 return
961
961
962 if self._inputhook is None and gui is None:
962 if self._inputhook is None and gui is None:
963 print("No event loop hook running.")
963 print("No event loop hook running.")
964 return
964 return
965
965
966 if self._inputhook is not None and gui is not None:
966 if self._inputhook is not None and gui is not None:
967 newev, newinhook = get_inputhook_name_and_func(gui)
967 newev, newinhook = get_inputhook_name_and_func(gui)
968 if self._inputhook == newinhook:
968 if self._inputhook == newinhook:
969 # same inputhook, do nothing
969 # same inputhook, do nothing
970 self.log.info(
970 self.log.info(
971 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing"
971 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing"
972 )
972 )
973 return
973 return
974 self.log.warning(
974 self.log.warning(
975 f"Shell is already running a different gui event loop for {self.active_eventloop}. "
975 f"Shell is already running a different gui event loop for {self.active_eventloop}. "
976 "Call with no arguments to disable the current loop."
976 "Call with no arguments to disable the current loop."
977 )
977 )
978 return
978 return
979 if self._inputhook is not None and gui is None:
979 if self._inputhook is not None and gui is None:
980 self.active_eventloop = self._inputhook = None
980 self.active_eventloop = self._inputhook = None
981
981
982 if gui and (gui not in {None, "webagg"}):
982 if gui and (gui not in {None, "webagg"}):
983 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
983 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
984 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
984 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
985 else:
985 else:
986 self.active_eventloop = self._inputhook = None
986 self.active_eventloop = self._inputhook = None
987
987
988 self._use_asyncio_inputhook = gui == "asyncio"
988 self._use_asyncio_inputhook = gui == "asyncio"
989
989
990 # Run !system commands directly, not through pipes, so terminal programs
990 # Run !system commands directly, not through pipes, so terminal programs
991 # work correctly.
991 # work correctly.
992 system = InteractiveShell.system_raw
992 system = InteractiveShell.system_raw
993
993
994 def auto_rewrite_input(self, cmd):
994 def auto_rewrite_input(self, cmd):
995 """Overridden from the parent class to use fancy rewriting prompt"""
995 """Overridden from the parent class to use fancy rewriting prompt"""
996 if not self.show_rewritten_input:
996 if not self.show_rewritten_input:
997 return
997 return
998
998
999 tokens = self.prompts.rewrite_prompt_tokens()
999 tokens = self.prompts.rewrite_prompt_tokens()
1000 if self.pt_app:
1000 if self.pt_app:
1001 print_formatted_text(PygmentsTokens(tokens), end='',
1001 print_formatted_text(PygmentsTokens(tokens), end='',
1002 style=self.pt_app.app.style)
1002 style=self.pt_app.app.style)
1003 print(cmd)
1003 print(cmd)
1004 else:
1004 else:
1005 prompt = ''.join(s for t, s in tokens)
1005 prompt = ''.join(s for t, s in tokens)
1006 print(prompt, cmd, sep='')
1006 print(prompt, cmd, sep='')
1007
1007
1008 _prompts_before = None
1008 _prompts_before = None
1009 def switch_doctest_mode(self, mode):
1009 def switch_doctest_mode(self, mode):
1010 """Switch prompts to classic for %doctest_mode"""
1010 """Switch prompts to classic for %doctest_mode"""
1011 if mode:
1011 if mode:
1012 self._prompts_before = self.prompts
1012 self._prompts_before = self.prompts
1013 self.prompts = ClassicPrompts(self)
1013 self.prompts = ClassicPrompts(self)
1014 elif self._prompts_before:
1014 elif self._prompts_before:
1015 self.prompts = self._prompts_before
1015 self.prompts = self._prompts_before
1016 self._prompts_before = None
1016 self._prompts_before = None
1017 # self._update_layout()
1017 # self._update_layout()
1018
1018
1019
1019
1020 InteractiveShellABC.register(TerminalInteractiveShell)
1020 InteractiveShellABC.register(TerminalInteractiveShell)
1021
1021
1022 if __name__ == '__main__':
1022 if __name__ == '__main__':
1023 TerminalInteractiveShell.instance().interact()
1023 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now