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