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