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