##// END OF EJS Templates
format
M Bussonnier -
Show More
@@ -1,1026 +1,1030
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 Bool,
15 Bool,
16 Unicode,
16 Unicode,
17 Dict,
17 Dict,
18 Integer,
18 Integer,
19 List,
19 List,
20 observe,
20 observe,
21 Instance,
21 Instance,
22 Type,
22 Type,
23 default,
23 default,
24 Enum,
24 Enum,
25 Union,
25 Union,
26 Any,
26 Any,
27 validate,
27 validate,
28 Float,
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 create_identifier(command): command
520 create_identifier(command): command
520 for command in UNASSIGNED_ALLOWED_COMMANDS
521 for command in UNASSIGNED_ALLOWED_COMMANDS
521 })
522 }
523 )
522 shortcuts_to_skip = []
524 shortcuts_to_skip = []
523 shortcuts_to_add = []
525 shortcuts_to_add = []
524
526
525 for shortcut in user_shortcuts:
527 for shortcut in user_shortcuts:
526 command_id = shortcut["command"]
528 command_id = shortcut["command"]
527 if command_id not in allowed_commands:
529 if command_id not in allowed_commands:
528 allowed_commands = "\n - ".join(allowed_commands)
530 allowed_commands = "\n - ".join(allowed_commands)
529 raise ValueError(
531 raise ValueError(
530 f"{command_id} is not a known shortcut command."
532 f"{command_id} is not a known shortcut command."
531 f" Allowed commands are: \n - {allowed_commands}"
533 f" Allowed commands are: \n - {allowed_commands}"
532 )
534 )
533 old_keys = shortcut.get("match_keys", None)
535 old_keys = shortcut.get("match_keys", None)
534 old_filter = (
536 old_filter = (
535 filter_from_string(shortcut["match_filter"])
537 filter_from_string(shortcut["match_filter"])
536 if "match_filter" in shortcut
538 if "match_filter" in shortcut
537 else None
539 else None
538 )
540 )
539 matching = [
541 matching = [
540 binding
542 binding
541 for binding in KEY_BINDINGS
543 for binding in KEY_BINDINGS
542 if (
544 if (
543 (old_filter is None or binding.filter == old_filter)
545 (old_filter is None or binding.filter == old_filter)
544 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)
545 and create_identifier(binding.command) == command_id
547 and create_identifier(binding.command) == command_id
546 )
548 )
547 ]
549 ]
548
550
549 new_keys = shortcut.get("new_keys", None)
551 new_keys = shortcut.get("new_keys", None)
550 new_filter = shortcut.get("new_filter", None)
552 new_filter = shortcut.get("new_filter", None)
551
553
552 command = allowed_commands[command_id]
554 command = allowed_commands[command_id]
553
555
554 creating_new = shortcut.get("create", False)
556 creating_new = shortcut.get("create", False)
555 modifying_existing = not creating_new and (
557 modifying_existing = not creating_new and (
556 new_keys is not None or new_filter
558 new_keys is not None or new_filter
557 )
559 )
558
560
559 if creating_new and new_keys == []:
561 if creating_new and new_keys == []:
560 raise ValueError("Cannot add a shortcut without keys")
562 raise ValueError("Cannot add a shortcut without keys")
561
563
562 if modifying_existing:
564 if modifying_existing:
563 specification = {
565 specification = {
564 key: shortcut[key]
566 key: shortcut[key]
565 for key in ["command", "filter"]
567 for key in ["command", "filter"]
566 if key in shortcut
568 if key in shortcut
567 }
569 }
568 if len(matching) == 0:
570 if len(matching) == 0:
569 raise ValueError(
571 raise ValueError(
570 f"No shortcuts matching {specification} found in {KEY_BINDINGS}"
572 f"No shortcuts matching {specification} found in {KEY_BINDINGS}"
571 )
573 )
572 elif len(matching) > 1:
574 elif len(matching) > 1:
573 raise ValueError(
575 raise ValueError(
574 f"Multiple shortcuts matching {specification} found,"
576 f"Multiple shortcuts matching {specification} found,"
575 f" please add keys/filter to select one of: {matching}"
577 f" please add keys/filter to select one of: {matching}"
576 )
578 )
577
579
578 matched = matching[0]
580 matched = matching[0]
579 old_filter = matched.filter
581 old_filter = matched.filter
580 old_keys = list(matched.keys)
582 old_keys = list(matched.keys)
581 shortcuts_to_skip.append(
583 shortcuts_to_skip.append(
582 RuntimeBinding(
584 RuntimeBinding(
583 command,
585 command,
584 keys=old_keys,
586 keys=old_keys,
585 filter=old_filter,
587 filter=old_filter,
586 )
588 )
587 )
589 )
588
590
589 if new_keys != []:
591 if new_keys != []:
590 shortcuts_to_add.append(
592 shortcuts_to_add.append(
591 RuntimeBinding(
593 RuntimeBinding(
592 command,
594 command,
593 keys=new_keys or old_keys,
595 keys=new_keys or old_keys,
594 filter=filter_from_string(new_filter)
596 filter=(
595 if new_filter is not None
597 filter_from_string(new_filter)
596 else (
598 if new_filter is not None
597 old_filter
599 else (
598 if old_filter is not None
600 old_filter
599 else filter_from_string("always")
601 if old_filter is not None
602 else filter_from_string("always")
603 )
600 ),
604 ),
601 )
605 )
602 )
606 )
603
607
604 # rebuild the bindings list from scratch
608 # rebuild the bindings list from scratch
605 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
609 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
606 for binding in shortcuts_to_add:
610 for binding in shortcuts_to_add:
607 add_binding(key_bindings, binding)
611 add_binding(key_bindings, binding)
608
612
609 return key_bindings
613 return key_bindings
610
614
611 prompt_includes_vi_mode = Bool(True,
615 prompt_includes_vi_mode = Bool(True,
612 help="Display the current vi mode (when using vi editing mode)."
616 help="Display the current vi mode (when using vi editing mode)."
613 ).tag(config=True)
617 ).tag(config=True)
614
618
615 prompt_line_number_format = Unicode(
619 prompt_line_number_format = Unicode(
616 "",
620 "",
617 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)"
618 " the current line number and `rel_line` the relative line number."
622 " the current line number and `rel_line` the relative line number."
619 " 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 :"
620 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '"
624 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '"
621 " 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"
622 " 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."
623 " 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.",
624 ).tag(config=True)
628 ).tag(config=True)
625
629
626 @observe('term_title')
630 @observe('term_title')
627 def init_term_title(self, change=None):
631 def init_term_title(self, change=None):
628 # Enable or disable the terminal title.
632 # Enable or disable the terminal title.
629 if self.term_title and _is_tty:
633 if self.term_title and _is_tty:
630 toggle_set_term_title(True)
634 toggle_set_term_title(True)
631 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
635 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
632 else:
636 else:
633 toggle_set_term_title(False)
637 toggle_set_term_title(False)
634
638
635 def restore_term_title(self):
639 def restore_term_title(self):
636 if self.term_title and _is_tty:
640 if self.term_title and _is_tty:
637 restore_term_title()
641 restore_term_title()
638
642
639 def init_display_formatter(self):
643 def init_display_formatter(self):
640 super(TerminalInteractiveShell, self).init_display_formatter()
644 super(TerminalInteractiveShell, self).init_display_formatter()
641 # terminal only supports plain text
645 # terminal only supports plain text
642 self.display_formatter.active_types = ["text/plain"]
646 self.display_formatter.active_types = ["text/plain"]
643
647
644 def init_prompt_toolkit_cli(self):
648 def init_prompt_toolkit_cli(self):
645 if self.simple_prompt:
649 if self.simple_prompt:
646 # Fall back to plain non-interactive output for tests.
650 # Fall back to plain non-interactive output for tests.
647 # This is very limited.
651 # This is very limited.
648 def prompt():
652 def prompt():
649 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())
650 lines = [input(prompt_text)]
654 lines = [input(prompt_text)]
651 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())
652 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
656 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
653 lines.append( input(prompt_continuation) )
657 lines.append( input(prompt_continuation) )
654 return '\n'.join(lines)
658 return '\n'.join(lines)
655 self.prompt_for_code = prompt
659 self.prompt_for_code = prompt
656 return
660 return
657
661
658 # Set up keyboard shortcuts
662 # Set up keyboard shortcuts
659 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
663 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
660
664
661 # Pre-populate history from IPython's history database
665 # Pre-populate history from IPython's history database
662 history = PtkHistoryAdapter(self)
666 history = PtkHistoryAdapter(self)
663
667
664 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)
665 self.style = DynamicStyle(lambda: self._style)
669 self.style = DynamicStyle(lambda: self._style)
666
670
667 editing_mode = getattr(EditingMode, self.editing_mode.upper())
671 editing_mode = getattr(EditingMode, self.editing_mode.upper())
668
672
669 self._use_asyncio_inputhook = False
673 self._use_asyncio_inputhook = False
670 self.pt_app = PromptSession(
674 self.pt_app = PromptSession(
671 auto_suggest=self.auto_suggest,
675 auto_suggest=self.auto_suggest,
672 editing_mode=editing_mode,
676 editing_mode=editing_mode,
673 key_bindings=key_bindings,
677 key_bindings=key_bindings,
674 history=history,
678 history=history,
675 completer=IPythonPTCompleter(shell=self),
679 completer=IPythonPTCompleter(shell=self),
676 enable_history_search=self.enable_history_search,
680 enable_history_search=self.enable_history_search,
677 style=self.style,
681 style=self.style,
678 include_default_pygments_style=False,
682 include_default_pygments_style=False,
679 mouse_support=self.mouse_support,
683 mouse_support=self.mouse_support,
680 enable_open_in_editor=self.extra_open_editor_shortcuts,
684 enable_open_in_editor=self.extra_open_editor_shortcuts,
681 color_depth=self.color_depth,
685 color_depth=self.color_depth,
682 tempfile_suffix=".py",
686 tempfile_suffix=".py",
683 **self._extra_prompt_options(),
687 **self._extra_prompt_options(),
684 )
688 )
685 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
689 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
686 self.auto_suggest.connect(self.pt_app)
690 self.auto_suggest.connect(self.pt_app)
687
691
688 def _make_style_from_name_or_cls(self, name_or_cls):
692 def _make_style_from_name_or_cls(self, name_or_cls):
689 """
693 """
690 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
691
695
692 We need that to add style for prompt ... etc.
696 We need that to add style for prompt ... etc.
693 """
697 """
694 style_overrides = {}
698 style_overrides = {}
695 if name_or_cls == 'legacy':
699 if name_or_cls == 'legacy':
696 legacy = self.colors.lower()
700 legacy = self.colors.lower()
697 if legacy == 'linux':
701 if legacy == 'linux':
698 style_cls = get_style_by_name('monokai')
702 style_cls = get_style_by_name('monokai')
699 style_overrides = _style_overrides_linux
703 style_overrides = _style_overrides_linux
700 elif legacy == 'lightbg':
704 elif legacy == 'lightbg':
701 style_overrides = _style_overrides_light_bg
705 style_overrides = _style_overrides_light_bg
702 style_cls = get_style_by_name('pastie')
706 style_cls = get_style_by_name('pastie')
703 elif legacy == 'neutral':
707 elif legacy == 'neutral':
704 # 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
705 # 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
706 # looks like. These tweaks to the default theme help with that.
710 # looks like. These tweaks to the default theme help with that.
707 style_cls = get_style_by_name('default')
711 style_cls = get_style_by_name('default')
708 style_overrides.update({
712 style_overrides.update({
709 Token.Number: '#ansigreen',
713 Token.Number: '#ansigreen',
710 Token.Operator: 'noinherit',
714 Token.Operator: 'noinherit',
711 Token.String: '#ansiyellow',
715 Token.String: '#ansiyellow',
712 Token.Name.Function: '#ansiblue',
716 Token.Name.Function: '#ansiblue',
713 Token.Name.Class: 'bold #ansiblue',
717 Token.Name.Class: 'bold #ansiblue',
714 Token.Name.Namespace: 'bold #ansiblue',
718 Token.Name.Namespace: 'bold #ansiblue',
715 Token.Name.Variable.Magic: '#ansiblue',
719 Token.Name.Variable.Magic: '#ansiblue',
716 Token.Prompt: '#ansigreen',
720 Token.Prompt: '#ansigreen',
717 Token.PromptNum: '#ansibrightgreen bold',
721 Token.PromptNum: '#ansibrightgreen bold',
718 Token.OutPrompt: '#ansired',
722 Token.OutPrompt: '#ansired',
719 Token.OutPromptNum: '#ansibrightred bold',
723 Token.OutPromptNum: '#ansibrightred bold',
720 })
724 })
721
725
722 # Hack: Due to limited color support on the Windows console
726 # Hack: Due to limited color support on the Windows console
723 # the prompt colors will be wrong without this
727 # the prompt colors will be wrong without this
724 if os.name == 'nt':
728 if os.name == 'nt':
725 style_overrides.update({
729 style_overrides.update({
726 Token.Prompt: '#ansidarkgreen',
730 Token.Prompt: '#ansidarkgreen',
727 Token.PromptNum: '#ansigreen bold',
731 Token.PromptNum: '#ansigreen bold',
728 Token.OutPrompt: '#ansidarkred',
732 Token.OutPrompt: '#ansidarkred',
729 Token.OutPromptNum: '#ansired bold',
733 Token.OutPromptNum: '#ansired bold',
730 })
734 })
731 elif legacy =='nocolor':
735 elif legacy =='nocolor':
732 style_cls=_NoStyle
736 style_cls=_NoStyle
733 style_overrides = {}
737 style_overrides = {}
734 else :
738 else :
735 raise ValueError('Got unknown colors: ', legacy)
739 raise ValueError('Got unknown colors: ', legacy)
736 else :
740 else :
737 if isinstance(name_or_cls, str):
741 if isinstance(name_or_cls, str):
738 style_cls = get_style_by_name(name_or_cls)
742 style_cls = get_style_by_name(name_or_cls)
739 else:
743 else:
740 style_cls = name_or_cls
744 style_cls = name_or_cls
741 style_overrides = {
745 style_overrides = {
742 Token.Prompt: '#ansigreen',
746 Token.Prompt: '#ansigreen',
743 Token.PromptNum: '#ansibrightgreen bold',
747 Token.PromptNum: '#ansibrightgreen bold',
744 Token.OutPrompt: '#ansired',
748 Token.OutPrompt: '#ansired',
745 Token.OutPromptNum: '#ansibrightred bold',
749 Token.OutPromptNum: '#ansibrightred bold',
746 }
750 }
747 style_overrides.update(self.highlighting_style_overrides)
751 style_overrides.update(self.highlighting_style_overrides)
748 style = merge_styles([
752 style = merge_styles([
749 style_from_pygments_cls(style_cls),
753 style_from_pygments_cls(style_cls),
750 style_from_pygments_dict(style_overrides),
754 style_from_pygments_dict(style_overrides),
751 ])
755 ])
752
756
753 return style
757 return style
754
758
755 @property
759 @property
756 def pt_complete_style(self):
760 def pt_complete_style(self):
757 return {
761 return {
758 'multicolumn': CompleteStyle.MULTI_COLUMN,
762 'multicolumn': CompleteStyle.MULTI_COLUMN,
759 'column': CompleteStyle.COLUMN,
763 'column': CompleteStyle.COLUMN,
760 'readlinelike': CompleteStyle.READLINE_LIKE,
764 'readlinelike': CompleteStyle.READLINE_LIKE,
761 }[self.display_completions]
765 }[self.display_completions]
762
766
763 @property
767 @property
764 def color_depth(self):
768 def color_depth(self):
765 return (ColorDepth.TRUE_COLOR if self.true_color else None)
769 return (ColorDepth.TRUE_COLOR if self.true_color else None)
766
770
767 def _extra_prompt_options(self):
771 def _extra_prompt_options(self):
768 """
772 """
769 Return the current layout option for the current Terminal InteractiveShell
773 Return the current layout option for the current Terminal InteractiveShell
770 """
774 """
771 def get_message():
775 def get_message():
772 return PygmentsTokens(self.prompts.in_prompt_tokens())
776 return PygmentsTokens(self.prompts.in_prompt_tokens())
773
777
774 if self.editing_mode == "emacs" and self.prompt_line_number_format == "":
778 if self.editing_mode == "emacs" and self.prompt_line_number_format == "":
775 # 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
776 # 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
777 # [nor] so we can't precompute.
781 # [nor] so we can't precompute.
778 # here I'm going to favor the default keybinding which almost
782 # here I'm going to favor the default keybinding which almost
779 # everybody uses to decrease CPU usage.
783 # everybody uses to decrease CPU usage.
780 # 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
781 # work around this.
785 # work around this.
782 get_message = get_message()
786 get_message = get_message()
783
787
784 options = {
788 options = {
785 "complete_in_thread": False,
789 "complete_in_thread": False,
786 "lexer": IPythonPTLexer(),
790 "lexer": IPythonPTLexer(),
787 "reserve_space_for_menu": self.space_for_menu,
791 "reserve_space_for_menu": self.space_for_menu,
788 "message": get_message,
792 "message": get_message,
789 "prompt_continuation": (
793 "prompt_continuation": (
790 lambda width, lineno, is_soft_wrap: PygmentsTokens(
794 lambda width, lineno, is_soft_wrap: PygmentsTokens(
791 _backward_compat_continuation_prompt_tokens(
795 _backward_compat_continuation_prompt_tokens(
792 self.prompts.continuation_prompt_tokens, width, lineno=lineno
796 self.prompts.continuation_prompt_tokens, width, lineno=lineno
793 )
797 )
794 )
798 )
795 ),
799 ),
796 "multiline": True,
800 "multiline": True,
797 "complete_style": self.pt_complete_style,
801 "complete_style": self.pt_complete_style,
798 "input_processors": [
802 "input_processors": [
799 # Highlight matching brackets, but only when this setting is
803 # Highlight matching brackets, but only when this setting is
800 # enabled, and only when the DEFAULT_BUFFER has the focus.
804 # enabled, and only when the DEFAULT_BUFFER has the focus.
801 ConditionalProcessor(
805 ConditionalProcessor(
802 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
806 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
803 filter=HasFocus(DEFAULT_BUFFER)
807 filter=HasFocus(DEFAULT_BUFFER)
804 & ~IsDone()
808 & ~IsDone()
805 & Condition(lambda: self.highlight_matching_brackets),
809 & Condition(lambda: self.highlight_matching_brackets),
806 ),
810 ),
807 # Show auto-suggestion in lines other than the last line.
811 # Show auto-suggestion in lines other than the last line.
808 ConditionalProcessor(
812 ConditionalProcessor(
809 processor=AppendAutoSuggestionInAnyLine(),
813 processor=AppendAutoSuggestionInAnyLine(),
810 filter=HasFocus(DEFAULT_BUFFER)
814 filter=HasFocus(DEFAULT_BUFFER)
811 & ~IsDone()
815 & ~IsDone()
812 & Condition(
816 & Condition(
813 lambda: isinstance(
817 lambda: isinstance(
814 self.auto_suggest, NavigableAutoSuggestFromHistory
818 self.auto_suggest, NavigableAutoSuggestFromHistory
815 )
819 )
816 ),
820 ),
817 ),
821 ),
818 ],
822 ],
819 }
823 }
820 if not PTK3:
824 if not PTK3:
821 options['inputhook'] = self.inputhook
825 options['inputhook'] = self.inputhook
822
826
823 return options
827 return options
824
828
825 def prompt_for_code(self):
829 def prompt_for_code(self):
826 if self.rl_next_input:
830 if self.rl_next_input:
827 default = self.rl_next_input
831 default = self.rl_next_input
828 self.rl_next_input = None
832 self.rl_next_input = None
829 else:
833 else:
830 default = ''
834 default = ''
831
835
832 # In order to make sure that asyncio code written in the
836 # In order to make sure that asyncio code written in the
833 # interactive shell doesn't interfere with the prompt, we run the
837 # interactive shell doesn't interfere with the prompt, we run the
834 # prompt in a different event loop.
838 # prompt in a different event loop.
835 # 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
836 # while/true inside which will freeze the prompt.
840 # while/true inside which will freeze the prompt.
837
841
838 with patch_stdout(raw=True):
842 with patch_stdout(raw=True):
839 if self._use_asyncio_inputhook:
843 if self._use_asyncio_inputhook:
840 # 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
841 # 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
842 # input hook. (Asyncio is not made for nesting event loops.)
846 # input hook. (Asyncio is not made for nesting event loops.)
843 asyncio_loop = get_asyncio_loop()
847 asyncio_loop = get_asyncio_loop()
844 text = asyncio_loop.run_until_complete(
848 text = asyncio_loop.run_until_complete(
845 self.pt_app.prompt_async(
849 self.pt_app.prompt_async(
846 default=default, **self._extra_prompt_options()
850 default=default, **self._extra_prompt_options()
847 )
851 )
848 )
852 )
849 else:
853 else:
850 text = self.pt_app.prompt(
854 text = self.pt_app.prompt(
851 default=default,
855 default=default,
852 inputhook=self._inputhook,
856 inputhook=self._inputhook,
853 **self._extra_prompt_options(),
857 **self._extra_prompt_options(),
854 )
858 )
855
859
856 return text
860 return text
857
861
858 def enable_win_unicode_console(self):
862 def enable_win_unicode_console(self):
859 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
863 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
860 # console by default, so WUC shouldn't be needed.
864 # console by default, so WUC shouldn't be needed.
861 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
865 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
862 DeprecationWarning,
866 DeprecationWarning,
863 stacklevel=2)
867 stacklevel=2)
864
868
865 def init_io(self):
869 def init_io(self):
866 if sys.platform not in {'win32', 'cli'}:
870 if sys.platform not in {'win32', 'cli'}:
867 return
871 return
868
872
869 import colorama
873 import colorama
870 colorama.init()
874 colorama.init()
871
875
872 def init_magics(self):
876 def init_magics(self):
873 super(TerminalInteractiveShell, self).init_magics()
877 super(TerminalInteractiveShell, self).init_magics()
874 self.register_magics(TerminalMagics)
878 self.register_magics(TerminalMagics)
875
879
876 def init_alias(self):
880 def init_alias(self):
877 # The parent class defines aliases that can be safely used with any
881 # The parent class defines aliases that can be safely used with any
878 # frontend.
882 # frontend.
879 super(TerminalInteractiveShell, self).init_alias()
883 super(TerminalInteractiveShell, self).init_alias()
880
884
881 # Now define aliases that only make sense on the terminal, because they
885 # Now define aliases that only make sense on the terminal, because they
882 # need direct access to the console in a way that we can't emulate in
886 # need direct access to the console in a way that we can't emulate in
883 # GUI or web frontend
887 # GUI or web frontend
884 if os.name == 'posix':
888 if os.name == 'posix':
885 for cmd in ('clear', 'more', 'less', 'man'):
889 for cmd in ('clear', 'more', 'less', 'man'):
886 self.alias_manager.soft_define_alias(cmd, cmd)
890 self.alias_manager.soft_define_alias(cmd, cmd)
887
891
888 def __init__(self, *args, **kwargs) -> None:
892 def __init__(self, *args, **kwargs) -> None:
889 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
893 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
890 self._set_autosuggestions(self.autosuggestions_provider)
894 self._set_autosuggestions(self.autosuggestions_provider)
891 self.init_prompt_toolkit_cli()
895 self.init_prompt_toolkit_cli()
892 self.init_term_title()
896 self.init_term_title()
893 self.keep_running = True
897 self.keep_running = True
894 self._set_formatter(self.autoformatter)
898 self._set_formatter(self.autoformatter)
895
899
896 def ask_exit(self):
900 def ask_exit(self):
897 self.keep_running = False
901 self.keep_running = False
898
902
899 rl_next_input = None
903 rl_next_input = None
900
904
901 def interact(self):
905 def interact(self):
902 self.keep_running = True
906 self.keep_running = True
903 while self.keep_running:
907 while self.keep_running:
904 print(self.separate_in, end='')
908 print(self.separate_in, end='')
905
909
906 try:
910 try:
907 code = self.prompt_for_code()
911 code = self.prompt_for_code()
908 except EOFError:
912 except EOFError:
909 if (not self.confirm_exit) \
913 if (not self.confirm_exit) \
910 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
914 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
911 self.ask_exit()
915 self.ask_exit()
912
916
913 else:
917 else:
914 if code:
918 if code:
915 self.run_cell(code, store_history=True)
919 self.run_cell(code, store_history=True)
916
920
917 def mainloop(self):
921 def mainloop(self):
918 # An extra layer of protection in case someone mashing Ctrl-C breaks
922 # An extra layer of protection in case someone mashing Ctrl-C breaks
919 # out of our internal code.
923 # out of our internal code.
920 while True:
924 while True:
921 try:
925 try:
922 self.interact()
926 self.interact()
923 break
927 break
924 except KeyboardInterrupt as e:
928 except KeyboardInterrupt as e:
925 print("\n%s escaped interact()\n" % type(e).__name__)
929 print("\n%s escaped interact()\n" % type(e).__name__)
926 finally:
930 finally:
927 # An interrupt during the eventloop will mess up the
931 # An interrupt during the eventloop will mess up the
928 # internal state of the prompt_toolkit library.
932 # internal state of the prompt_toolkit library.
929 # Stopping the eventloop fixes this, see
933 # Stopping the eventloop fixes this, see
930 # https://github.com/ipython/ipython/pull/9867
934 # https://github.com/ipython/ipython/pull/9867
931 if hasattr(self, '_eventloop'):
935 if hasattr(self, '_eventloop'):
932 self._eventloop.stop()
936 self._eventloop.stop()
933
937
934 self.restore_term_title()
938 self.restore_term_title()
935
939
936 # try to call some at-exit operation optimistically as some things can't
940 # try to call some at-exit operation optimistically as some things can't
937 # be done during interpreter shutdown. this is technically inaccurate as
941 # be done during interpreter shutdown. this is technically inaccurate as
938 # this make mainlool not re-callable, but that should be a rare if not
942 # this make mainlool not re-callable, but that should be a rare if not
939 # in existent use case.
943 # in existent use case.
940
944
941 self._atexit_once()
945 self._atexit_once()
942
946
943 _inputhook = None
947 _inputhook = None
944 def inputhook(self, context):
948 def inputhook(self, context):
945 if self._inputhook is not None:
949 if self._inputhook is not None:
946 self._inputhook(context)
950 self._inputhook(context)
947
951
948 active_eventloop: Optional[str] = None
952 active_eventloop: Optional[str] = None
949
953
950 def enable_gui(self, gui: Optional[str] = None) -> None:
954 def enable_gui(self, gui: Optional[str] = None) -> None:
951 if gui:
955 if gui:
952 from ..core.pylabtools import _convert_gui_from_matplotlib
956 from ..core.pylabtools import _convert_gui_from_matplotlib
953
957
954 gui = _convert_gui_from_matplotlib(gui)
958 gui = _convert_gui_from_matplotlib(gui)
955
959
956 if self.simple_prompt is True and gui is not None:
960 if self.simple_prompt is True and gui is not None:
957 print(
961 print(
958 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.'
962 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.'
959 )
963 )
960 print(
964 print(
961 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`."
965 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`."
962 )
966 )
963 return
967 return
964
968
965 if self._inputhook is None and gui is None:
969 if self._inputhook is None and gui is None:
966 print("No event loop hook running.")
970 print("No event loop hook running.")
967 return
971 return
968
972
969 if self._inputhook is not None and gui is not None:
973 if self._inputhook is not None and gui is not None:
970 newev, newinhook = get_inputhook_name_and_func(gui)
974 newev, newinhook = get_inputhook_name_and_func(gui)
971 if self._inputhook == newinhook:
975 if self._inputhook == newinhook:
972 # same inputhook, do nothing
976 # same inputhook, do nothing
973 self.log.info(
977 self.log.info(
974 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing"
978 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing"
975 )
979 )
976 return
980 return
977 self.log.warning(
981 self.log.warning(
978 f"Shell is already running a different gui event loop for {self.active_eventloop}. "
982 f"Shell is already running a different gui event loop for {self.active_eventloop}. "
979 "Call with no arguments to disable the current loop."
983 "Call with no arguments to disable the current loop."
980 )
984 )
981 return
985 return
982 if self._inputhook is not None and gui is None:
986 if self._inputhook is not None and gui is None:
983 self.active_eventloop = self._inputhook = None
987 self.active_eventloop = self._inputhook = None
984
988
985 if gui and (gui not in {None, "webagg"}):
989 if gui and (gui not in {None, "webagg"}):
986 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
990 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
987 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
991 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
988 else:
992 else:
989 self.active_eventloop = self._inputhook = None
993 self.active_eventloop = self._inputhook = None
990
994
991 self._use_asyncio_inputhook = gui == "asyncio"
995 self._use_asyncio_inputhook = gui == "asyncio"
992
996
993 # Run !system commands directly, not through pipes, so terminal programs
997 # Run !system commands directly, not through pipes, so terminal programs
994 # work correctly.
998 # work correctly.
995 system = InteractiveShell.system_raw
999 system = InteractiveShell.system_raw
996
1000
997 def auto_rewrite_input(self, cmd):
1001 def auto_rewrite_input(self, cmd):
998 """Overridden from the parent class to use fancy rewriting prompt"""
1002 """Overridden from the parent class to use fancy rewriting prompt"""
999 if not self.show_rewritten_input:
1003 if not self.show_rewritten_input:
1000 return
1004 return
1001
1005
1002 tokens = self.prompts.rewrite_prompt_tokens()
1006 tokens = self.prompts.rewrite_prompt_tokens()
1003 if self.pt_app:
1007 if self.pt_app:
1004 print_formatted_text(PygmentsTokens(tokens), end='',
1008 print_formatted_text(PygmentsTokens(tokens), end='',
1005 style=self.pt_app.app.style)
1009 style=self.pt_app.app.style)
1006 print(cmd)
1010 print(cmd)
1007 else:
1011 else:
1008 prompt = ''.join(s for t, s in tokens)
1012 prompt = ''.join(s for t, s in tokens)
1009 print(prompt, cmd, sep='')
1013 print(prompt, cmd, sep='')
1010
1014
1011 _prompts_before = None
1015 _prompts_before = None
1012 def switch_doctest_mode(self, mode):
1016 def switch_doctest_mode(self, mode):
1013 """Switch prompts to classic for %doctest_mode"""
1017 """Switch prompts to classic for %doctest_mode"""
1014 if mode:
1018 if mode:
1015 self._prompts_before = self.prompts
1019 self._prompts_before = self.prompts
1016 self.prompts = ClassicPrompts(self)
1020 self.prompts = ClassicPrompts(self)
1017 elif self._prompts_before:
1021 elif self._prompts_before:
1018 self.prompts = self._prompts_before
1022 self.prompts = self._prompts_before
1019 self._prompts_before = None
1023 self._prompts_before = None
1020 # self._update_layout()
1024 # self._update_layout()
1021
1025
1022
1026
1023 InteractiveShellABC.register(TerminalInteractiveShell)
1027 InteractiveShellABC.register(TerminalInteractiveShell)
1024
1028
1025 if __name__ == '__main__':
1029 if __name__ == '__main__':
1026 TerminalInteractiveShell.instance().interact()
1030 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now