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