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