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