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