##// END OF EJS Templates
attempt fix
Matthias Bussonnier -
Show More
@@ -1,987 +1,998
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(
592 "",
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."
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} | '"
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."
599 " Note that when using Emacs mode the prompt of the first line may not update.",
600 ).tag(config=True)
601
591 @observe('term_title')
602 @observe('term_title')
592 def init_term_title(self, change=None):
603 def init_term_title(self, change=None):
593 # Enable or disable the terminal title.
604 # Enable or disable the terminal title.
594 if self.term_title and _is_tty:
605 if self.term_title and _is_tty:
595 toggle_set_term_title(True)
606 toggle_set_term_title(True)
596 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
607 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
597 else:
608 else:
598 toggle_set_term_title(False)
609 toggle_set_term_title(False)
599
610
600 def restore_term_title(self):
611 def restore_term_title(self):
601 if self.term_title and _is_tty:
612 if self.term_title and _is_tty:
602 restore_term_title()
613 restore_term_title()
603
614
604 def init_display_formatter(self):
615 def init_display_formatter(self):
605 super(TerminalInteractiveShell, self).init_display_formatter()
616 super(TerminalInteractiveShell, self).init_display_formatter()
606 # terminal only supports plain text
617 # terminal only supports plain text
607 self.display_formatter.active_types = ["text/plain"]
618 self.display_formatter.active_types = ["text/plain"]
608
619
609 def init_prompt_toolkit_cli(self):
620 def init_prompt_toolkit_cli(self):
610 if self.simple_prompt:
621 if self.simple_prompt:
611 # Fall back to plain non-interactive output for tests.
622 # Fall back to plain non-interactive output for tests.
612 # This is very limited.
623 # This is very limited.
613 def prompt():
624 def prompt():
614 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())
615 lines = [input(prompt_text)]
626 lines = [input(prompt_text)]
616 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())
617 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
628 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
618 lines.append( input(prompt_continuation) )
629 lines.append( input(prompt_continuation) )
619 return '\n'.join(lines)
630 return '\n'.join(lines)
620 self.prompt_for_code = prompt
631 self.prompt_for_code = prompt
621 return
632 return
622
633
623 # Set up keyboard shortcuts
634 # Set up keyboard shortcuts
624 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
635 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
625
636
626 # Pre-populate history from IPython's history database
637 # Pre-populate history from IPython's history database
627 history = PtkHistoryAdapter(self)
638 history = PtkHistoryAdapter(self)
628
639
629 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)
630 self.style = DynamicStyle(lambda: self._style)
641 self.style = DynamicStyle(lambda: self._style)
631
642
632 editing_mode = getattr(EditingMode, self.editing_mode.upper())
643 editing_mode = getattr(EditingMode, self.editing_mode.upper())
633
644
634 self._use_asyncio_inputhook = False
645 self._use_asyncio_inputhook = False
635 self.pt_app = PromptSession(
646 self.pt_app = PromptSession(
636 auto_suggest=self.auto_suggest,
647 auto_suggest=self.auto_suggest,
637 editing_mode=editing_mode,
648 editing_mode=editing_mode,
638 key_bindings=key_bindings,
649 key_bindings=key_bindings,
639 history=history,
650 history=history,
640 completer=IPythonPTCompleter(shell=self),
651 completer=IPythonPTCompleter(shell=self),
641 enable_history_search=self.enable_history_search,
652 enable_history_search=self.enable_history_search,
642 style=self.style,
653 style=self.style,
643 include_default_pygments_style=False,
654 include_default_pygments_style=False,
644 mouse_support=self.mouse_support,
655 mouse_support=self.mouse_support,
645 enable_open_in_editor=self.extra_open_editor_shortcuts,
656 enable_open_in_editor=self.extra_open_editor_shortcuts,
646 color_depth=self.color_depth,
657 color_depth=self.color_depth,
647 tempfile_suffix=".py",
658 tempfile_suffix=".py",
648 **self._extra_prompt_options(),
659 **self._extra_prompt_options(),
649 )
660 )
650 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
661 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
651 self.auto_suggest.connect(self.pt_app)
662 self.auto_suggest.connect(self.pt_app)
652
663
653 def _make_style_from_name_or_cls(self, name_or_cls):
664 def _make_style_from_name_or_cls(self, name_or_cls):
654 """
665 """
655 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
656
667
657 We need that to add style for prompt ... etc.
668 We need that to add style for prompt ... etc.
658 """
669 """
659 style_overrides = {}
670 style_overrides = {}
660 if name_or_cls == 'legacy':
671 if name_or_cls == 'legacy':
661 legacy = self.colors.lower()
672 legacy = self.colors.lower()
662 if legacy == 'linux':
673 if legacy == 'linux':
663 style_cls = get_style_by_name('monokai')
674 style_cls = get_style_by_name('monokai')
664 style_overrides = _style_overrides_linux
675 style_overrides = _style_overrides_linux
665 elif legacy == 'lightbg':
676 elif legacy == 'lightbg':
666 style_overrides = _style_overrides_light_bg
677 style_overrides = _style_overrides_light_bg
667 style_cls = get_style_by_name('pastie')
678 style_cls = get_style_by_name('pastie')
668 elif legacy == 'neutral':
679 elif legacy == 'neutral':
669 # 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
670 # 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
671 # looks like. These tweaks to the default theme help with that.
682 # looks like. These tweaks to the default theme help with that.
672 style_cls = get_style_by_name('default')
683 style_cls = get_style_by_name('default')
673 style_overrides.update({
684 style_overrides.update({
674 Token.Number: '#ansigreen',
685 Token.Number: '#ansigreen',
675 Token.Operator: 'noinherit',
686 Token.Operator: 'noinherit',
676 Token.String: '#ansiyellow',
687 Token.String: '#ansiyellow',
677 Token.Name.Function: '#ansiblue',
688 Token.Name.Function: '#ansiblue',
678 Token.Name.Class: 'bold #ansiblue',
689 Token.Name.Class: 'bold #ansiblue',
679 Token.Name.Namespace: 'bold #ansiblue',
690 Token.Name.Namespace: 'bold #ansiblue',
680 Token.Name.Variable.Magic: '#ansiblue',
691 Token.Name.Variable.Magic: '#ansiblue',
681 Token.Prompt: '#ansigreen',
692 Token.Prompt: '#ansigreen',
682 Token.PromptNum: '#ansibrightgreen bold',
693 Token.PromptNum: '#ansibrightgreen bold',
683 Token.OutPrompt: '#ansired',
694 Token.OutPrompt: '#ansired',
684 Token.OutPromptNum: '#ansibrightred bold',
695 Token.OutPromptNum: '#ansibrightred bold',
685 })
696 })
686
697
687 # Hack: Due to limited color support on the Windows console
698 # Hack: Due to limited color support on the Windows console
688 # the prompt colors will be wrong without this
699 # the prompt colors will be wrong without this
689 if os.name == 'nt':
700 if os.name == 'nt':
690 style_overrides.update({
701 style_overrides.update({
691 Token.Prompt: '#ansidarkgreen',
702 Token.Prompt: '#ansidarkgreen',
692 Token.PromptNum: '#ansigreen bold',
703 Token.PromptNum: '#ansigreen bold',
693 Token.OutPrompt: '#ansidarkred',
704 Token.OutPrompt: '#ansidarkred',
694 Token.OutPromptNum: '#ansired bold',
705 Token.OutPromptNum: '#ansired bold',
695 })
706 })
696 elif legacy =='nocolor':
707 elif legacy =='nocolor':
697 style_cls=_NoStyle
708 style_cls=_NoStyle
698 style_overrides = {}
709 style_overrides = {}
699 else :
710 else :
700 raise ValueError('Got unknown colors: ', legacy)
711 raise ValueError('Got unknown colors: ', legacy)
701 else :
712 else :
702 if isinstance(name_or_cls, str):
713 if isinstance(name_or_cls, str):
703 style_cls = get_style_by_name(name_or_cls)
714 style_cls = get_style_by_name(name_or_cls)
704 else:
715 else:
705 style_cls = name_or_cls
716 style_cls = name_or_cls
706 style_overrides = {
717 style_overrides = {
707 Token.Prompt: '#ansigreen',
718 Token.Prompt: '#ansigreen',
708 Token.PromptNum: '#ansibrightgreen bold',
719 Token.PromptNum: '#ansibrightgreen bold',
709 Token.OutPrompt: '#ansired',
720 Token.OutPrompt: '#ansired',
710 Token.OutPromptNum: '#ansibrightred bold',
721 Token.OutPromptNum: '#ansibrightred bold',
711 }
722 }
712 style_overrides.update(self.highlighting_style_overrides)
723 style_overrides.update(self.highlighting_style_overrides)
713 style = merge_styles([
724 style = merge_styles([
714 style_from_pygments_cls(style_cls),
725 style_from_pygments_cls(style_cls),
715 style_from_pygments_dict(style_overrides),
726 style_from_pygments_dict(style_overrides),
716 ])
727 ])
717
728
718 return style
729 return style
719
730
720 @property
731 @property
721 def pt_complete_style(self):
732 def pt_complete_style(self):
722 return {
733 return {
723 'multicolumn': CompleteStyle.MULTI_COLUMN,
734 'multicolumn': CompleteStyle.MULTI_COLUMN,
724 'column': CompleteStyle.COLUMN,
735 'column': CompleteStyle.COLUMN,
725 'readlinelike': CompleteStyle.READLINE_LIKE,
736 'readlinelike': CompleteStyle.READLINE_LIKE,
726 }[self.display_completions]
737 }[self.display_completions]
727
738
728 @property
739 @property
729 def color_depth(self):
740 def color_depth(self):
730 return (ColorDepth.TRUE_COLOR if self.true_color else None)
741 return (ColorDepth.TRUE_COLOR if self.true_color else None)
731
742
732 def _extra_prompt_options(self):
743 def _extra_prompt_options(self):
733 """
744 """
734 Return the current layout option for the current Terminal InteractiveShell
745 Return the current layout option for the current Terminal InteractiveShell
735 """
746 """
736 def get_message():
747 def get_message():
737 return PygmentsTokens(self.prompts.in_prompt_tokens())
748 return PygmentsTokens(self.prompts.in_prompt_tokens())
738
749
739 if self.editing_mode == 'emacs':
750 if self.editing_mode == 'emacs':
740 # 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
741 # 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
742 # [nor] so we can't precompute.
753 # [nor] so we can't precompute.
743 # here I'm going to favor the default keybinding which almost
754 # here I'm going to favor the default keybinding which almost
744 # everybody uses to decrease CPU usage.
755 # everybody uses to decrease CPU usage.
745 # 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
746 # work around this.
757 # work around this.
747 get_message = get_message()
758 get_message = get_message()
748
759
749 options = {
760 options = {
750 "complete_in_thread": False,
761 "complete_in_thread": False,
751 "lexer": IPythonPTLexer(),
762 "lexer": IPythonPTLexer(),
752 "reserve_space_for_menu": self.space_for_menu,
763 "reserve_space_for_menu": self.space_for_menu,
753 "message": get_message,
764 "message": get_message,
754 "prompt_continuation": (
765 "prompt_continuation": (
755 lambda width, lineno, is_soft_wrap: PygmentsTokens(
766 lambda width, lineno, is_soft_wrap: PygmentsTokens(
756 self.prompts.continuation_prompt_tokens(width, lineno=lineno)
767 self.prompts.continuation_prompt_tokens(width, lineno=lineno)
757 )
768 )
758 ),
769 ),
759 "multiline": True,
770 "multiline": True,
760 "complete_style": self.pt_complete_style,
771 "complete_style": self.pt_complete_style,
761 "input_processors": [
772 "input_processors": [
762 # Highlight matching brackets, but only when this setting is
773 # Highlight matching brackets, but only when this setting is
763 # enabled, and only when the DEFAULT_BUFFER has the focus.
774 # enabled, and only when the DEFAULT_BUFFER has the focus.
764 ConditionalProcessor(
775 ConditionalProcessor(
765 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
776 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
766 filter=HasFocus(DEFAULT_BUFFER)
777 filter=HasFocus(DEFAULT_BUFFER)
767 & ~IsDone()
778 & ~IsDone()
768 & Condition(lambda: self.highlight_matching_brackets),
779 & Condition(lambda: self.highlight_matching_brackets),
769 ),
780 ),
770 # Show auto-suggestion in lines other than the last line.
781 # Show auto-suggestion in lines other than the last line.
771 ConditionalProcessor(
782 ConditionalProcessor(
772 processor=AppendAutoSuggestionInAnyLine(),
783 processor=AppendAutoSuggestionInAnyLine(),
773 filter=HasFocus(DEFAULT_BUFFER)
784 filter=HasFocus(DEFAULT_BUFFER)
774 & ~IsDone()
785 & ~IsDone()
775 & Condition(
786 & Condition(
776 lambda: isinstance(
787 lambda: isinstance(
777 self.auto_suggest, NavigableAutoSuggestFromHistory
788 self.auto_suggest, NavigableAutoSuggestFromHistory
778 )
789 )
779 ),
790 ),
780 ),
791 ),
781 ],
792 ],
782 }
793 }
783 if not PTK3:
794 if not PTK3:
784 options['inputhook'] = self.inputhook
795 options['inputhook'] = self.inputhook
785
796
786 return options
797 return options
787
798
788 def prompt_for_code(self):
799 def prompt_for_code(self):
789 if self.rl_next_input:
800 if self.rl_next_input:
790 default = self.rl_next_input
801 default = self.rl_next_input
791 self.rl_next_input = None
802 self.rl_next_input = None
792 else:
803 else:
793 default = ''
804 default = ''
794
805
795 # In order to make sure that asyncio code written in the
806 # In order to make sure that asyncio code written in the
796 # interactive shell doesn't interfere with the prompt, we run the
807 # interactive shell doesn't interfere with the prompt, we run the
797 # prompt in a different event loop.
808 # prompt in a different event loop.
798 # 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
799 # while/true inside which will freeze the prompt.
810 # while/true inside which will freeze the prompt.
800
811
801 with patch_stdout(raw=True):
812 with patch_stdout(raw=True):
802 if self._use_asyncio_inputhook:
813 if self._use_asyncio_inputhook:
803 # 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
804 # 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
805 # input hook. (Asyncio is not made for nesting event loops.)
816 # input hook. (Asyncio is not made for nesting event loops.)
806 asyncio_loop = get_asyncio_loop()
817 asyncio_loop = get_asyncio_loop()
807 text = asyncio_loop.run_until_complete(
818 text = asyncio_loop.run_until_complete(
808 self.pt_app.prompt_async(
819 self.pt_app.prompt_async(
809 default=default, **self._extra_prompt_options()
820 default=default, **self._extra_prompt_options()
810 )
821 )
811 )
822 )
812 else:
823 else:
813 text = self.pt_app.prompt(
824 text = self.pt_app.prompt(
814 default=default,
825 default=default,
815 inputhook=self._inputhook,
826 inputhook=self._inputhook,
816 **self._extra_prompt_options(),
827 **self._extra_prompt_options(),
817 )
828 )
818
829
819 return text
830 return text
820
831
821 def enable_win_unicode_console(self):
832 def enable_win_unicode_console(self):
822 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
833 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
823 # console by default, so WUC shouldn't be needed.
834 # console by default, so WUC shouldn't be needed.
824 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
835 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
825 DeprecationWarning,
836 DeprecationWarning,
826 stacklevel=2)
837 stacklevel=2)
827
838
828 def init_io(self):
839 def init_io(self):
829 if sys.platform not in {'win32', 'cli'}:
840 if sys.platform not in {'win32', 'cli'}:
830 return
841 return
831
842
832 import colorama
843 import colorama
833 colorama.init()
844 colorama.init()
834
845
835 def init_magics(self):
846 def init_magics(self):
836 super(TerminalInteractiveShell, self).init_magics()
847 super(TerminalInteractiveShell, self).init_magics()
837 self.register_magics(TerminalMagics)
848 self.register_magics(TerminalMagics)
838
849
839 def init_alias(self):
850 def init_alias(self):
840 # 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
841 # frontend.
852 # frontend.
842 super(TerminalInteractiveShell, self).init_alias()
853 super(TerminalInteractiveShell, self).init_alias()
843
854
844 # 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
845 # 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
846 # GUI or web frontend
857 # GUI or web frontend
847 if os.name == 'posix':
858 if os.name == 'posix':
848 for cmd in ('clear', 'more', 'less', 'man'):
859 for cmd in ('clear', 'more', 'less', 'man'):
849 self.alias_manager.soft_define_alias(cmd, cmd)
860 self.alias_manager.soft_define_alias(cmd, cmd)
850
861
851
862
852 def __init__(self, *args, **kwargs) -> None:
863 def __init__(self, *args, **kwargs) -> None:
853 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
864 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
854 self._set_autosuggestions(self.autosuggestions_provider)
865 self._set_autosuggestions(self.autosuggestions_provider)
855 self.init_prompt_toolkit_cli()
866 self.init_prompt_toolkit_cli()
856 self.init_term_title()
867 self.init_term_title()
857 self.keep_running = True
868 self.keep_running = True
858 self._set_formatter(self.autoformatter)
869 self._set_formatter(self.autoformatter)
859
870
860
871
861 def ask_exit(self):
872 def ask_exit(self):
862 self.keep_running = False
873 self.keep_running = False
863
874
864 rl_next_input = None
875 rl_next_input = None
865
876
866 def interact(self):
877 def interact(self):
867 self.keep_running = True
878 self.keep_running = True
868 while self.keep_running:
879 while self.keep_running:
869 print(self.separate_in, end='')
880 print(self.separate_in, end='')
870
881
871 try:
882 try:
872 code = self.prompt_for_code()
883 code = self.prompt_for_code()
873 except EOFError:
884 except EOFError:
874 if (not self.confirm_exit) \
885 if (not self.confirm_exit) \
875 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'):
876 self.ask_exit()
887 self.ask_exit()
877
888
878 else:
889 else:
879 if code:
890 if code:
880 self.run_cell(code, store_history=True)
891 self.run_cell(code, store_history=True)
881
892
882 def mainloop(self):
893 def mainloop(self):
883 # 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
884 # out of our internal code.
895 # out of our internal code.
885 while True:
896 while True:
886 try:
897 try:
887 self.interact()
898 self.interact()
888 break
899 break
889 except KeyboardInterrupt as e:
900 except KeyboardInterrupt as e:
890 print("\n%s escaped interact()\n" % type(e).__name__)
901 print("\n%s escaped interact()\n" % type(e).__name__)
891 finally:
902 finally:
892 # An interrupt during the eventloop will mess up the
903 # An interrupt during the eventloop will mess up the
893 # internal state of the prompt_toolkit library.
904 # internal state of the prompt_toolkit library.
894 # Stopping the eventloop fixes this, see
905 # Stopping the eventloop fixes this, see
895 # https://github.com/ipython/ipython/pull/9867
906 # https://github.com/ipython/ipython/pull/9867
896 if hasattr(self, '_eventloop'):
907 if hasattr(self, '_eventloop'):
897 self._eventloop.stop()
908 self._eventloop.stop()
898
909
899 self.restore_term_title()
910 self.restore_term_title()
900
911
901 # 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
902 # be done during interpreter shutdown. this is technically inaccurate as
913 # be done during interpreter shutdown. this is technically inaccurate as
903 # this make mainlool not re-callable, but that should be a rare if not
914 # this make mainlool not re-callable, but that should be a rare if not
904 # in existent use case.
915 # in existent use case.
905
916
906 self._atexit_once()
917 self._atexit_once()
907
918
908
919
909 _inputhook = None
920 _inputhook = None
910 def inputhook(self, context):
921 def inputhook(self, context):
911 if self._inputhook is not None:
922 if self._inputhook is not None:
912 self._inputhook(context)
923 self._inputhook(context)
913
924
914 active_eventloop: Optional[str] = None
925 active_eventloop: Optional[str] = None
915
926
916 def enable_gui(self, gui: Optional[str] = None) -> None:
927 def enable_gui(self, gui: Optional[str] = None) -> None:
917 if self.simple_prompt is True and gui is not None:
928 if self.simple_prompt is True and gui is not None:
918 print(
929 print(
919 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`.'
920 )
931 )
921 print(
932 print(
922 "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`."
923 )
934 )
924 return
935 return
925
936
926 if self._inputhook is None and gui is None:
937 if self._inputhook is None and gui is None:
927 print("No event loop hook running.")
938 print("No event loop hook running.")
928 return
939 return
929
940
930 if self._inputhook is not None and gui is not None:
941 if self._inputhook is not None and gui is not None:
931 newev, newinhook = get_inputhook_name_and_func(gui)
942 newev, newinhook = get_inputhook_name_and_func(gui)
932 if self._inputhook == newinhook:
943 if self._inputhook == newinhook:
933 # same inputhook, do nothing
944 # same inputhook, do nothing
934 self.log.info(
945 self.log.info(
935 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"
936 )
947 )
937 return
948 return
938 self.log.warning(
949 self.log.warning(
939 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}. "
940 "Call with no arguments to disable the current loop."
951 "Call with no arguments to disable the current loop."
941 )
952 )
942 return
953 return
943 if self._inputhook is not None and gui is None:
954 if self._inputhook is not None and gui is None:
944 self.active_eventloop = self._inputhook = None
955 self.active_eventloop = self._inputhook = None
945
956
946 if gui and (gui not in {"inline", "webagg"}):
957 if gui and (gui not in {"inline", "webagg"}):
947 # 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.
948 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
959 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
949 else:
960 else:
950 self.active_eventloop = self._inputhook = None
961 self.active_eventloop = self._inputhook = None
951
962
952 self._use_asyncio_inputhook = gui == "asyncio"
963 self._use_asyncio_inputhook = gui == "asyncio"
953
964
954 # Run !system commands directly, not through pipes, so terminal programs
965 # Run !system commands directly, not through pipes, so terminal programs
955 # work correctly.
966 # work correctly.
956 system = InteractiveShell.system_raw
967 system = InteractiveShell.system_raw
957
968
958 def auto_rewrite_input(self, cmd):
969 def auto_rewrite_input(self, cmd):
959 """Overridden from the parent class to use fancy rewriting prompt"""
970 """Overridden from the parent class to use fancy rewriting prompt"""
960 if not self.show_rewritten_input:
971 if not self.show_rewritten_input:
961 return
972 return
962
973
963 tokens = self.prompts.rewrite_prompt_tokens()
974 tokens = self.prompts.rewrite_prompt_tokens()
964 if self.pt_app:
975 if self.pt_app:
965 print_formatted_text(PygmentsTokens(tokens), end='',
976 print_formatted_text(PygmentsTokens(tokens), end='',
966 style=self.pt_app.app.style)
977 style=self.pt_app.app.style)
967 print(cmd)
978 print(cmd)
968 else:
979 else:
969 prompt = ''.join(s for t, s in tokens)
980 prompt = ''.join(s for t, s in tokens)
970 print(prompt, cmd, sep='')
981 print(prompt, cmd, sep='')
971
982
972 _prompts_before = None
983 _prompts_before = None
973 def switch_doctest_mode(self, mode):
984 def switch_doctest_mode(self, mode):
974 """Switch prompts to classic for %doctest_mode"""
985 """Switch prompts to classic for %doctest_mode"""
975 if mode:
986 if mode:
976 self._prompts_before = self.prompts
987 self._prompts_before = self.prompts
977 self.prompts = ClassicPrompts(self)
988 self.prompts = ClassicPrompts(self)
978 elif self._prompts_before:
989 elif self._prompts_before:
979 self.prompts = self._prompts_before
990 self.prompts = self._prompts_before
980 self._prompts_before = None
991 self._prompts_before = None
981 # self._update_layout()
992 # self._update_layout()
982
993
983
994
984 InteractiveShellABC.register(TerminalInteractiveShell)
995 InteractiveShellABC.register(TerminalInteractiveShell)
985
996
986 if __name__ == '__main__':
997 if __name__ == '__main__':
987 TerminalInteractiveShell.instance().interact()
998 TerminalInteractiveShell.instance().interact()
@@ -1,113 +1,127
1 """Terminal input and output prompts."""
1 """Terminal input and output prompts."""
2
2
3 from pygments.token import Token
3 from pygments.token import Token
4 import sys
4 import sys
5
5
6 from IPython.core.displayhook import DisplayHook
6 from IPython.core.displayhook import DisplayHook
7
7
8 from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens
8 from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens
9 from prompt_toolkit.shortcuts import print_formatted_text
9 from prompt_toolkit.shortcuts import print_formatted_text
10 from prompt_toolkit.enums import EditingMode
10 from prompt_toolkit.enums import EditingMode
11
11
12
12
13 class Prompts(object):
13 class Prompts(object):
14 def __init__(self, shell):
14 def __init__(self, shell):
15 self.shell = shell
15 self.shell = shell
16
16
17 def vi_mode(self):
17 def vi_mode(self):
18 if (getattr(self.shell.pt_app, 'editing_mode', None) == EditingMode.VI
18 if (getattr(self.shell.pt_app, 'editing_mode', None) == EditingMode.VI
19 and self.shell.prompt_includes_vi_mode):
19 and self.shell.prompt_includes_vi_mode):
20 mode = str(self.shell.pt_app.app.vi_state.input_mode)
20 mode = str(self.shell.pt_app.app.vi_state.input_mode)
21 if mode.startswith('InputMode.'):
21 if mode.startswith('InputMode.'):
22 mode = mode[10:13].lower()
22 mode = mode[10:13].lower()
23 elif mode.startswith('vi-'):
23 elif mode.startswith('vi-'):
24 mode = mode[3:6]
24 mode = mode[3:6]
25 return '['+mode+'] '
25 return '['+mode+'] '
26 return ''
26 return ''
27
27
28 def current_line(self) -> int:
29 if self.shell.pt_app is not None:
30 return self.shell.pt_app.default_buffer.document.cursor_position_row or 0
31 return 0
28
32
29 def in_prompt_tokens(self):
33 def in_prompt_tokens(self):
30 return [
34 return [
31 (Token.Prompt, self.vi_mode()),
35 (Token.Prompt, self.vi_mode()),
32 (Token.Prompt, "1 | "),
36 (
37 Token.Prompt,
38 self.shell.prompt_line_number_format.format(
39 line=1, rel_line=-self.current_line()
40 ),
41 ),
33 (Token.Prompt, "In ["),
42 (Token.Prompt, "In ["),
34 (Token.PromptNum, str(self.shell.execution_count)),
43 (Token.PromptNum, str(self.shell.execution_count)),
35 (Token.Prompt, ']: '),
44 (Token.Prompt, ']: '),
36 ]
45 ]
37
46
38 def _width(self):
47 def _width(self):
39 return fragment_list_width(self.in_prompt_tokens())
48 return fragment_list_width(self.in_prompt_tokens())
40
49
41 def continuation_prompt_tokens(self, width=None, *, lineno=None):
50 def continuation_prompt_tokens(self, width=None, *, lineno=None):
42 if width is None:
51 if width is None:
43 width = self._width()
52 width = self._width()
44 prefix = " " * len(self.vi_mode()) + str(lineno + 1) + " | "
53 line = lineno + 1 if lineno is not None else 0
54 prefix = " " * len(
55 self.vi_mode()
56 ) + self.shell.prompt_line_number_format.format(
57 line=line, rel_line=line - self.current_line() - 1
58 )
45 return [
59 return [
46 (
60 (
47 Token.Prompt,
61 Token.Prompt,
48 prefix + (" " * (width - len(prefix) - 5)) + "...: ",
62 prefix + (" " * (width - len(prefix) - 5)) + "...: ",
49 ),
63 ),
50 ]
64 ]
51
65
52 def rewrite_prompt_tokens(self):
66 def rewrite_prompt_tokens(self):
53 width = self._width()
67 width = self._width()
54 return [
68 return [
55 (Token.Prompt, ('-' * (width - 2)) + '> '),
69 (Token.Prompt, ('-' * (width - 2)) + '> '),
56 ]
70 ]
57
71
58 def out_prompt_tokens(self):
72 def out_prompt_tokens(self):
59 return [
73 return [
60 (Token.OutPrompt, 'Out['),
74 (Token.OutPrompt, 'Out['),
61 (Token.OutPromptNum, str(self.shell.execution_count)),
75 (Token.OutPromptNum, str(self.shell.execution_count)),
62 (Token.OutPrompt, ']: '),
76 (Token.OutPrompt, ']: '),
63 ]
77 ]
64
78
65 class ClassicPrompts(Prompts):
79 class ClassicPrompts(Prompts):
66 def in_prompt_tokens(self):
80 def in_prompt_tokens(self):
67 return [
81 return [
68 (Token.Prompt, '>>> '),
82 (Token.Prompt, '>>> '),
69 ]
83 ]
70
84
71 def continuation_prompt_tokens(self, width=None):
85 def continuation_prompt_tokens(self, width=None):
72 return [
86 return [
73 (Token.Prompt, '... ')
87 (Token.Prompt, '... ')
74 ]
88 ]
75
89
76 def rewrite_prompt_tokens(self):
90 def rewrite_prompt_tokens(self):
77 return []
91 return []
78
92
79 def out_prompt_tokens(self):
93 def out_prompt_tokens(self):
80 return []
94 return []
81
95
82 class RichPromptDisplayHook(DisplayHook):
96 class RichPromptDisplayHook(DisplayHook):
83 """Subclass of base display hook using coloured prompt"""
97 """Subclass of base display hook using coloured prompt"""
84 def write_output_prompt(self):
98 def write_output_prompt(self):
85 sys.stdout.write(self.shell.separate_out)
99 sys.stdout.write(self.shell.separate_out)
86 # If we're not displaying a prompt, it effectively ends with a newline,
100 # If we're not displaying a prompt, it effectively ends with a newline,
87 # because the output will be left-aligned.
101 # because the output will be left-aligned.
88 self.prompt_end_newline = True
102 self.prompt_end_newline = True
89
103
90 if self.do_full_cache:
104 if self.do_full_cache:
91 tokens = self.shell.prompts.out_prompt_tokens()
105 tokens = self.shell.prompts.out_prompt_tokens()
92 prompt_txt = ''.join(s for t, s in tokens)
106 prompt_txt = ''.join(s for t, s in tokens)
93 if prompt_txt and not prompt_txt.endswith('\n'):
107 if prompt_txt and not prompt_txt.endswith('\n'):
94 # Ask for a newline before multiline output
108 # Ask for a newline before multiline output
95 self.prompt_end_newline = False
109 self.prompt_end_newline = False
96
110
97 if self.shell.pt_app:
111 if self.shell.pt_app:
98 print_formatted_text(PygmentsTokens(tokens),
112 print_formatted_text(PygmentsTokens(tokens),
99 style=self.shell.pt_app.app.style, end='',
113 style=self.shell.pt_app.app.style, end='',
100 )
114 )
101 else:
115 else:
102 sys.stdout.write(prompt_txt)
116 sys.stdout.write(prompt_txt)
103
117
104 def write_format_data(self, format_dict, md_dict=None) -> None:
118 def write_format_data(self, format_dict, md_dict=None) -> None:
105 if self.shell.mime_renderers:
119 if self.shell.mime_renderers:
106
120
107 for mime, handler in self.shell.mime_renderers.items():
121 for mime, handler in self.shell.mime_renderers.items():
108 if mime in format_dict:
122 if mime in format_dict:
109 handler(format_dict[mime], None)
123 handler(format_dict[mime], None)
110 return
124 return
111
125
112 super().write_format_data(format_dict, md_dict)
126 super().write_format_data(format_dict, md_dict)
113
127
General Comments 0
You need to be logged in to leave comments. Login now