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