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