##// END OF EJS Templates
Make inclusion of vi mode in prompt togglable.
Antony Lee -
Show More
@@ -0,0 +1,5 b''
1 In vi editing mode, whether the prompt includes the current vi mode can now be configured
2 -----------------------------------------------------------------------------------------
3
4 Set the ``TerminalInteractiveShell.prompt_includes_vi_mode`` to a boolean value
5 (default: True) to control this feature.
@@ -1,548 +1,552 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
11 from IPython.utils.terminal import toggle_set_term_title, set_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,
229 help="Display the current vi mode (when using vi editing mode)."
230 ).tag(config=True)
231
228 @observe('term_title')
232 @observe('term_title')
229 def init_term_title(self, change=None):
233 def init_term_title(self, change=None):
230 # Enable or disable the terminal title.
234 # Enable or disable the terminal title.
231 if self.term_title:
235 if self.term_title:
232 toggle_set_term_title(True)
236 toggle_set_term_title(True)
233 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
237 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
234 else:
238 else:
235 toggle_set_term_title(False)
239 toggle_set_term_title(False)
236
240
237 def init_display_formatter(self):
241 def init_display_formatter(self):
238 super(TerminalInteractiveShell, self).init_display_formatter()
242 super(TerminalInteractiveShell, self).init_display_formatter()
239 # terminal only supports plain text
243 # terminal only supports plain text
240 self.display_formatter.active_types = ['text/plain']
244 self.display_formatter.active_types = ['text/plain']
241 # disable `_ipython_display_`
245 # disable `_ipython_display_`
242 self.display_formatter.ipython_display_formatter.enabled = False
246 self.display_formatter.ipython_display_formatter.enabled = False
243
247
244 def init_prompt_toolkit_cli(self):
248 def init_prompt_toolkit_cli(self):
245 if self.simple_prompt:
249 if self.simple_prompt:
246 # Fall back to plain non-interactive output for tests.
250 # Fall back to plain non-interactive output for tests.
247 # This is very limited.
251 # This is very limited.
248 def prompt():
252 def prompt():
249 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
253 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
250 lines = [input(prompt_text)]
254 lines = [input(prompt_text)]
251 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
255 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
252 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
256 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
253 lines.append( input(prompt_continuation) )
257 lines.append( input(prompt_continuation) )
254 return '\n'.join(lines)
258 return '\n'.join(lines)
255 self.prompt_for_code = prompt
259 self.prompt_for_code = prompt
256 return
260 return
257
261
258 # Set up keyboard shortcuts
262 # Set up keyboard shortcuts
259 key_bindings = create_ipython_shortcuts(self)
263 key_bindings = create_ipython_shortcuts(self)
260
264
261 # Pre-populate history from IPython's history database
265 # Pre-populate history from IPython's history database
262 history = InMemoryHistory()
266 history = InMemoryHistory()
263 last_cell = u""
267 last_cell = u""
264 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
268 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
265 include_latest=True):
269 include_latest=True):
266 # Ignore blank lines and consecutive duplicates
270 # Ignore blank lines and consecutive duplicates
267 cell = cell.rstrip()
271 cell = cell.rstrip()
268 if cell and (cell != last_cell):
272 if cell and (cell != last_cell):
269 history.append_string(cell)
273 history.append_string(cell)
270 last_cell = cell
274 last_cell = cell
271
275
272 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
276 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
273 self.style = DynamicStyle(lambda: self._style)
277 self.style = DynamicStyle(lambda: self._style)
274
278
275 editing_mode = getattr(EditingMode, self.editing_mode.upper())
279 editing_mode = getattr(EditingMode, self.editing_mode.upper())
276
280
277 self.pt_app = PromptSession(
281 self.pt_app = PromptSession(
278 editing_mode=editing_mode,
282 editing_mode=editing_mode,
279 key_bindings=key_bindings,
283 key_bindings=key_bindings,
280 history=history,
284 history=history,
281 completer=IPythonPTCompleter(shell=self),
285 completer=IPythonPTCompleter(shell=self),
282 enable_history_search = self.enable_history_search,
286 enable_history_search = self.enable_history_search,
283 style=self.style,
287 style=self.style,
284 include_default_pygments_style=False,
288 include_default_pygments_style=False,
285 mouse_support=self.mouse_support,
289 mouse_support=self.mouse_support,
286 enable_open_in_editor=self.extra_open_editor_shortcuts,
290 enable_open_in_editor=self.extra_open_editor_shortcuts,
287 color_depth=(ColorDepth.TRUE_COLOR if self.true_color else None),
291 color_depth=(ColorDepth.TRUE_COLOR if self.true_color else None),
288 **self._extra_prompt_options())
292 **self._extra_prompt_options())
289
293
290 def _make_style_from_name_or_cls(self, name_or_cls):
294 def _make_style_from_name_or_cls(self, name_or_cls):
291 """
295 """
292 Small wrapper that make an IPython compatible style from a style name
296 Small wrapper that make an IPython compatible style from a style name
293
297
294 We need that to add style for prompt ... etc.
298 We need that to add style for prompt ... etc.
295 """
299 """
296 style_overrides = {}
300 style_overrides = {}
297 if name_or_cls == 'legacy':
301 if name_or_cls == 'legacy':
298 legacy = self.colors.lower()
302 legacy = self.colors.lower()
299 if legacy == 'linux':
303 if legacy == 'linux':
300 style_cls = get_style_by_name('monokai')
304 style_cls = get_style_by_name('monokai')
301 style_overrides = _style_overrides_linux
305 style_overrides = _style_overrides_linux
302 elif legacy == 'lightbg':
306 elif legacy == 'lightbg':
303 style_overrides = _style_overrides_light_bg
307 style_overrides = _style_overrides_light_bg
304 style_cls = get_style_by_name('pastie')
308 style_cls = get_style_by_name('pastie')
305 elif legacy == 'neutral':
309 elif legacy == 'neutral':
306 # The default theme needs to be visible on both a dark background
310 # The default theme needs to be visible on both a dark background
307 # and a light background, because we can't tell what the terminal
311 # and a light background, because we can't tell what the terminal
308 # looks like. These tweaks to the default theme help with that.
312 # looks like. These tweaks to the default theme help with that.
309 style_cls = get_style_by_name('default')
313 style_cls = get_style_by_name('default')
310 style_overrides.update({
314 style_overrides.update({
311 Token.Number: '#007700',
315 Token.Number: '#007700',
312 Token.Operator: 'noinherit',
316 Token.Operator: 'noinherit',
313 Token.String: '#BB6622',
317 Token.String: '#BB6622',
314 Token.Name.Function: '#2080D0',
318 Token.Name.Function: '#2080D0',
315 Token.Name.Class: 'bold #2080D0',
319 Token.Name.Class: 'bold #2080D0',
316 Token.Name.Namespace: 'bold #2080D0',
320 Token.Name.Namespace: 'bold #2080D0',
317 Token.Prompt: '#009900',
321 Token.Prompt: '#009900',
318 Token.PromptNum: '#ansibrightgreen bold',
322 Token.PromptNum: '#ansibrightgreen bold',
319 Token.OutPrompt: '#990000',
323 Token.OutPrompt: '#990000',
320 Token.OutPromptNum: '#ansibrightred bold',
324 Token.OutPromptNum: '#ansibrightred bold',
321 })
325 })
322
326
323 # Hack: Due to limited color support on the Windows console
327 # Hack: Due to limited color support on the Windows console
324 # the prompt colors will be wrong without this
328 # the prompt colors will be wrong without this
325 if os.name == 'nt':
329 if os.name == 'nt':
326 style_overrides.update({
330 style_overrides.update({
327 Token.Prompt: '#ansidarkgreen',
331 Token.Prompt: '#ansidarkgreen',
328 Token.PromptNum: '#ansigreen bold',
332 Token.PromptNum: '#ansigreen bold',
329 Token.OutPrompt: '#ansidarkred',
333 Token.OutPrompt: '#ansidarkred',
330 Token.OutPromptNum: '#ansired bold',
334 Token.OutPromptNum: '#ansired bold',
331 })
335 })
332 elif legacy =='nocolor':
336 elif legacy =='nocolor':
333 style_cls=_NoStyle
337 style_cls=_NoStyle
334 style_overrides = {}
338 style_overrides = {}
335 else :
339 else :
336 raise ValueError('Got unknown colors: ', legacy)
340 raise ValueError('Got unknown colors: ', legacy)
337 else :
341 else :
338 if isinstance(name_or_cls, str):
342 if isinstance(name_or_cls, str):
339 style_cls = get_style_by_name(name_or_cls)
343 style_cls = get_style_by_name(name_or_cls)
340 else:
344 else:
341 style_cls = name_or_cls
345 style_cls = name_or_cls
342 style_overrides = {
346 style_overrides = {
343 Token.Prompt: '#009900',
347 Token.Prompt: '#009900',
344 Token.PromptNum: '#ansibrightgreen bold',
348 Token.PromptNum: '#ansibrightgreen bold',
345 Token.OutPrompt: '#990000',
349 Token.OutPrompt: '#990000',
346 Token.OutPromptNum: '#ansibrightred bold',
350 Token.OutPromptNum: '#ansibrightred bold',
347 }
351 }
348 style_overrides.update(self.highlighting_style_overrides)
352 style_overrides.update(self.highlighting_style_overrides)
349 style = merge_styles([
353 style = merge_styles([
350 style_from_pygments_cls(style_cls),
354 style_from_pygments_cls(style_cls),
351 style_from_pygments_dict(style_overrides),
355 style_from_pygments_dict(style_overrides),
352 ])
356 ])
353
357
354 return style
358 return style
355
359
356 @property
360 @property
357 def pt_complete_style(self):
361 def pt_complete_style(self):
358 return {
362 return {
359 'multicolumn': CompleteStyle.MULTI_COLUMN,
363 'multicolumn': CompleteStyle.MULTI_COLUMN,
360 'column': CompleteStyle.COLUMN,
364 'column': CompleteStyle.COLUMN,
361 'readlinelike': CompleteStyle.READLINE_LIKE,
365 'readlinelike': CompleteStyle.READLINE_LIKE,
362 }[self.display_completions]
366 }[self.display_completions]
363
367
364 def _extra_prompt_options(self):
368 def _extra_prompt_options(self):
365 """
369 """
366 Return the current layout option for the current Terminal InteractiveShell
370 Return the current layout option for the current Terminal InteractiveShell
367 """
371 """
368 def get_message():
372 def get_message():
369 return PygmentsTokens(self.prompts.in_prompt_tokens())
373 return PygmentsTokens(self.prompts.in_prompt_tokens())
370
374
371 return {
375 return {
372 'complete_in_thread': False,
376 'complete_in_thread': False,
373 'lexer':IPythonPTLexer(),
377 'lexer':IPythonPTLexer(),
374 'reserve_space_for_menu':self.space_for_menu,
378 'reserve_space_for_menu':self.space_for_menu,
375 'message': get_message,
379 'message': get_message,
376 'prompt_continuation': (
380 'prompt_continuation': (
377 lambda width, lineno, is_soft_wrap:
381 lambda width, lineno, is_soft_wrap:
378 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
382 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
379 'multiline': True,
383 'multiline': True,
380 'complete_style': self.pt_complete_style,
384 'complete_style': self.pt_complete_style,
381
385
382 # Highlight matching brackets, but only when this setting is
386 # Highlight matching brackets, but only when this setting is
383 # enabled, and only when the DEFAULT_BUFFER has the focus.
387 # enabled, and only when the DEFAULT_BUFFER has the focus.
384 'input_processors': [ConditionalProcessor(
388 'input_processors': [ConditionalProcessor(
385 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
389 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
386 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
390 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
387 Condition(lambda: self.highlight_matching_brackets))],
391 Condition(lambda: self.highlight_matching_brackets))],
388 'inputhook': self.inputhook,
392 'inputhook': self.inputhook,
389 }
393 }
390
394
391 def prompt_for_code(self):
395 def prompt_for_code(self):
392 if self.rl_next_input:
396 if self.rl_next_input:
393 default = self.rl_next_input
397 default = self.rl_next_input
394 self.rl_next_input = None
398 self.rl_next_input = None
395 else:
399 else:
396 default = ''
400 default = ''
397
401
398 with patch_stdout(raw=True):
402 with patch_stdout(raw=True):
399 text = self.pt_app.prompt(
403 text = self.pt_app.prompt(
400 default=default,
404 default=default,
401 # pre_run=self.pre_prompt,# reset_current_buffer=True,
405 # pre_run=self.pre_prompt,# reset_current_buffer=True,
402 **self._extra_prompt_options())
406 **self._extra_prompt_options())
403 return text
407 return text
404
408
405 def enable_win_unicode_console(self):
409 def enable_win_unicode_console(self):
406 if sys.version_info >= (3, 6):
410 if sys.version_info >= (3, 6):
407 # Since PEP 528, Python uses the unicode APIs for the Windows
411 # Since PEP 528, Python uses the unicode APIs for the Windows
408 # console by default, so WUC shouldn't be needed.
412 # console by default, so WUC shouldn't be needed.
409 return
413 return
410
414
411 import win_unicode_console
415 import win_unicode_console
412 win_unicode_console.enable()
416 win_unicode_console.enable()
413
417
414 def init_io(self):
418 def init_io(self):
415 if sys.platform not in {'win32', 'cli'}:
419 if sys.platform not in {'win32', 'cli'}:
416 return
420 return
417
421
418 self.enable_win_unicode_console()
422 self.enable_win_unicode_console()
419
423
420 import colorama
424 import colorama
421 colorama.init()
425 colorama.init()
422
426
423 # For some reason we make these wrappers around stdout/stderr.
427 # For some reason we make these wrappers around stdout/stderr.
424 # For now, we need to reset them so all output gets coloured.
428 # For now, we need to reset them so all output gets coloured.
425 # https://github.com/ipython/ipython/issues/8669
429 # https://github.com/ipython/ipython/issues/8669
426 # io.std* are deprecated, but don't show our own deprecation warnings
430 # io.std* are deprecated, but don't show our own deprecation warnings
427 # during initialization of the deprecated API.
431 # during initialization of the deprecated API.
428 with warnings.catch_warnings():
432 with warnings.catch_warnings():
429 warnings.simplefilter('ignore', DeprecationWarning)
433 warnings.simplefilter('ignore', DeprecationWarning)
430 io.stdout = io.IOStream(sys.stdout)
434 io.stdout = io.IOStream(sys.stdout)
431 io.stderr = io.IOStream(sys.stderr)
435 io.stderr = io.IOStream(sys.stderr)
432
436
433 def init_magics(self):
437 def init_magics(self):
434 super(TerminalInteractiveShell, self).init_magics()
438 super(TerminalInteractiveShell, self).init_magics()
435 self.register_magics(TerminalMagics)
439 self.register_magics(TerminalMagics)
436
440
437 def init_alias(self):
441 def init_alias(self):
438 # The parent class defines aliases that can be safely used with any
442 # The parent class defines aliases that can be safely used with any
439 # frontend.
443 # frontend.
440 super(TerminalInteractiveShell, self).init_alias()
444 super(TerminalInteractiveShell, self).init_alias()
441
445
442 # Now define aliases that only make sense on the terminal, because they
446 # Now define aliases that only make sense on the terminal, because they
443 # need direct access to the console in a way that we can't emulate in
447 # need direct access to the console in a way that we can't emulate in
444 # GUI or web frontend
448 # GUI or web frontend
445 if os.name == 'posix':
449 if os.name == 'posix':
446 for cmd in ['clear', 'more', 'less', 'man']:
450 for cmd in ['clear', 'more', 'less', 'man']:
447 self.alias_manager.soft_define_alias(cmd, cmd)
451 self.alias_manager.soft_define_alias(cmd, cmd)
448
452
449
453
450 def __init__(self, *args, **kwargs):
454 def __init__(self, *args, **kwargs):
451 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
455 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
452 self.init_prompt_toolkit_cli()
456 self.init_prompt_toolkit_cli()
453 self.init_term_title()
457 self.init_term_title()
454 self.keep_running = True
458 self.keep_running = True
455
459
456 self.debugger_history = InMemoryHistory()
460 self.debugger_history = InMemoryHistory()
457
461
458 def ask_exit(self):
462 def ask_exit(self):
459 self.keep_running = False
463 self.keep_running = False
460
464
461 rl_next_input = None
465 rl_next_input = None
462
466
463 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
467 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
464
468
465 if display_banner is not DISPLAY_BANNER_DEPRECATED:
469 if display_banner is not DISPLAY_BANNER_DEPRECATED:
466 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
470 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
467
471
468 self.keep_running = True
472 self.keep_running = True
469 while self.keep_running:
473 while self.keep_running:
470 print(self.separate_in, end='')
474 print(self.separate_in, end='')
471
475
472 try:
476 try:
473 code = self.prompt_for_code()
477 code = self.prompt_for_code()
474 except EOFError:
478 except EOFError:
475 if (not self.confirm_exit) \
479 if (not self.confirm_exit) \
476 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
480 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
477 self.ask_exit()
481 self.ask_exit()
478
482
479 else:
483 else:
480 if code:
484 if code:
481 self.run_cell(code, store_history=True)
485 self.run_cell(code, store_history=True)
482
486
483 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
487 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
484 # An extra layer of protection in case someone mashing Ctrl-C breaks
488 # An extra layer of protection in case someone mashing Ctrl-C breaks
485 # out of our internal code.
489 # out of our internal code.
486 if display_banner is not DISPLAY_BANNER_DEPRECATED:
490 if display_banner is not DISPLAY_BANNER_DEPRECATED:
487 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
491 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
488 while True:
492 while True:
489 try:
493 try:
490 self.interact()
494 self.interact()
491 break
495 break
492 except KeyboardInterrupt as e:
496 except KeyboardInterrupt as e:
493 print("\n%s escaped interact()\n" % type(e).__name__)
497 print("\n%s escaped interact()\n" % type(e).__name__)
494 finally:
498 finally:
495 # An interrupt during the eventloop will mess up the
499 # An interrupt during the eventloop will mess up the
496 # internal state of the prompt_toolkit library.
500 # internal state of the prompt_toolkit library.
497 # Stopping the eventloop fixes this, see
501 # Stopping the eventloop fixes this, see
498 # https://github.com/ipython/ipython/pull/9867
502 # https://github.com/ipython/ipython/pull/9867
499 if hasattr(self, '_eventloop'):
503 if hasattr(self, '_eventloop'):
500 self._eventloop.stop()
504 self._eventloop.stop()
501
505
502 _inputhook = None
506 _inputhook = None
503 def inputhook(self, context):
507 def inputhook(self, context):
504 if self._inputhook is not None:
508 if self._inputhook is not None:
505 self._inputhook(context)
509 self._inputhook(context)
506
510
507 active_eventloop = None
511 active_eventloop = None
508 def enable_gui(self, gui=None):
512 def enable_gui(self, gui=None):
509 if gui:
513 if gui:
510 self.active_eventloop, self._inputhook =\
514 self.active_eventloop, self._inputhook =\
511 get_inputhook_name_and_func(gui)
515 get_inputhook_name_and_func(gui)
512 else:
516 else:
513 self.active_eventloop = self._inputhook = None
517 self.active_eventloop = self._inputhook = None
514
518
515 # Run !system commands directly, not through pipes, so terminal programs
519 # Run !system commands directly, not through pipes, so terminal programs
516 # work correctly.
520 # work correctly.
517 system = InteractiveShell.system_raw
521 system = InteractiveShell.system_raw
518
522
519 def auto_rewrite_input(self, cmd):
523 def auto_rewrite_input(self, cmd):
520 """Overridden from the parent class to use fancy rewriting prompt"""
524 """Overridden from the parent class to use fancy rewriting prompt"""
521 if not self.show_rewritten_input:
525 if not self.show_rewritten_input:
522 return
526 return
523
527
524 tokens = self.prompts.rewrite_prompt_tokens()
528 tokens = self.prompts.rewrite_prompt_tokens()
525 if self.pt_app:
529 if self.pt_app:
526 print_formatted_text(PygmentsTokens(tokens), end='',
530 print_formatted_text(PygmentsTokens(tokens), end='',
527 style=self.pt_app.app.style)
531 style=self.pt_app.app.style)
528 print(cmd)
532 print(cmd)
529 else:
533 else:
530 prompt = ''.join(s for t, s in tokens)
534 prompt = ''.join(s for t, s in tokens)
531 print(prompt, cmd, sep='')
535 print(prompt, cmd, sep='')
532
536
533 _prompts_before = None
537 _prompts_before = None
534 def switch_doctest_mode(self, mode):
538 def switch_doctest_mode(self, mode):
535 """Switch prompts to classic for %doctest_mode"""
539 """Switch prompts to classic for %doctest_mode"""
536 if mode:
540 if mode:
537 self._prompts_before = self.prompts
541 self._prompts_before = self.prompts
538 self.prompts = ClassicPrompts(self)
542 self.prompts = ClassicPrompts(self)
539 elif self._prompts_before:
543 elif self._prompts_before:
540 self.prompts = self._prompts_before
544 self.prompts = self._prompts_before
541 self._prompts_before = None
545 self._prompts_before = None
542 # self._update_layout()
546 # self._update_layout()
543
547
544
548
545 InteractiveShellABC.register(TerminalInteractiveShell)
549 InteractiveShellABC.register(TerminalInteractiveShell)
546
550
547 if __name__ == '__main__':
551 if __name__ == '__main__':
548 TerminalInteractiveShell.instance().interact()
552 TerminalInteractiveShell.instance().interact()
@@ -1,92 +1,91 b''
1 """Terminal input and output prompts."""
1 """Terminal input and output prompts."""
2
2
3 from pygments.token import Token
3 from pygments.token import Token
4 import sys
4 import sys
5
5
6 from IPython.core.displayhook import DisplayHook
6 from IPython.core.displayhook import DisplayHook
7
7
8 from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens
8 from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens
9 from prompt_toolkit.shortcuts import print_formatted_text
9 from prompt_toolkit.shortcuts import print_formatted_text
10
10
11
11
12 class Prompts(object):
12 class Prompts(object):
13 def __init__(self, shell):
13 def __init__(self, shell):
14 self.shell = shell
14 self.shell = shell
15
15
16 def vi_mode(self):
16 def vi_mode(self):
17 if not hasattr(self.shell.pt_app, 'editing_mode'):
17 if (getattr(self.shell.pt_app, 'editing_mode', None) == 'VI'
18 return ''
18 and self.shell.prompt_includes_vi_mode):
19 if self.shell.pt_app.editing_mode == 'VI':
20 return '['+str(self.shell.pt_app.app.vi_state.input_mode)[3:6]+'] '
19 return '['+str(self.shell.pt_app.app.vi_state.input_mode)[3:6]+'] '
21 return ''
20 return ''
22
21
23
22
24 def in_prompt_tokens(self):
23 def in_prompt_tokens(self):
25 return [
24 return [
26 (Token.Prompt, self.vi_mode() ),
25 (Token.Prompt, self.vi_mode() ),
27 (Token.Prompt, 'In ['),
26 (Token.Prompt, 'In ['),
28 (Token.PromptNum, str(self.shell.execution_count)),
27 (Token.PromptNum, str(self.shell.execution_count)),
29 (Token.Prompt, ']: '),
28 (Token.Prompt, ']: '),
30 ]
29 ]
31
30
32 def _width(self):
31 def _width(self):
33 return fragment_list_width(self.in_prompt_tokens())
32 return fragment_list_width(self.in_prompt_tokens())
34
33
35 def continuation_prompt_tokens(self, width=None):
34 def continuation_prompt_tokens(self, width=None):
36 if width is None:
35 if width is None:
37 width = self._width()
36 width = self._width()
38 return [
37 return [
39 (Token.Prompt, (' ' * (width - 5)) + '...: '),
38 (Token.Prompt, (' ' * (width - 5)) + '...: '),
40 ]
39 ]
41
40
42 def rewrite_prompt_tokens(self):
41 def rewrite_prompt_tokens(self):
43 width = self._width()
42 width = self._width()
44 return [
43 return [
45 (Token.Prompt, ('-' * (width - 2)) + '> '),
44 (Token.Prompt, ('-' * (width - 2)) + '> '),
46 ]
45 ]
47
46
48 def out_prompt_tokens(self):
47 def out_prompt_tokens(self):
49 return [
48 return [
50 (Token.OutPrompt, 'Out['),
49 (Token.OutPrompt, 'Out['),
51 (Token.OutPromptNum, str(self.shell.execution_count)),
50 (Token.OutPromptNum, str(self.shell.execution_count)),
52 (Token.OutPrompt, ']: '),
51 (Token.OutPrompt, ']: '),
53 ]
52 ]
54
53
55 class ClassicPrompts(Prompts):
54 class ClassicPrompts(Prompts):
56 def in_prompt_tokens(self):
55 def in_prompt_tokens(self):
57 return [
56 return [
58 (Token.Prompt, '>>> '),
57 (Token.Prompt, '>>> '),
59 ]
58 ]
60
59
61 def continuation_prompt_tokens(self, width=None):
60 def continuation_prompt_tokens(self, width=None):
62 return [
61 return [
63 (Token.Prompt, '... ')
62 (Token.Prompt, '... ')
64 ]
63 ]
65
64
66 def rewrite_prompt_tokens(self):
65 def rewrite_prompt_tokens(self):
67 return []
66 return []
68
67
69 def out_prompt_tokens(self):
68 def out_prompt_tokens(self):
70 return []
69 return []
71
70
72 class RichPromptDisplayHook(DisplayHook):
71 class RichPromptDisplayHook(DisplayHook):
73 """Subclass of base display hook using coloured prompt"""
72 """Subclass of base display hook using coloured prompt"""
74 def write_output_prompt(self):
73 def write_output_prompt(self):
75 sys.stdout.write(self.shell.separate_out)
74 sys.stdout.write(self.shell.separate_out)
76 # If we're not displaying a prompt, it effectively ends with a newline,
75 # If we're not displaying a prompt, it effectively ends with a newline,
77 # because the output will be left-aligned.
76 # because the output will be left-aligned.
78 self.prompt_end_newline = True
77 self.prompt_end_newline = True
79
78
80 if self.do_full_cache:
79 if self.do_full_cache:
81 tokens = self.shell.prompts.out_prompt_tokens()
80 tokens = self.shell.prompts.out_prompt_tokens()
82 prompt_txt = ''.join(s for t, s in tokens)
81 prompt_txt = ''.join(s for t, s in tokens)
83 if prompt_txt and not prompt_txt.endswith('\n'):
82 if prompt_txt and not prompt_txt.endswith('\n'):
84 # Ask for a newline before multiline output
83 # Ask for a newline before multiline output
85 self.prompt_end_newline = False
84 self.prompt_end_newline = False
86
85
87 if self.shell.pt_app:
86 if self.shell.pt_app:
88 print_formatted_text(PygmentsTokens(tokens),
87 print_formatted_text(PygmentsTokens(tokens),
89 style=self.shell.pt_app.app.style, end='',
88 style=self.shell.pt_app.app.style, end='',
90 )
89 )
91 else:
90 else:
92 sys.stdout.write(prompt_txt)
91 sys.stdout.write(prompt_txt)
General Comments 0
You need to be logged in to leave comments. Login now