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