##// END OF EJS Templates
Have Ctrl-C in search mode clear query, if present...
Gil Forsyth -
Show More
@@ -1,409 +1,416 b''
1 """IPython terminal interface using prompt_toolkit in place of readline"""
1 """IPython terminal interface using prompt_toolkit in place of readline"""
2 from __future__ import print_function
2 from __future__ import print_function
3
3
4 import os
4 import os
5 import sys
5 import sys
6 import signal
6 import signal
7 import unicodedata
7 import unicodedata
8 from warnings import warn
8 from warnings import warn
9 from wcwidth import wcwidth
9 from wcwidth import wcwidth
10
10
11 from IPython.core.error import TryNext
11 from IPython.core.error import TryNext
12 from IPython.core.interactiveshell import InteractiveShell
12 from IPython.core.interactiveshell import InteractiveShell
13 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
13 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
14 from IPython.utils.terminal import toggle_set_term_title, set_term_title
14 from IPython.utils.terminal import toggle_set_term_title, set_term_title
15 from IPython.utils.process import abbrev_cwd
15 from IPython.utils.process import abbrev_cwd
16 from traitlets import Bool, CBool, Unicode, Dict
16 from traitlets import Bool, CBool, Unicode, Dict
17
17
18 from prompt_toolkit.completion import Completer, Completion
18 from prompt_toolkit.completion import Completer, Completion
19 from prompt_toolkit.enums import DEFAULT_BUFFER
19 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
20 from prompt_toolkit.filters import HasFocus, HasSelection, Condition
20 from prompt_toolkit.filters import HasFocus, HasSelection, Condition
21 from prompt_toolkit.history import InMemoryHistory
21 from prompt_toolkit.history import InMemoryHistory
22 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop
22 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop
23 from prompt_toolkit.interface import CommandLineInterface
23 from prompt_toolkit.interface import CommandLineInterface
24 from prompt_toolkit.key_binding.manager import KeyBindingManager
24 from prompt_toolkit.key_binding.manager import KeyBindingManager
25 from prompt_toolkit.key_binding.vi_state import InputMode
25 from prompt_toolkit.key_binding.vi_state import InputMode
26 from prompt_toolkit.key_binding.bindings.vi import ViStateFilter
26 from prompt_toolkit.key_binding.bindings.vi import ViStateFilter
27 from prompt_toolkit.keys import Keys
27 from prompt_toolkit.keys import Keys
28 from prompt_toolkit.layout.lexers import Lexer
28 from prompt_toolkit.layout.lexers import Lexer
29 from prompt_toolkit.layout.lexers import PygmentsLexer
29 from prompt_toolkit.layout.lexers import PygmentsLexer
30 from prompt_toolkit.styles import PygmentsStyle
30 from prompt_toolkit.styles import PygmentsStyle
31
31
32 from pygments.styles import get_style_by_name
32 from pygments.styles import get_style_by_name
33 from pygments.lexers import Python3Lexer, BashLexer, PythonLexer
33 from pygments.lexers import Python3Lexer, BashLexer, PythonLexer
34 from pygments.token import Token
34 from pygments.token import Token
35
35
36 from .pt_inputhooks import get_inputhook_func
36 from .pt_inputhooks import get_inputhook_func
37 from .interactiveshell import get_default_editor, TerminalMagics
37 from .interactiveshell import get_default_editor, TerminalMagics
38
38
39
39
40 class IPythonPTCompleter(Completer):
40 class IPythonPTCompleter(Completer):
41 """Adaptor to provide IPython completions to prompt_toolkit"""
41 """Adaptor to provide IPython completions to prompt_toolkit"""
42 def __init__(self, ipy_completer):
42 def __init__(self, ipy_completer):
43 self.ipy_completer = ipy_completer
43 self.ipy_completer = ipy_completer
44
44
45 def get_completions(self, document, complete_event):
45 def get_completions(self, document, complete_event):
46 if not document.current_line.strip():
46 if not document.current_line.strip():
47 return
47 return
48
48
49 used, matches = self.ipy_completer.complete(
49 used, matches = self.ipy_completer.complete(
50 line_buffer=document.current_line,
50 line_buffer=document.current_line,
51 cursor_pos=document.cursor_position_col
51 cursor_pos=document.cursor_position_col
52 )
52 )
53 start_pos = -len(used)
53 start_pos = -len(used)
54 for m in matches:
54 for m in matches:
55 m = unicodedata.normalize('NFC', m)
55 m = unicodedata.normalize('NFC', m)
56
56
57 # When the first character of the completion has a zero length,
57 # When the first character of the completion has a zero length,
58 # then it's probably a decomposed unicode character. E.g. caused by
58 # then it's probably a decomposed unicode character. E.g. caused by
59 # the "\dot" completion. Try to compose again with the previous
59 # the "\dot" completion. Try to compose again with the previous
60 # character.
60 # character.
61 if wcwidth(m[0]) == 0:
61 if wcwidth(m[0]) == 0:
62 if document.cursor_position + start_pos > 0:
62 if document.cursor_position + start_pos > 0:
63 char_before = document.text[document.cursor_position + start_pos - 1]
63 char_before = document.text[document.cursor_position + start_pos - 1]
64 m = unicodedata.normalize('NFC', char_before + m)
64 m = unicodedata.normalize('NFC', char_before + m)
65
65
66 # Yield the modified completion instead, if this worked.
66 # Yield the modified completion instead, if this worked.
67 if wcwidth(m[0:1]) == 1:
67 if wcwidth(m[0:1]) == 1:
68 yield Completion(m, start_position=start_pos - 1)
68 yield Completion(m, start_position=start_pos - 1)
69 continue
69 continue
70
70
71 yield Completion(m, start_position=start_pos)
71 yield Completion(m, start_position=start_pos)
72
72
73
73
74 class IPythonPTLexer(Lexer):
74 class IPythonPTLexer(Lexer):
75 """
75 """
76 Wrapper around PythonLexer and BashLexer.
76 Wrapper around PythonLexer and BashLexer.
77 """
77 """
78 def __init__(self):
78 def __init__(self):
79 self.python_lexer = PygmentsLexer(Python3Lexer if PY3 else PythonLexer)
79 self.python_lexer = PygmentsLexer(Python3Lexer if PY3 else PythonLexer)
80 self.shell_lexer = PygmentsLexer(BashLexer)
80 self.shell_lexer = PygmentsLexer(BashLexer)
81
81
82 def lex_document(self, cli, document):
82 def lex_document(self, cli, document):
83 if document.text.startswith('!'):
83 if document.text.startswith('!'):
84 return self.shell_lexer.lex_document(cli, document)
84 return self.shell_lexer.lex_document(cli, document)
85 else:
85 else:
86 return self.python_lexer.lex_document(cli, document)
86 return self.python_lexer.lex_document(cli, document)
87
87
88
88
89 class TerminalInteractiveShell(InteractiveShell):
89 class TerminalInteractiveShell(InteractiveShell):
90 colors_force = True
90 colors_force = True
91
91
92 pt_cli = None
92 pt_cli = None
93
93
94 autoedit_syntax = CBool(False, config=True,
94 autoedit_syntax = CBool(False, config=True,
95 help="auto editing of files with syntax errors.")
95 help="auto editing of files with syntax errors.")
96
96
97 confirm_exit = CBool(True, config=True,
97 confirm_exit = CBool(True, config=True,
98 help="""
98 help="""
99 Set to confirm when you try to exit IPython with an EOF (Control-D
99 Set to confirm when you try to exit IPython with an EOF (Control-D
100 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
100 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
101 you can force a direct exit without any confirmation.""",
101 you can force a direct exit without any confirmation.""",
102 )
102 )
103 vi_mode = Bool(False, config=True,
103 vi_mode = Bool(False, config=True,
104 help="Use vi style keybindings at the prompt",
104 help="Use vi style keybindings at the prompt",
105 )
105 )
106
106
107 mouse_support = Bool(False, config=True,
107 mouse_support = Bool(False, config=True,
108 help="Enable mouse support in the prompt"
108 help="Enable mouse support in the prompt"
109 )
109 )
110
110
111 highlighting_style = Unicode('', config=True,
111 highlighting_style = Unicode('', config=True,
112 help="The name of a Pygments style to use for syntax highlighting"
112 help="The name of a Pygments style to use for syntax highlighting"
113 )
113 )
114
114
115 highlighting_style_overrides = Dict(config=True,
115 highlighting_style_overrides = Dict(config=True,
116 help="Override highlighting format for specific tokens"
116 help="Override highlighting format for specific tokens"
117 )
117 )
118
118
119 editor = Unicode(get_default_editor(), config=True,
119 editor = Unicode(get_default_editor(), config=True,
120 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
120 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
121 )
121 )
122
122
123 term_title = Bool(True, config=True,
123 term_title = Bool(True, config=True,
124 help="Automatically set the terminal title"
124 help="Automatically set the terminal title"
125 )
125 )
126 def _term_title_changed(self, name, new_value):
126 def _term_title_changed(self, name, new_value):
127 self.init_term_title()
127 self.init_term_title()
128
128
129 def init_term_title(self):
129 def init_term_title(self):
130 # Enable or disable the terminal title.
130 # Enable or disable the terminal title.
131 if self.term_title:
131 if self.term_title:
132 toggle_set_term_title(True)
132 toggle_set_term_title(True)
133 set_term_title('IPython: ' + abbrev_cwd())
133 set_term_title('IPython: ' + abbrev_cwd())
134 else:
134 else:
135 toggle_set_term_title(False)
135 toggle_set_term_title(False)
136
136
137 def get_prompt_tokens(self, cli):
137 def get_prompt_tokens(self, cli):
138 return [
138 return [
139 (Token.Prompt, 'In ['),
139 (Token.Prompt, 'In ['),
140 (Token.PromptNum, str(self.execution_count)),
140 (Token.PromptNum, str(self.execution_count)),
141 (Token.Prompt, ']: '),
141 (Token.Prompt, ']: '),
142 ]
142 ]
143
143
144 def get_continuation_tokens(self, cli, width):
144 def get_continuation_tokens(self, cli, width):
145 return [
145 return [
146 (Token.Prompt, (' ' * (width - 5)) + '...: '),
146 (Token.Prompt, (' ' * (width - 5)) + '...: '),
147 ]
147 ]
148
148
149 def init_prompt_toolkit_cli(self):
149 def init_prompt_toolkit_cli(self):
150 if ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or not sys.stdin.isatty():
150 if ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or not sys.stdin.isatty():
151 # Fall back to plain non-interactive output for tests.
151 # Fall back to plain non-interactive output for tests.
152 # This is very limited, and only accepts a single line.
152 # This is very limited, and only accepts a single line.
153 def prompt():
153 def prompt():
154 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
154 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
155 self.prompt_for_code = prompt
155 self.prompt_for_code = prompt
156 return
156 return
157
157
158 kbmanager = KeyBindingManager.for_prompt(enable_vi_mode=self.vi_mode)
158 kbmanager = KeyBindingManager.for_prompt(enable_vi_mode=self.vi_mode)
159 insert_mode = ViStateFilter(kbmanager.get_vi_state, InputMode.INSERT)
159 insert_mode = ViStateFilter(kbmanager.get_vi_state, InputMode.INSERT)
160 # Ctrl+J == Enter, seemingly
160 # Ctrl+J == Enter, seemingly
161 @kbmanager.registry.add_binding(Keys.ControlJ,
161 @kbmanager.registry.add_binding(Keys.ControlJ,
162 filter=(HasFocus(DEFAULT_BUFFER)
162 filter=(HasFocus(DEFAULT_BUFFER)
163 & ~HasSelection()
163 & ~HasSelection()
164 & insert_mode
164 & insert_mode
165 ))
165 ))
166 def _(event):
166 def _(event):
167 b = event.current_buffer
167 b = event.current_buffer
168 d = b.document
168 d = b.document
169 if not (d.on_last_line or d.cursor_position_row >= d.line_count
169 if not (d.on_last_line or d.cursor_position_row >= d.line_count
170 - d.empty_line_count_at_the_end()):
170 - d.empty_line_count_at_the_end()):
171 b.newline()
171 b.newline()
172 return
172 return
173
173
174 status, indent = self.input_splitter.check_complete(d.text)
174 status, indent = self.input_splitter.check_complete(d.text)
175
175
176 if (status != 'incomplete') and b.accept_action.is_returnable:
176 if (status != 'incomplete') and b.accept_action.is_returnable:
177 b.accept_action.validate_and_handle(event.cli, b)
177 b.accept_action.validate_and_handle(event.cli, b)
178 else:
178 else:
179 b.insert_text('\n' + (' ' * (indent or 0)))
179 b.insert_text('\n' + (' ' * (indent or 0)))
180
180
181 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
181 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
182 def _(event):
182 def _(event):
183 event.current_buffer.reset()
183 event.current_buffer.reset()
184
184
185 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER))
186 def _(event):
187 if event.current_buffer.document.text:
188 event.current_buffer.reset()
189 else:
190 event.cli.push_focus(DEFAULT_BUFFER)
191
185 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
192 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
186
193
187 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
194 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
188 def _(event):
195 def _(event):
189 event.cli.suspend_to_background()
196 event.cli.suspend_to_background()
190
197
191 @Condition
198 @Condition
192 def cursor_in_leading_ws(cli):
199 def cursor_in_leading_ws(cli):
193 before = cli.application.buffer.document.current_line_before_cursor
200 before = cli.application.buffer.document.current_line_before_cursor
194 return (not before) or before.isspace()
201 return (not before) or before.isspace()
195
202
196 # Ctrl+I == Tab
203 # Ctrl+I == Tab
197 @kbmanager.registry.add_binding(Keys.ControlI,
204 @kbmanager.registry.add_binding(Keys.ControlI,
198 filter=(HasFocus(DEFAULT_BUFFER)
205 filter=(HasFocus(DEFAULT_BUFFER)
199 & ~HasSelection()
206 & ~HasSelection()
200 & insert_mode
207 & insert_mode
201 & cursor_in_leading_ws
208 & cursor_in_leading_ws
202 ))
209 ))
203 def _(event):
210 def _(event):
204 event.current_buffer.insert_text(' ' * 4)
211 event.current_buffer.insert_text(' ' * 4)
205
212
206 # Pre-populate history from IPython's history database
213 # Pre-populate history from IPython's history database
207 history = InMemoryHistory()
214 history = InMemoryHistory()
208 last_cell = u""
215 last_cell = u""
209 for _, _, cell in self.history_manager.get_tail(self.history_load_length,
216 for _, _, cell in self.history_manager.get_tail(self.history_load_length,
210 include_latest=True):
217 include_latest=True):
211 # Ignore blank lines and consecutive duplicates
218 # Ignore blank lines and consecutive duplicates
212 cell = cell.rstrip()
219 cell = cell.rstrip()
213 if cell and (cell != last_cell):
220 if cell and (cell != last_cell):
214 history.append(cell)
221 history.append(cell)
215
222
216 style_overrides = {
223 style_overrides = {
217 Token.Prompt: '#009900',
224 Token.Prompt: '#009900',
218 Token.PromptNum: '#00ff00 bold',
225 Token.PromptNum: '#00ff00 bold',
219 }
226 }
220 if self.highlighting_style:
227 if self.highlighting_style:
221 style_cls = get_style_by_name(self.highlighting_style)
228 style_cls = get_style_by_name(self.highlighting_style)
222 else:
229 else:
223 style_cls = get_style_by_name('default')
230 style_cls = get_style_by_name('default')
224 # The default theme needs to be visible on both a dark background
231 # The default theme needs to be visible on both a dark background
225 # and a light background, because we can't tell what the terminal
232 # and a light background, because we can't tell what the terminal
226 # looks like. These tweaks to the default theme help with that.
233 # looks like. These tweaks to the default theme help with that.
227 style_overrides.update({
234 style_overrides.update({
228 Token.Number: '#007700',
235 Token.Number: '#007700',
229 Token.Operator: 'noinherit',
236 Token.Operator: 'noinherit',
230 Token.String: '#BB6622',
237 Token.String: '#BB6622',
231 Token.Name.Function: '#2080D0',
238 Token.Name.Function: '#2080D0',
232 Token.Name.Class: 'bold #2080D0',
239 Token.Name.Class: 'bold #2080D0',
233 Token.Name.Namespace: 'bold #2080D0',
240 Token.Name.Namespace: 'bold #2080D0',
234 })
241 })
235 style_overrides.update(self.highlighting_style_overrides)
242 style_overrides.update(self.highlighting_style_overrides)
236 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
243 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
237 style_dict=style_overrides)
244 style_dict=style_overrides)
238
245
239 app = create_prompt_application(multiline=True,
246 app = create_prompt_application(multiline=True,
240 lexer=IPythonPTLexer(),
247 lexer=IPythonPTLexer(),
241 get_prompt_tokens=self.get_prompt_tokens,
248 get_prompt_tokens=self.get_prompt_tokens,
242 get_continuation_tokens=self.get_continuation_tokens,
249 get_continuation_tokens=self.get_continuation_tokens,
243 key_bindings_registry=kbmanager.registry,
250 key_bindings_registry=kbmanager.registry,
244 history=history,
251 history=history,
245 completer=IPythonPTCompleter(self.Completer),
252 completer=IPythonPTCompleter(self.Completer),
246 enable_history_search=True,
253 enable_history_search=True,
247 style=style,
254 style=style,
248 mouse_support=self.mouse_support,
255 mouse_support=self.mouse_support,
249 reserve_space_for_menu=6,
256 reserve_space_for_menu=6,
250 )
257 )
251
258
252 self.pt_cli = CommandLineInterface(app,
259 self.pt_cli = CommandLineInterface(app,
253 eventloop=create_eventloop(self.inputhook))
260 eventloop=create_eventloop(self.inputhook))
254
261
255 def prompt_for_code(self):
262 def prompt_for_code(self):
256 document = self.pt_cli.run(pre_run=self.pre_prompt)
263 document = self.pt_cli.run(pre_run=self.pre_prompt)
257 return document.text
264 return document.text
258
265
259 def init_io(self):
266 def init_io(self):
260 if sys.platform not in {'win32', 'cli'}:
267 if sys.platform not in {'win32', 'cli'}:
261 return
268 return
262
269
263 import colorama
270 import colorama
264 colorama.init()
271 colorama.init()
265
272
266 # For some reason we make these wrappers around stdout/stderr.
273 # For some reason we make these wrappers around stdout/stderr.
267 # For now, we need to reset them so all output gets coloured.
274 # For now, we need to reset them so all output gets coloured.
268 # https://github.com/ipython/ipython/issues/8669
275 # https://github.com/ipython/ipython/issues/8669
269 from IPython.utils import io
276 from IPython.utils import io
270 io.stdout = io.IOStream(sys.stdout)
277 io.stdout = io.IOStream(sys.stdout)
271 io.stderr = io.IOStream(sys.stderr)
278 io.stderr = io.IOStream(sys.stderr)
272
279
273 def init_magics(self):
280 def init_magics(self):
274 super(TerminalInteractiveShell, self).init_magics()
281 super(TerminalInteractiveShell, self).init_magics()
275 self.register_magics(TerminalMagics)
282 self.register_magics(TerminalMagics)
276
283
277 def init_alias(self):
284 def init_alias(self):
278 # The parent class defines aliases that can be safely used with any
285 # The parent class defines aliases that can be safely used with any
279 # frontend.
286 # frontend.
280 super(TerminalInteractiveShell, self).init_alias()
287 super(TerminalInteractiveShell, self).init_alias()
281
288
282 # Now define aliases that only make sense on the terminal, because they
289 # Now define aliases that only make sense on the terminal, because they
283 # need direct access to the console in a way that we can't emulate in
290 # need direct access to the console in a way that we can't emulate in
284 # GUI or web frontend
291 # GUI or web frontend
285 if os.name == 'posix':
292 if os.name == 'posix':
286 for cmd in ['clear', 'more', 'less', 'man']:
293 for cmd in ['clear', 'more', 'less', 'man']:
287 self.alias_manager.soft_define_alias(cmd, cmd)
294 self.alias_manager.soft_define_alias(cmd, cmd)
288
295
289
296
290 def __init__(self, *args, **kwargs):
297 def __init__(self, *args, **kwargs):
291 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
298 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
292 self.init_prompt_toolkit_cli()
299 self.init_prompt_toolkit_cli()
293 self.init_term_title()
300 self.init_term_title()
294 self.keep_running = True
301 self.keep_running = True
295
302
296 def ask_exit(self):
303 def ask_exit(self):
297 self.keep_running = False
304 self.keep_running = False
298
305
299 rl_next_input = None
306 rl_next_input = None
300
307
301 def pre_prompt(self):
308 def pre_prompt(self):
302 if self.rl_next_input:
309 if self.rl_next_input:
303 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
310 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
304 self.rl_next_input = None
311 self.rl_next_input = None
305
312
306 def interact(self):
313 def interact(self):
307 while self.keep_running:
314 while self.keep_running:
308 print(self.separate_in, end='')
315 print(self.separate_in, end='')
309
316
310 try:
317 try:
311 code = self.prompt_for_code()
318 code = self.prompt_for_code()
312 except EOFError:
319 except EOFError:
313 if (not self.confirm_exit) \
320 if (not self.confirm_exit) \
314 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
321 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
315 self.ask_exit()
322 self.ask_exit()
316
323
317 else:
324 else:
318 if code:
325 if code:
319 self.run_cell(code, store_history=True)
326 self.run_cell(code, store_history=True)
320 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
327 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
321 self.edit_syntax_error()
328 self.edit_syntax_error()
322
329
323 def mainloop(self):
330 def mainloop(self):
324 # An extra layer of protection in case someone mashing Ctrl-C breaks
331 # An extra layer of protection in case someone mashing Ctrl-C breaks
325 # out of our internal code.
332 # out of our internal code.
326 while True:
333 while True:
327 try:
334 try:
328 self.interact()
335 self.interact()
329 break
336 break
330 except KeyboardInterrupt:
337 except KeyboardInterrupt:
331 print("\nKeyboardInterrupt escaped interact()\n")
338 print("\nKeyboardInterrupt escaped interact()\n")
332
339
333 _inputhook = None
340 _inputhook = None
334 def inputhook(self, context):
341 def inputhook(self, context):
335 if self._inputhook is not None:
342 if self._inputhook is not None:
336 self._inputhook(context)
343 self._inputhook(context)
337
344
338 def enable_gui(self, gui=None):
345 def enable_gui(self, gui=None):
339 if gui:
346 if gui:
340 self._inputhook = get_inputhook_func(gui)
347 self._inputhook = get_inputhook_func(gui)
341 else:
348 else:
342 self._inputhook = None
349 self._inputhook = None
343
350
344 # Methods to support auto-editing of SyntaxErrors:
351 # Methods to support auto-editing of SyntaxErrors:
345
352
346 def edit_syntax_error(self):
353 def edit_syntax_error(self):
347 """The bottom half of the syntax error handler called in the main loop.
354 """The bottom half of the syntax error handler called in the main loop.
348
355
349 Loop until syntax error is fixed or user cancels.
356 Loop until syntax error is fixed or user cancels.
350 """
357 """
351
358
352 while self.SyntaxTB.last_syntax_error:
359 while self.SyntaxTB.last_syntax_error:
353 # copy and clear last_syntax_error
360 # copy and clear last_syntax_error
354 err = self.SyntaxTB.clear_err_state()
361 err = self.SyntaxTB.clear_err_state()
355 if not self._should_recompile(err):
362 if not self._should_recompile(err):
356 return
363 return
357 try:
364 try:
358 # may set last_syntax_error again if a SyntaxError is raised
365 # may set last_syntax_error again if a SyntaxError is raised
359 self.safe_execfile(err.filename, self.user_ns)
366 self.safe_execfile(err.filename, self.user_ns)
360 except:
367 except:
361 self.showtraceback()
368 self.showtraceback()
362 else:
369 else:
363 try:
370 try:
364 with open(err.filename) as f:
371 with open(err.filename) as f:
365 # This should be inside a display_trap block and I
372 # This should be inside a display_trap block and I
366 # think it is.
373 # think it is.
367 sys.displayhook(f.read())
374 sys.displayhook(f.read())
368 except:
375 except:
369 self.showtraceback()
376 self.showtraceback()
370
377
371 def _should_recompile(self, e):
378 def _should_recompile(self, e):
372 """Utility routine for edit_syntax_error"""
379 """Utility routine for edit_syntax_error"""
373
380
374 if e.filename in ('<ipython console>', '<input>', '<string>',
381 if e.filename in ('<ipython console>', '<input>', '<string>',
375 '<console>', '<BackgroundJob compilation>',
382 '<console>', '<BackgroundJob compilation>',
376 None):
383 None):
377 return False
384 return False
378 try:
385 try:
379 if (self.autoedit_syntax and
386 if (self.autoedit_syntax and
380 not self.ask_yes_no(
387 not self.ask_yes_no(
381 'Return to editor to correct syntax error? '
388 'Return to editor to correct syntax error? '
382 '[Y/n] ', 'y')):
389 '[Y/n] ', 'y')):
383 return False
390 return False
384 except EOFError:
391 except EOFError:
385 return False
392 return False
386
393
387 def int0(x):
394 def int0(x):
388 try:
395 try:
389 return int(x)
396 return int(x)
390 except TypeError:
397 except TypeError:
391 return 0
398 return 0
392
399
393 # always pass integer line and offset values to editor hook
400 # always pass integer line and offset values to editor hook
394 try:
401 try:
395 self.hooks.fix_error_editor(e.filename,
402 self.hooks.fix_error_editor(e.filename,
396 int0(e.lineno), int0(e.offset),
403 int0(e.lineno), int0(e.offset),
397 e.msg)
404 e.msg)
398 except TryNext:
405 except TryNext:
399 warn('Could not open editor')
406 warn('Could not open editor')
400 return False
407 return False
401 return True
408 return True
402
409
403 # Run !system commands directly, not through pipes, so terminal programs
410 # Run !system commands directly, not through pipes, so terminal programs
404 # work correctly.
411 # work correctly.
405 system = InteractiveShell.system_raw
412 system = InteractiveShell.system_raw
406
413
407
414
408 if __name__ == '__main__':
415 if __name__ == '__main__':
409 TerminalInteractiveShell.instance().interact()
416 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now