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