##// END OF EJS Templates
Merge pull request #13091 from madbird1304/madbird1304/issue#12897...
Matthias Bussonnier -
r26757:e7680611 merge
parent child Browse files
Show More
@@ -1,693 +1,692 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
204
205 @observe('editing_mode')
205 @observe('editing_mode')
206 def _editing_mode(self, change):
206 def _editing_mode(self, change):
207 u_mode = change.new.upper()
208 if self.pt_app:
207 if self.pt_app:
209 self.pt_app.editing_mode = u_mode
208 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
210
209
211 @observe('autoformatter')
210 @observe('autoformatter')
212 def _autoformatter_changed(self, change):
211 def _autoformatter_changed(self, change):
213 formatter = change.new
212 formatter = change.new
214 if formatter is None:
213 if formatter is None:
215 self.reformat_handler = lambda x:x
214 self.reformat_handler = lambda x:x
216 elif formatter == 'black':
215 elif formatter == 'black':
217 self.reformat_handler = black_reformat_handler
216 self.reformat_handler = black_reformat_handler
218 else:
217 else:
219 raise ValueError
218 raise ValueError
220
219
221 @observe('highlighting_style')
220 @observe('highlighting_style')
222 @observe('colors')
221 @observe('colors')
223 def _highlighting_style_changed(self, change):
222 def _highlighting_style_changed(self, change):
224 self.refresh_style()
223 self.refresh_style()
225
224
226 def refresh_style(self):
225 def refresh_style(self):
227 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
226 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
228
227
229
228
230 highlighting_style_overrides = Dict(
229 highlighting_style_overrides = Dict(
231 help="Override highlighting format for specific tokens"
230 help="Override highlighting format for specific tokens"
232 ).tag(config=True)
231 ).tag(config=True)
233
232
234 true_color = Bool(False,
233 true_color = Bool(False,
235 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
234 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
236 If your terminal supports true color, the following command should
235 If your terminal supports true color, the following command should
237 print ``TRUECOLOR`` in orange::
236 print ``TRUECOLOR`` in orange::
238
237
239 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
238 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
240 """,
239 """,
241 ).tag(config=True)
240 ).tag(config=True)
242
241
243 editor = Unicode(get_default_editor(),
242 editor = Unicode(get_default_editor(),
244 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
243 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
245 ).tag(config=True)
244 ).tag(config=True)
246
245
247 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
246 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
248
247
249 prompts = Instance(Prompts)
248 prompts = Instance(Prompts)
250
249
251 @default('prompts')
250 @default('prompts')
252 def _prompts_default(self):
251 def _prompts_default(self):
253 return self.prompts_class(self)
252 return self.prompts_class(self)
254
253
255 # @observe('prompts')
254 # @observe('prompts')
256 # def _(self, change):
255 # def _(self, change):
257 # self._update_layout()
256 # self._update_layout()
258
257
259 @default('displayhook_class')
258 @default('displayhook_class')
260 def _displayhook_class_default(self):
259 def _displayhook_class_default(self):
261 return RichPromptDisplayHook
260 return RichPromptDisplayHook
262
261
263 term_title = Bool(True,
262 term_title = Bool(True,
264 help="Automatically set the terminal title"
263 help="Automatically set the terminal title"
265 ).tag(config=True)
264 ).tag(config=True)
266
265
267 term_title_format = Unicode("IPython: {cwd}",
266 term_title_format = Unicode("IPython: {cwd}",
268 help="Customize the terminal title format. This is a python format string. " +
267 help="Customize the terminal title format. This is a python format string. " +
269 "Available substitutions are: {cwd}."
268 "Available substitutions are: {cwd}."
270 ).tag(config=True)
269 ).tag(config=True)
271
270
272 display_completions = Enum(('column', 'multicolumn','readlinelike'),
271 display_completions = Enum(('column', 'multicolumn','readlinelike'),
273 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
272 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
274 "'readlinelike'. These options are for `prompt_toolkit`, see "
273 "'readlinelike'. These options are for `prompt_toolkit`, see "
275 "`prompt_toolkit` documentation for more information."
274 "`prompt_toolkit` documentation for more information."
276 ),
275 ),
277 default_value='multicolumn').tag(config=True)
276 default_value='multicolumn').tag(config=True)
278
277
279 highlight_matching_brackets = Bool(True,
278 highlight_matching_brackets = Bool(True,
280 help="Highlight matching brackets.",
279 help="Highlight matching brackets.",
281 ).tag(config=True)
280 ).tag(config=True)
282
281
283 extra_open_editor_shortcuts = Bool(False,
282 extra_open_editor_shortcuts = Bool(False,
284 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
283 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
285 "This is in addition to the F2 binding, which is always enabled."
284 "This is in addition to the F2 binding, which is always enabled."
286 ).tag(config=True)
285 ).tag(config=True)
287
286
288 handle_return = Any(None,
287 handle_return = Any(None,
289 help="Provide an alternative handler to be called when the user presses "
288 help="Provide an alternative handler to be called when the user presses "
290 "Return. This is an advanced option intended for debugging, which "
289 "Return. This is an advanced option intended for debugging, which "
291 "may be changed or removed in later releases."
290 "may be changed or removed in later releases."
292 ).tag(config=True)
291 ).tag(config=True)
293
292
294 enable_history_search = Bool(True,
293 enable_history_search = Bool(True,
295 help="Allows to enable/disable the prompt toolkit history search"
294 help="Allows to enable/disable the prompt toolkit history search"
296 ).tag(config=True)
295 ).tag(config=True)
297
296
298 prompt_includes_vi_mode = Bool(True,
297 prompt_includes_vi_mode = Bool(True,
299 help="Display the current vi mode (when using vi editing mode)."
298 help="Display the current vi mode (when using vi editing mode)."
300 ).tag(config=True)
299 ).tag(config=True)
301
300
302 @observe('term_title')
301 @observe('term_title')
303 def init_term_title(self, change=None):
302 def init_term_title(self, change=None):
304 # Enable or disable the terminal title.
303 # Enable or disable the terminal title.
305 if self.term_title:
304 if self.term_title:
306 toggle_set_term_title(True)
305 toggle_set_term_title(True)
307 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
306 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
308 else:
307 else:
309 toggle_set_term_title(False)
308 toggle_set_term_title(False)
310
309
311 def restore_term_title(self):
310 def restore_term_title(self):
312 if self.term_title:
311 if self.term_title:
313 restore_term_title()
312 restore_term_title()
314
313
315 def init_display_formatter(self):
314 def init_display_formatter(self):
316 super(TerminalInteractiveShell, self).init_display_formatter()
315 super(TerminalInteractiveShell, self).init_display_formatter()
317 # terminal only supports plain text
316 # terminal only supports plain text
318 self.display_formatter.active_types = ['text/plain']
317 self.display_formatter.active_types = ['text/plain']
319 # disable `_ipython_display_`
318 # disable `_ipython_display_`
320 self.display_formatter.ipython_display_formatter.enabled = False
319 self.display_formatter.ipython_display_formatter.enabled = False
321
320
322 def init_prompt_toolkit_cli(self):
321 def init_prompt_toolkit_cli(self):
323 if self.simple_prompt:
322 if self.simple_prompt:
324 # Fall back to plain non-interactive output for tests.
323 # Fall back to plain non-interactive output for tests.
325 # This is very limited.
324 # This is very limited.
326 def prompt():
325 def prompt():
327 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
326 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
328 lines = [input(prompt_text)]
327 lines = [input(prompt_text)]
329 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
328 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
330 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
329 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
331 lines.append( input(prompt_continuation) )
330 lines.append( input(prompt_continuation) )
332 return '\n'.join(lines)
331 return '\n'.join(lines)
333 self.prompt_for_code = prompt
332 self.prompt_for_code = prompt
334 return
333 return
335
334
336 # Set up keyboard shortcuts
335 # Set up keyboard shortcuts
337 key_bindings = create_ipython_shortcuts(self)
336 key_bindings = create_ipython_shortcuts(self)
338
337
339 # Pre-populate history from IPython's history database
338 # Pre-populate history from IPython's history database
340 history = InMemoryHistory()
339 history = InMemoryHistory()
341 last_cell = u""
340 last_cell = u""
342 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
341 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
343 include_latest=True):
342 include_latest=True):
344 # Ignore blank lines and consecutive duplicates
343 # Ignore blank lines and consecutive duplicates
345 cell = cell.rstrip()
344 cell = cell.rstrip()
346 if cell and (cell != last_cell):
345 if cell and (cell != last_cell):
347 history.append_string(cell)
346 history.append_string(cell)
348 last_cell = cell
347 last_cell = cell
349
348
350 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
349 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
351 self.style = DynamicStyle(lambda: self._style)
350 self.style = DynamicStyle(lambda: self._style)
352
351
353 editing_mode = getattr(EditingMode, self.editing_mode.upper())
352 editing_mode = getattr(EditingMode, self.editing_mode.upper())
354
353
355 self.pt_loop = asyncio.new_event_loop()
354 self.pt_loop = asyncio.new_event_loop()
356 self.pt_app = PromptSession(
355 self.pt_app = PromptSession(
357 auto_suggest=AutoSuggestFromHistory(),
356 auto_suggest=AutoSuggestFromHistory(),
358 editing_mode=editing_mode,
357 editing_mode=editing_mode,
359 key_bindings=key_bindings,
358 key_bindings=key_bindings,
360 history=history,
359 history=history,
361 completer=IPythonPTCompleter(shell=self),
360 completer=IPythonPTCompleter(shell=self),
362 enable_history_search=self.enable_history_search,
361 enable_history_search=self.enable_history_search,
363 style=self.style,
362 style=self.style,
364 include_default_pygments_style=False,
363 include_default_pygments_style=False,
365 mouse_support=self.mouse_support,
364 mouse_support=self.mouse_support,
366 enable_open_in_editor=self.extra_open_editor_shortcuts,
365 enable_open_in_editor=self.extra_open_editor_shortcuts,
367 color_depth=self.color_depth,
366 color_depth=self.color_depth,
368 tempfile_suffix=".py",
367 tempfile_suffix=".py",
369 **self._extra_prompt_options()
368 **self._extra_prompt_options()
370 )
369 )
371
370
372 def _make_style_from_name_or_cls(self, name_or_cls):
371 def _make_style_from_name_or_cls(self, name_or_cls):
373 """
372 """
374 Small wrapper that make an IPython compatible style from a style name
373 Small wrapper that make an IPython compatible style from a style name
375
374
376 We need that to add style for prompt ... etc.
375 We need that to add style for prompt ... etc.
377 """
376 """
378 style_overrides = {}
377 style_overrides = {}
379 if name_or_cls == 'legacy':
378 if name_or_cls == 'legacy':
380 legacy = self.colors.lower()
379 legacy = self.colors.lower()
381 if legacy == 'linux':
380 if legacy == 'linux':
382 style_cls = get_style_by_name('monokai')
381 style_cls = get_style_by_name('monokai')
383 style_overrides = _style_overrides_linux
382 style_overrides = _style_overrides_linux
384 elif legacy == 'lightbg':
383 elif legacy == 'lightbg':
385 style_overrides = _style_overrides_light_bg
384 style_overrides = _style_overrides_light_bg
386 style_cls = get_style_by_name('pastie')
385 style_cls = get_style_by_name('pastie')
387 elif legacy == 'neutral':
386 elif legacy == 'neutral':
388 # The default theme needs to be visible on both a dark background
387 # The default theme needs to be visible on both a dark background
389 # and a light background, because we can't tell what the terminal
388 # and a light background, because we can't tell what the terminal
390 # looks like. These tweaks to the default theme help with that.
389 # looks like. These tweaks to the default theme help with that.
391 style_cls = get_style_by_name('default')
390 style_cls = get_style_by_name('default')
392 style_overrides.update({
391 style_overrides.update({
393 Token.Number: '#ansigreen',
392 Token.Number: '#ansigreen',
394 Token.Operator: 'noinherit',
393 Token.Operator: 'noinherit',
395 Token.String: '#ansiyellow',
394 Token.String: '#ansiyellow',
396 Token.Name.Function: '#ansiblue',
395 Token.Name.Function: '#ansiblue',
397 Token.Name.Class: 'bold #ansiblue',
396 Token.Name.Class: 'bold #ansiblue',
398 Token.Name.Namespace: 'bold #ansiblue',
397 Token.Name.Namespace: 'bold #ansiblue',
399 Token.Name.Variable.Magic: '#ansiblue',
398 Token.Name.Variable.Magic: '#ansiblue',
400 Token.Prompt: '#ansigreen',
399 Token.Prompt: '#ansigreen',
401 Token.PromptNum: '#ansibrightgreen bold',
400 Token.PromptNum: '#ansibrightgreen bold',
402 Token.OutPrompt: '#ansired',
401 Token.OutPrompt: '#ansired',
403 Token.OutPromptNum: '#ansibrightred bold',
402 Token.OutPromptNum: '#ansibrightred bold',
404 })
403 })
405
404
406 # Hack: Due to limited color support on the Windows console
405 # Hack: Due to limited color support on the Windows console
407 # the prompt colors will be wrong without this
406 # the prompt colors will be wrong without this
408 if os.name == 'nt':
407 if os.name == 'nt':
409 style_overrides.update({
408 style_overrides.update({
410 Token.Prompt: '#ansidarkgreen',
409 Token.Prompt: '#ansidarkgreen',
411 Token.PromptNum: '#ansigreen bold',
410 Token.PromptNum: '#ansigreen bold',
412 Token.OutPrompt: '#ansidarkred',
411 Token.OutPrompt: '#ansidarkred',
413 Token.OutPromptNum: '#ansired bold',
412 Token.OutPromptNum: '#ansired bold',
414 })
413 })
415 elif legacy =='nocolor':
414 elif legacy =='nocolor':
416 style_cls=_NoStyle
415 style_cls=_NoStyle
417 style_overrides = {}
416 style_overrides = {}
418 else :
417 else :
419 raise ValueError('Got unknown colors: ', legacy)
418 raise ValueError('Got unknown colors: ', legacy)
420 else :
419 else :
421 if isinstance(name_or_cls, str):
420 if isinstance(name_or_cls, str):
422 style_cls = get_style_by_name(name_or_cls)
421 style_cls = get_style_by_name(name_or_cls)
423 else:
422 else:
424 style_cls = name_or_cls
423 style_cls = name_or_cls
425 style_overrides = {
424 style_overrides = {
426 Token.Prompt: '#ansigreen',
425 Token.Prompt: '#ansigreen',
427 Token.PromptNum: '#ansibrightgreen bold',
426 Token.PromptNum: '#ansibrightgreen bold',
428 Token.OutPrompt: '#ansired',
427 Token.OutPrompt: '#ansired',
429 Token.OutPromptNum: '#ansibrightred bold',
428 Token.OutPromptNum: '#ansibrightred bold',
430 }
429 }
431 style_overrides.update(self.highlighting_style_overrides)
430 style_overrides.update(self.highlighting_style_overrides)
432 style = merge_styles([
431 style = merge_styles([
433 style_from_pygments_cls(style_cls),
432 style_from_pygments_cls(style_cls),
434 style_from_pygments_dict(style_overrides),
433 style_from_pygments_dict(style_overrides),
435 ])
434 ])
436
435
437 return style
436 return style
438
437
439 @property
438 @property
440 def pt_complete_style(self):
439 def pt_complete_style(self):
441 return {
440 return {
442 'multicolumn': CompleteStyle.MULTI_COLUMN,
441 'multicolumn': CompleteStyle.MULTI_COLUMN,
443 'column': CompleteStyle.COLUMN,
442 'column': CompleteStyle.COLUMN,
444 'readlinelike': CompleteStyle.READLINE_LIKE,
443 'readlinelike': CompleteStyle.READLINE_LIKE,
445 }[self.display_completions]
444 }[self.display_completions]
446
445
447 @property
446 @property
448 def color_depth(self):
447 def color_depth(self):
449 return (ColorDepth.TRUE_COLOR if self.true_color else None)
448 return (ColorDepth.TRUE_COLOR if self.true_color else None)
450
449
451 def _extra_prompt_options(self):
450 def _extra_prompt_options(self):
452 """
451 """
453 Return the current layout option for the current Terminal InteractiveShell
452 Return the current layout option for the current Terminal InteractiveShell
454 """
453 """
455 def get_message():
454 def get_message():
456 return PygmentsTokens(self.prompts.in_prompt_tokens())
455 return PygmentsTokens(self.prompts.in_prompt_tokens())
457
456
458 if self.editing_mode == 'emacs':
457 if self.editing_mode == 'emacs':
459 # with emacs mode the prompt is (usually) static, so we call only
458 # with emacs mode the prompt is (usually) static, so we call only
460 # the function once. With VI mode it can toggle between [ins] and
459 # the function once. With VI mode it can toggle between [ins] and
461 # [nor] so we can't precompute.
460 # [nor] so we can't precompute.
462 # here I'm going to favor the default keybinding which almost
461 # here I'm going to favor the default keybinding which almost
463 # everybody uses to decrease CPU usage.
462 # everybody uses to decrease CPU usage.
464 # if we have issues with users with custom Prompts we can see how to
463 # if we have issues with users with custom Prompts we can see how to
465 # work around this.
464 # work around this.
466 get_message = get_message()
465 get_message = get_message()
467
466
468 options = {
467 options = {
469 'complete_in_thread': False,
468 'complete_in_thread': False,
470 'lexer':IPythonPTLexer(),
469 'lexer':IPythonPTLexer(),
471 'reserve_space_for_menu':self.space_for_menu,
470 'reserve_space_for_menu':self.space_for_menu,
472 'message': get_message,
471 'message': get_message,
473 'prompt_continuation': (
472 'prompt_continuation': (
474 lambda width, lineno, is_soft_wrap:
473 lambda width, lineno, is_soft_wrap:
475 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
474 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
476 'multiline': True,
475 'multiline': True,
477 'complete_style': self.pt_complete_style,
476 'complete_style': self.pt_complete_style,
478
477
479 # Highlight matching brackets, but only when this setting is
478 # Highlight matching brackets, but only when this setting is
480 # enabled, and only when the DEFAULT_BUFFER has the focus.
479 # enabled, and only when the DEFAULT_BUFFER has the focus.
481 'input_processors': [ConditionalProcessor(
480 'input_processors': [ConditionalProcessor(
482 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
481 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
483 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
482 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
484 Condition(lambda: self.highlight_matching_brackets))],
483 Condition(lambda: self.highlight_matching_brackets))],
485 }
484 }
486 if not PTK3:
485 if not PTK3:
487 options['inputhook'] = self.inputhook
486 options['inputhook'] = self.inputhook
488
487
489 return options
488 return options
490
489
491 def prompt_for_code(self):
490 def prompt_for_code(self):
492 if self.rl_next_input:
491 if self.rl_next_input:
493 default = self.rl_next_input
492 default = self.rl_next_input
494 self.rl_next_input = None
493 self.rl_next_input = None
495 else:
494 else:
496 default = ''
495 default = ''
497
496
498 # In order to make sure that asyncio code written in the
497 # In order to make sure that asyncio code written in the
499 # interactive shell doesn't interfere with the prompt, we run the
498 # interactive shell doesn't interfere with the prompt, we run the
500 # prompt in a different event loop.
499 # prompt in a different event loop.
501 # If we don't do this, people could spawn coroutine with a
500 # If we don't do this, people could spawn coroutine with a
502 # while/true inside which will freeze the prompt.
501 # while/true inside which will freeze the prompt.
503
502
504 try:
503 try:
505 old_loop = asyncio.get_event_loop()
504 old_loop = asyncio.get_event_loop()
506 except RuntimeError:
505 except RuntimeError:
507 # This happens when the user used `asyncio.run()`.
506 # This happens when the user used `asyncio.run()`.
508 old_loop = None
507 old_loop = None
509
508
510 asyncio.set_event_loop(self.pt_loop)
509 asyncio.set_event_loop(self.pt_loop)
511 try:
510 try:
512 with patch_stdout(raw=True):
511 with patch_stdout(raw=True):
513 text = self.pt_app.prompt(
512 text = self.pt_app.prompt(
514 default=default,
513 default=default,
515 **self._extra_prompt_options())
514 **self._extra_prompt_options())
516 finally:
515 finally:
517 # Restore the original event loop.
516 # Restore the original event loop.
518 asyncio.set_event_loop(old_loop)
517 asyncio.set_event_loop(old_loop)
519
518
520 return text
519 return text
521
520
522 def enable_win_unicode_console(self):
521 def enable_win_unicode_console(self):
523 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
522 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
524 # console by default, so WUC shouldn't be needed.
523 # console by default, so WUC shouldn't be needed.
525 from warnings import warn
524 from warnings import warn
526 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
525 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
527 DeprecationWarning,
526 DeprecationWarning,
528 stacklevel=2)
527 stacklevel=2)
529
528
530 def init_io(self):
529 def init_io(self):
531 if sys.platform not in {'win32', 'cli'}:
530 if sys.platform not in {'win32', 'cli'}:
532 return
531 return
533
532
534 import colorama
533 import colorama
535 colorama.init()
534 colorama.init()
536
535
537 # For some reason we make these wrappers around stdout/stderr.
536 # For some reason we make these wrappers around stdout/stderr.
538 # For now, we need to reset them so all output gets coloured.
537 # For now, we need to reset them so all output gets coloured.
539 # https://github.com/ipython/ipython/issues/8669
538 # https://github.com/ipython/ipython/issues/8669
540 # io.std* are deprecated, but don't show our own deprecation warnings
539 # io.std* are deprecated, but don't show our own deprecation warnings
541 # during initialization of the deprecated API.
540 # during initialization of the deprecated API.
542 with warnings.catch_warnings():
541 with warnings.catch_warnings():
543 warnings.simplefilter('ignore', DeprecationWarning)
542 warnings.simplefilter('ignore', DeprecationWarning)
544 io.stdout = io.IOStream(sys.stdout)
543 io.stdout = io.IOStream(sys.stdout)
545 io.stderr = io.IOStream(sys.stderr)
544 io.stderr = io.IOStream(sys.stderr)
546
545
547 def init_magics(self):
546 def init_magics(self):
548 super(TerminalInteractiveShell, self).init_magics()
547 super(TerminalInteractiveShell, self).init_magics()
549 self.register_magics(TerminalMagics)
548 self.register_magics(TerminalMagics)
550
549
551 def init_alias(self):
550 def init_alias(self):
552 # The parent class defines aliases that can be safely used with any
551 # The parent class defines aliases that can be safely used with any
553 # frontend.
552 # frontend.
554 super(TerminalInteractiveShell, self).init_alias()
553 super(TerminalInteractiveShell, self).init_alias()
555
554
556 # Now define aliases that only make sense on the terminal, because they
555 # Now define aliases that only make sense on the terminal, because they
557 # need direct access to the console in a way that we can't emulate in
556 # need direct access to the console in a way that we can't emulate in
558 # GUI or web frontend
557 # GUI or web frontend
559 if os.name == 'posix':
558 if os.name == 'posix':
560 for cmd in ('clear', 'more', 'less', 'man'):
559 for cmd in ('clear', 'more', 'less', 'man'):
561 self.alias_manager.soft_define_alias(cmd, cmd)
560 self.alias_manager.soft_define_alias(cmd, cmd)
562
561
563
562
564 def __init__(self, *args, **kwargs):
563 def __init__(self, *args, **kwargs):
565 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
564 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
566 self.init_prompt_toolkit_cli()
565 self.init_prompt_toolkit_cli()
567 self.init_term_title()
566 self.init_term_title()
568 self.keep_running = True
567 self.keep_running = True
569
568
570 self.debugger_history = InMemoryHistory()
569 self.debugger_history = InMemoryHistory()
571
570
572 def ask_exit(self):
571 def ask_exit(self):
573 self.keep_running = False
572 self.keep_running = False
574
573
575 rl_next_input = None
574 rl_next_input = None
576
575
577 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
576 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
578
577
579 if display_banner is not DISPLAY_BANNER_DEPRECATED:
578 if display_banner is not DISPLAY_BANNER_DEPRECATED:
580 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
579 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
581
580
582 self.keep_running = True
581 self.keep_running = True
583 while self.keep_running:
582 while self.keep_running:
584 print(self.separate_in, end='')
583 print(self.separate_in, end='')
585
584
586 try:
585 try:
587 code = self.prompt_for_code()
586 code = self.prompt_for_code()
588 except EOFError:
587 except EOFError:
589 if (not self.confirm_exit) \
588 if (not self.confirm_exit) \
590 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
589 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
591 self.ask_exit()
590 self.ask_exit()
592
591
593 else:
592 else:
594 if code:
593 if code:
595 self.run_cell(code, store_history=True)
594 self.run_cell(code, store_history=True)
596
595
597 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
596 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
598 # An extra layer of protection in case someone mashing Ctrl-C breaks
597 # An extra layer of protection in case someone mashing Ctrl-C breaks
599 # out of our internal code.
598 # out of our internal code.
600 if display_banner is not DISPLAY_BANNER_DEPRECATED:
599 if display_banner is not DISPLAY_BANNER_DEPRECATED:
601 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
600 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
602 while True:
601 while True:
603 try:
602 try:
604 self.interact()
603 self.interact()
605 break
604 break
606 except KeyboardInterrupt as e:
605 except KeyboardInterrupt as e:
607 print("\n%s escaped interact()\n" % type(e).__name__)
606 print("\n%s escaped interact()\n" % type(e).__name__)
608 finally:
607 finally:
609 # An interrupt during the eventloop will mess up the
608 # An interrupt during the eventloop will mess up the
610 # internal state of the prompt_toolkit library.
609 # internal state of the prompt_toolkit library.
611 # Stopping the eventloop fixes this, see
610 # Stopping the eventloop fixes this, see
612 # https://github.com/ipython/ipython/pull/9867
611 # https://github.com/ipython/ipython/pull/9867
613 if hasattr(self, '_eventloop'):
612 if hasattr(self, '_eventloop'):
614 self._eventloop.stop()
613 self._eventloop.stop()
615
614
616 self.restore_term_title()
615 self.restore_term_title()
617
616
618 # try to call some at-exit operation optimistically as some things can't
617 # try to call some at-exit operation optimistically as some things can't
619 # be done during interpreter shutdown. this is technically inaccurate as
618 # be done during interpreter shutdown. this is technically inaccurate as
620 # this make mainlool not re-callable, but that should be a rare if not
619 # this make mainlool not re-callable, but that should be a rare if not
621 # in existent use case.
620 # in existent use case.
622
621
623 self._atexit_once()
622 self._atexit_once()
624
623
625
624
626 _inputhook = None
625 _inputhook = None
627 def inputhook(self, context):
626 def inputhook(self, context):
628 if self._inputhook is not None:
627 if self._inputhook is not None:
629 self._inputhook(context)
628 self._inputhook(context)
630
629
631 active_eventloop = None
630 active_eventloop = None
632 def enable_gui(self, gui=None):
631 def enable_gui(self, gui=None):
633 if gui and (gui != 'inline') :
632 if gui and (gui != 'inline') :
634 self.active_eventloop, self._inputhook =\
633 self.active_eventloop, self._inputhook =\
635 get_inputhook_name_and_func(gui)
634 get_inputhook_name_and_func(gui)
636 else:
635 else:
637 self.active_eventloop = self._inputhook = None
636 self.active_eventloop = self._inputhook = None
638
637
639 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
638 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
640 # this inputhook.
639 # this inputhook.
641 if PTK3:
640 if PTK3:
642 import asyncio
641 import asyncio
643 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
642 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
644
643
645 if gui == 'asyncio':
644 if gui == 'asyncio':
646 # When we integrate the asyncio event loop, run the UI in the
645 # When we integrate the asyncio event loop, run the UI in the
647 # same event loop as the rest of the code. don't use an actual
646 # same event loop as the rest of the code. don't use an actual
648 # input hook. (Asyncio is not made for nesting event loops.)
647 # input hook. (Asyncio is not made for nesting event loops.)
649 self.pt_loop = asyncio.get_event_loop()
648 self.pt_loop = asyncio.get_event_loop()
650
649
651 elif self._inputhook:
650 elif self._inputhook:
652 # If an inputhook was set, create a new asyncio event loop with
651 # If an inputhook was set, create a new asyncio event loop with
653 # this inputhook for the prompt.
652 # this inputhook for the prompt.
654 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
653 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
655 else:
654 else:
656 # When there's no inputhook, run the prompt in a separate
655 # When there's no inputhook, run the prompt in a separate
657 # asyncio event loop.
656 # asyncio event loop.
658 self.pt_loop = asyncio.new_event_loop()
657 self.pt_loop = asyncio.new_event_loop()
659
658
660 # Run !system commands directly, not through pipes, so terminal programs
659 # Run !system commands directly, not through pipes, so terminal programs
661 # work correctly.
660 # work correctly.
662 system = InteractiveShell.system_raw
661 system = InteractiveShell.system_raw
663
662
664 def auto_rewrite_input(self, cmd):
663 def auto_rewrite_input(self, cmd):
665 """Overridden from the parent class to use fancy rewriting prompt"""
664 """Overridden from the parent class to use fancy rewriting prompt"""
666 if not self.show_rewritten_input:
665 if not self.show_rewritten_input:
667 return
666 return
668
667
669 tokens = self.prompts.rewrite_prompt_tokens()
668 tokens = self.prompts.rewrite_prompt_tokens()
670 if self.pt_app:
669 if self.pt_app:
671 print_formatted_text(PygmentsTokens(tokens), end='',
670 print_formatted_text(PygmentsTokens(tokens), end='',
672 style=self.pt_app.app.style)
671 style=self.pt_app.app.style)
673 print(cmd)
672 print(cmd)
674 else:
673 else:
675 prompt = ''.join(s for t, s in tokens)
674 prompt = ''.join(s for t, s in tokens)
676 print(prompt, cmd, sep='')
675 print(prompt, cmd, sep='')
677
676
678 _prompts_before = None
677 _prompts_before = None
679 def switch_doctest_mode(self, mode):
678 def switch_doctest_mode(self, mode):
680 """Switch prompts to classic for %doctest_mode"""
679 """Switch prompts to classic for %doctest_mode"""
681 if mode:
680 if mode:
682 self._prompts_before = self.prompts
681 self._prompts_before = self.prompts
683 self.prompts = ClassicPrompts(self)
682 self.prompts = ClassicPrompts(self)
684 elif self._prompts_before:
683 elif self._prompts_before:
685 self.prompts = self._prompts_before
684 self.prompts = self._prompts_before
686 self._prompts_before = None
685 self._prompts_before = None
687 # self._update_layout()
686 # self._update_layout()
688
687
689
688
690 InteractiveShellABC.register(TerminalInteractiveShell)
689 InteractiveShellABC.register(TerminalInteractiveShell)
691
690
692 if __name__ == '__main__':
691 if __name__ == '__main__':
693 TerminalInteractiveShell.instance().interact()
692 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now