##// END OF EJS Templates
Merge pull request #12586 from mskar/auto_match...
Matthias Bussonnier -
r27376:b2c3261b merge
parent child Browse files
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,371 +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 if shell.display_completions == 'readlinelike':
88 @Condition
89 kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER)
89 def auto_match():
90 & ~has_selection
90 return shell.auto_match
91 & insert_mode
91
92 & ~cursor_in_leading_ws
92 focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER)
93 ))(display_completions_like_readline)
93 _preceding_text_cache = {}
94 _following_text_cache = {}
95
96 def preceding_text(pattern):
97 try:
98 return _preceding_text_cache[pattern]
99 except KeyError:
100 pass
101 m = re.compile(pattern)
102
103 def _preceding_text():
104 app = get_app()
105 return bool(m.match(app.current_buffer.document.current_line_before_cursor))
106
107 condition = Condition(_preceding_text)
108 _preceding_text_cache[pattern] = condition
109 return condition
110
111 def following_text(pattern):
112 try:
113 return _following_text_cache[pattern]
114 except KeyError:
115 pass
116 m = re.compile(pattern)
117
118 def _following_text():
119 app = get_app()
120 return bool(m.match(app.current_buffer.document.current_line_after_cursor))
121
122 condition = Condition(_following_text)
123 _following_text_cache[pattern] = condition
124 return condition
125
126 # auto match
127 @kb.add("(", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
128 def _(event):
129 event.current_buffer.insert_text("()")
130 event.current_buffer.cursor_left()
131
132 @kb.add("[", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
133 def _(event):
134 event.current_buffer.insert_text("[]")
135 event.current_buffer.cursor_left()
136
137 @kb.add("{", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
138 def _(event):
139 event.current_buffer.insert_text("{}")
140 event.current_buffer.cursor_left()
141
142 @kb.add('"', filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
143 def _(event):
144 event.current_buffer.insert_text('""')
145 event.current_buffer.cursor_left()
146
147 @kb.add("'", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
148 def _(event):
149 event.current_buffer.insert_text("''")
150 event.current_buffer.cursor_left()
151
152 # raw string
153 @kb.add("(", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$"))
154 def _(event):
155 matches = re.match(
156 r".*(r|R)[\"'](-*)",
157 event.current_buffer.document.current_line_before_cursor,
158 )
159 dashes = matches.group(2) or ""
160 event.current_buffer.insert_text("()" + dashes)
161 event.current_buffer.cursor_left(len(dashes) + 1)
162
163 @kb.add("[", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$"))
164 def _(event):
165 matches = re.match(
166 r".*(r|R)[\"'](-*)",
167 event.current_buffer.document.current_line_before_cursor,
168 )
169 dashes = matches.group(2) or ""
170 event.current_buffer.insert_text("[]" + dashes)
171 event.current_buffer.cursor_left(len(dashes) + 1)
172
173 @kb.add("{", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$"))
174 def _(event):
175 matches = re.match(
176 r".*(r|R)[\"'](-*)",
177 event.current_buffer.document.current_line_before_cursor,
178 )
179 dashes = matches.group(2) or ""
180 event.current_buffer.insert_text("{}" + dashes)
181 event.current_buffer.cursor_left(len(dashes) + 1)
182
183 @kb.add('"', filter=focused_insert & preceding_text(r".*(r|R)$"))
184 def _(event):
185 event.current_buffer.insert_text('""')
186 event.current_buffer.cursor_left()
187
188 @kb.add("'", filter=focused_insert & preceding_text(r".*(r|R)$"))
189 def _(event):
190 event.current_buffer.insert_text("''")
191 event.current_buffer.cursor_left()
192
193 # just move cursor
194 @kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))
195 @kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))
196 @kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))
197 @kb.add('"', filter=focused_insert & auto_match & following_text('^"'))
198 @kb.add("'", filter=focused_insert & auto_match & following_text("^'"))
199 def _(event):
200 event.current_buffer.cursor_right()
201
202 @kb.add(
203 "backspace",
204 filter=focused_insert
205 & preceding_text(r".*\($")
206 & auto_match
207 & following_text(r"^\)"),
208 )
209 @kb.add(
210 "backspace",
211 filter=focused_insert
212 & preceding_text(r".*\[$")
213 & auto_match
214 & following_text(r"^\]"),
215 )
216 @kb.add(
217 "backspace",
218 filter=focused_insert
219 & preceding_text(r".*\{$")
220 & auto_match
221 & following_text(r"^\}"),
222 )
223 @kb.add(
224 "backspace",
225 filter=focused_insert
226 & preceding_text('.*"$')
227 & auto_match
228 & following_text('^"'),
229 )
230 @kb.add(
231 "backspace",
232 filter=focused_insert
233 & preceding_text(r".*'$")
234 & auto_match
235 & following_text(r"^'"),
236 )
237 def _(event):
238 event.current_buffer.delete()
239 event.current_buffer.delete_before_cursor()
240
241 if shell.display_completions == "readlinelike":
242 kb.add(
243 "c-i",
244 filter=(
245 has_focus(DEFAULT_BUFFER)
246 & ~has_selection
247 & insert_mode
248 & ~cursor_in_leading_ws
249 ),
250 )(display_completions_like_readline)
94
251
95 if sys.platform == "win32":
252 if sys.platform == "win32":
96 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)
97
254
98 @Condition
255 @Condition
99 def ebivim():
256 def ebivim():
100 return shell.emacs_bindings_in_vi_insert_mode
257 return shell.emacs_bindings_in_vi_insert_mode
101
258
102 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
259 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
103
260
104 # Needed for to accept autosuggestions in vi insert mode
261 # Needed for to accept autosuggestions in vi insert mode
105 @kb.add("c-e", filter=focused_insert_vi & ebivim)
262 @kb.add("c-e", filter=focused_insert_vi & ebivim)
106 def _(event):
263 def _(event):
107 b = event.current_buffer
264 b = event.current_buffer
108 suggestion = b.suggestion
265 suggestion = b.suggestion
109 if suggestion:
266 if suggestion:
110 b.insert_text(suggestion.text)
267 b.insert_text(suggestion.text)
111 else:
268 else:
112 nc.end_of_line(event)
269 nc.end_of_line(event)
113
270
114 @kb.add("c-f", filter=focused_insert_vi)
271 @kb.add("c-f", filter=focused_insert_vi)
115 def _(event):
272 def _(event):
116 b = event.current_buffer
273 b = event.current_buffer
117 suggestion = b.suggestion
274 suggestion = b.suggestion
118 if suggestion:
275 if suggestion:
119 b.insert_text(suggestion.text)
276 b.insert_text(suggestion.text)
120 else:
277 else:
121 nc.forward_char(event)
278 nc.forward_char(event)
122
279
123 @kb.add("escape", "f", filter=focused_insert_vi & ebivim)
280 @kb.add("escape", "f", filter=focused_insert_vi & ebivim)
124 def _(event):
281 def _(event):
125 b = event.current_buffer
282 b = event.current_buffer
126 suggestion = b.suggestion
283 suggestion = b.suggestion
127 if suggestion:
284 if suggestion:
128 t = re.split(r"(\S+\s+)", suggestion.text)
285 t = re.split(r"(\S+\s+)", suggestion.text)
129 b.insert_text(next((x for x in t if x), ""))
286 b.insert_text(next((x for x in t if x), ""))
130 else:
287 else:
131 nc.forward_word(event)
288 nc.forward_word(event)
132
289
133 # Simple Control keybindings
290 # Simple Control keybindings
134 key_cmd_dict = {
291 key_cmd_dict = {
135 "c-a": nc.beginning_of_line,
292 "c-a": nc.beginning_of_line,
136 "c-b": nc.backward_char,
293 "c-b": nc.backward_char,
137 "c-k": nc.kill_line,
294 "c-k": nc.kill_line,
138 "c-w": nc.backward_kill_word,
295 "c-w": nc.backward_kill_word,
139 "c-y": nc.yank,
296 "c-y": nc.yank,
140 "c-_": nc.undo,
297 "c-_": nc.undo,
141 }
298 }
142
299
143 for key, cmd in key_cmd_dict.items():
300 for key, cmd in key_cmd_dict.items():
144 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
301 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
145
302
146 # Alt and Combo Control keybindings
303 # Alt and Combo Control keybindings
147 keys_cmd_dict = {
304 keys_cmd_dict = {
148 # Control Combos
305 # Control Combos
149 ("c-x", "c-e"): nc.edit_and_execute,
306 ("c-x", "c-e"): nc.edit_and_execute,
150 ("c-x", "e"): nc.edit_and_execute,
307 ("c-x", "e"): nc.edit_and_execute,
151 # Alt
308 # Alt
152 ("escape", "b"): nc.backward_word,
309 ("escape", "b"): nc.backward_word,
153 ("escape", "c"): nc.capitalize_word,
310 ("escape", "c"): nc.capitalize_word,
154 ("escape", "d"): nc.kill_word,
311 ("escape", "d"): nc.kill_word,
155 ("escape", "h"): nc.backward_kill_word,
312 ("escape", "h"): nc.backward_kill_word,
156 ("escape", "l"): nc.downcase_word,
313 ("escape", "l"): nc.downcase_word,
157 ("escape", "u"): nc.uppercase_word,
314 ("escape", "u"): nc.uppercase_word,
158 ("escape", "y"): nc.yank_pop,
315 ("escape", "y"): nc.yank_pop,
159 ("escape", "."): nc.yank_last_arg,
316 ("escape", "."): nc.yank_last_arg,
160 }
317 }
161
318
162 for keys, cmd in keys_cmd_dict.items():
319 for keys, cmd in keys_cmd_dict.items():
163 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
320 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
164
321
165 def get_input_mode(self):
322 def get_input_mode(self):
166 app = get_app()
323 app = get_app()
167 app.ttimeoutlen = shell.ttimeoutlen
324 app.ttimeoutlen = shell.ttimeoutlen
168 app.timeoutlen = shell.timeoutlen
325 app.timeoutlen = shell.timeoutlen
169
326
170 return self._input_mode
327 return self._input_mode
171
328
172 def set_input_mode(self, mode):
329 def set_input_mode(self, mode):
173 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
330 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
174 cursor = "\x1b[{} q".format(shape)
331 cursor = "\x1b[{} q".format(shape)
175
332
176 if hasattr(sys.stdout, "_cli"):
333 if hasattr(sys.stdout, "_cli"):
177 write = sys.stdout._cli.output.write_raw
334 write = sys.stdout._cli.output.write_raw
178 else:
335 else:
179 write = sys.stdout.write
336 write = sys.stdout.write
180
337
181 write(cursor)
338 write(cursor)
182 sys.stdout.flush()
339 sys.stdout.flush()
183
340
184 self._input_mode = mode
341 self._input_mode = mode
185
342
186 if shell.editing_mode == "vi" and shell.modal_cursor:
343 if shell.editing_mode == "vi" and shell.modal_cursor:
187 ViState._input_mode = InputMode.INSERT
344 ViState._input_mode = InputMode.INSERT
188 ViState.input_mode = property(get_input_mode, set_input_mode)
345 ViState.input_mode = property(get_input_mode, set_input_mode)
189
346
190 return kb
347 return kb
191
348
192
349
193 def reformat_text_before_cursor(buffer, document, shell):
350 def reformat_text_before_cursor(buffer, document, shell):
194 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
351 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
195 try:
352 try:
196 formatted_text = shell.reformat_handler(text)
353 formatted_text = shell.reformat_handler(text)
197 buffer.insert_text(formatted_text)
354 buffer.insert_text(formatted_text)
198 except Exception as e:
355 except Exception as e:
199 buffer.insert_text(text)
356 buffer.insert_text(text)
200
357
201
358
202 def newline_or_execute_outer(shell):
359 def newline_or_execute_outer(shell):
203
360
204 def newline_or_execute(event):
361 def newline_or_execute(event):
205 """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."""
206 b = event.current_buffer
363 b = event.current_buffer
207 d = b.document
364 d = b.document
208
365
209 if b.complete_state:
366 if b.complete_state:
210 cc = b.complete_state.current_completion
367 cc = b.complete_state.current_completion
211 if cc:
368 if cc:
212 b.apply_completion(cc)
369 b.apply_completion(cc)
213 else:
370 else:
214 b.cancel_completion()
371 b.cancel_completion()
215 return
372 return
216
373
217 # 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.
218 # See https://github.com/ipython/ipython/issues/10425
375 # See https://github.com/ipython/ipython/issues/10425
219 if d.line_count == 1:
376 if d.line_count == 1:
220 check_text = d.text
377 check_text = d.text
221 else:
378 else:
222 check_text = d.text[:d.cursor_position]
379 check_text = d.text[:d.cursor_position]
223 status, indent = shell.check_complete(check_text)
380 status, indent = shell.check_complete(check_text)
224
381
225 # 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
226 # before cursor
383 # before cursor
227 after_cursor = d.text[d.cursor_position:]
384 after_cursor = d.text[d.cursor_position:]
228 reformatted = False
385 reformatted = False
229 if not after_cursor.strip():
386 if not after_cursor.strip():
230 reformat_text_before_cursor(b, d, shell)
387 reformat_text_before_cursor(b, d, shell)
231 reformatted = True
388 reformatted = True
232 if not (d.on_last_line or
389 if not (d.on_last_line or
233 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()
234 ):
391 ):
235 if shell.autoindent:
392 if shell.autoindent:
236 b.insert_text('\n' + indent)
393 b.insert_text('\n' + indent)
237 else:
394 else:
238 b.insert_text('\n')
395 b.insert_text('\n')
239 return
396 return
240
397
241 if (status != 'incomplete') and b.accept_handler:
398 if (status != 'incomplete') and b.accept_handler:
242 if not reformatted:
399 if not reformatted:
243 reformat_text_before_cursor(b, d, shell)
400 reformat_text_before_cursor(b, d, shell)
244 b.validate_and_handle()
401 b.validate_and_handle()
245 else:
402 else:
246 if shell.autoindent:
403 if shell.autoindent:
247 b.insert_text('\n' + indent)
404 b.insert_text('\n' + indent)
248 else:
405 else:
249 b.insert_text('\n')
406 b.insert_text('\n')
250 return newline_or_execute
407 return newline_or_execute
251
408
252
409
253 def previous_history_or_previous_completion(event):
410 def previous_history_or_previous_completion(event):
254 """
411 """
255 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.
256
413
257 If completer is open this still select previous completion.
414 If completer is open this still select previous completion.
258 """
415 """
259 event.current_buffer.auto_up()
416 event.current_buffer.auto_up()
260
417
261
418
262 def next_history_or_next_completion(event):
419 def next_history_or_next_completion(event):
263 """
420 """
264 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.
265
422
266 If completer is open this still select next completion.
423 If completer is open this still select next completion.
267 """
424 """
268 event.current_buffer.auto_down()
425 event.current_buffer.auto_down()
269
426
270
427
271 def dismiss_completion(event):
428 def dismiss_completion(event):
272 b = event.current_buffer
429 b = event.current_buffer
273 if b.complete_state:
430 if b.complete_state:
274 b.cancel_completion()
431 b.cancel_completion()
275
432
276
433
277 def reset_buffer(event):
434 def reset_buffer(event):
278 b = event.current_buffer
435 b = event.current_buffer
279 if b.complete_state:
436 if b.complete_state:
280 b.cancel_completion()
437 b.cancel_completion()
281 else:
438 else:
282 b.reset()
439 b.reset()
283
440
284
441
285 def reset_search_buffer(event):
442 def reset_search_buffer(event):
286 if event.current_buffer.document.text:
443 if event.current_buffer.document.text:
287 event.current_buffer.reset()
444 event.current_buffer.reset()
288 else:
445 else:
289 event.app.layout.focus(DEFAULT_BUFFER)
446 event.app.layout.focus(DEFAULT_BUFFER)
290
447
291 def suspend_to_bg(event):
448 def suspend_to_bg(event):
292 event.app.suspend_to_background()
449 event.app.suspend_to_background()
293
450
294 def force_exit(event):
451 def force_exit(event):
295 """
452 """
296 Force exit (with a non-zero return value)
453 Force exit (with a non-zero return value)
297 """
454 """
298 sys.exit("Quit")
455 sys.exit("Quit")
299
456
300 def indent_buffer(event):
457 def indent_buffer(event):
301 event.current_buffer.insert_text(' ' * 4)
458 event.current_buffer.insert_text(' ' * 4)
302
459
303 @undoc
460 @undoc
304 def newline_with_copy_margin(event):
461 def newline_with_copy_margin(event):
305 """
462 """
306 DEPRECATED since IPython 6.0
463 DEPRECATED since IPython 6.0
307
464
308 See :any:`newline_autoindent_outer` for a replacement.
465 See :any:`newline_autoindent_outer` for a replacement.
309
466
310 Preserve margin and cursor position when using
467 Preserve margin and cursor position when using
311 Control-O to insert a newline in EMACS mode
468 Control-O to insert a newline in EMACS mode
312 """
469 """
313 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. "
314 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
471 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
315 DeprecationWarning, stacklevel=2)
472 DeprecationWarning, stacklevel=2)
316
473
317 b = event.current_buffer
474 b = event.current_buffer
318 cursor_start_pos = b.document.cursor_position_col
475 cursor_start_pos = b.document.cursor_position_col
319 b.newline(copy_margin=True)
476 b.newline(copy_margin=True)
320 b.cursor_up(count=1)
477 b.cursor_up(count=1)
321 cursor_end_pos = b.document.cursor_position_col
478 cursor_end_pos = b.document.cursor_position_col
322 if cursor_start_pos != cursor_end_pos:
479 if cursor_start_pos != cursor_end_pos:
323 pos_diff = cursor_start_pos - cursor_end_pos
480 pos_diff = cursor_start_pos - cursor_end_pos
324 b.cursor_right(count=pos_diff)
481 b.cursor_right(count=pos_diff)
325
482
326 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
483 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
327 """
484 """
328 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.
329
486
330 Fancier version of deprecated ``newline_with_copy_margin`` which should
487 Fancier version of deprecated ``newline_with_copy_margin`` which should
331 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
332 by 4 extra space after a function definition, class definition, context
489 by 4 extra space after a function definition, class definition, context
333 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
490 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
334 """
491 """
335
492
336 def newline_autoindent(event):
493 def newline_autoindent(event):
337 """insert a newline after the cursor indented appropriately."""
494 """insert a newline after the cursor indented appropriately."""
338 b = event.current_buffer
495 b = event.current_buffer
339 d = b.document
496 d = b.document
340
497
341 if b.complete_state:
498 if b.complete_state:
342 b.cancel_completion()
499 b.cancel_completion()
343 text = d.text[:d.cursor_position] + '\n'
500 text = d.text[:d.cursor_position] + '\n'
344 _, indent = inputsplitter.check_complete(text)
501 _, indent = inputsplitter.check_complete(text)
345 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
502 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
346
503
347 return newline_autoindent
504 return newline_autoindent
348
505
349
506
350 def open_input_in_editor(event):
507 def open_input_in_editor(event):
351 event.app.current_buffer.open_in_editor()
508 event.app.current_buffer.open_in_editor()
352
509
353
510
354 if sys.platform == 'win32':
511 if sys.platform == 'win32':
355 from IPython.core.error import TryNext
512 from IPython.core.error import TryNext
356 from IPython.lib.clipboard import (ClipboardEmpty,
513 from IPython.lib.clipboard import (ClipboardEmpty,
357 win32_clipboard_get,
514 win32_clipboard_get,
358 tkinter_clipboard_get)
515 tkinter_clipboard_get)
359
516
360 @undoc
517 @undoc
361 def win_paste(event):
518 def win_paste(event):
362 try:
519 try:
363 text = win32_clipboard_get()
520 text = win32_clipboard_get()
364 except TryNext:
521 except TryNext:
365 try:
522 try:
366 text = tkinter_clipboard_get()
523 text = tkinter_clipboard_get()
367 except (TryNext, ClipboardEmpty):
524 except (TryNext, ClipboardEmpty):
368 return
525 return
369 except ClipboardEmpty:
526 except ClipboardEmpty:
370 return
527 return
371 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