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