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