##// END OF EJS Templates
Merge pull request #9423 from Carreau/reconfigurable...
Thomas Kluyver -
r22281:28cf805f merge
parent child Browse files
Show More
@@ -1,416 +1,451 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, Integer
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, SEARCH_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, create_prompt_layout
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, DynamicStyle
31
31
32 from pygments.styles import get_style_by_name
32 from pygments.styles import get_style_by_name, get_all_styles
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 space_for_menu = Integer(6, config=True, help='Number of line at the bottom of the screen '
93 'to reserve for the completion menu')
94
95 def _space_for_menu_changed(self, old, new):
96 self._update_layout()
97
92 pt_cli = None
98 pt_cli = None
93
99
94 autoedit_syntax = CBool(False, config=True,
100 autoedit_syntax = CBool(False, config=True,
95 help="auto editing of files with syntax errors.")
101 help="auto editing of files with syntax errors.")
96
102
97 confirm_exit = CBool(True, config=True,
103 confirm_exit = CBool(True, config=True,
98 help="""
104 help="""
99 Set to confirm when you try to exit IPython with an EOF (Control-D
105 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',
106 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
101 you can force a direct exit without any confirmation.""",
107 you can force a direct exit without any confirmation.""",
102 )
108 )
103 vi_mode = Bool(False, config=True,
109 vi_mode = Bool(False, config=True,
104 help="Use vi style keybindings at the prompt",
110 help="Use vi style keybindings at the prompt",
105 )
111 )
106
112
107 mouse_support = Bool(False, config=True,
113 mouse_support = Bool(False, config=True,
108 help="Enable mouse support in the prompt"
114 help="Enable mouse support in the prompt"
109 )
115 )
110
116
111 highlighting_style = Unicode('', config=True,
117 highlighting_style = Unicode('default', config=True,
112 help="The name of a Pygments style to use for syntax highlighting"
118 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
113 )
119 )
114
120
121 def _highlighting_style_changed(self, old, new):
122 self._style = self._make_style_from_name(self.highlighting_style)
123
115 highlighting_style_overrides = Dict(config=True,
124 highlighting_style_overrides = Dict(config=True,
116 help="Override highlighting format for specific tokens"
125 help="Override highlighting format for specific tokens"
117 )
126 )
118
127
119 editor = Unicode(get_default_editor(), config=True,
128 editor = Unicode(get_default_editor(), config=True,
120 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
129 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
121 )
130 )
122
131
123 term_title = Bool(True, config=True,
132 term_title = Bool(True, config=True,
124 help="Automatically set the terminal title"
133 help="Automatically set the terminal title"
125 )
134 )
126 def _term_title_changed(self, name, new_value):
135 def _term_title_changed(self, name, new_value):
127 self.init_term_title()
136 self.init_term_title()
128
137
129 def init_term_title(self):
138 def init_term_title(self):
130 # Enable or disable the terminal title.
139 # Enable or disable the terminal title.
131 if self.term_title:
140 if self.term_title:
132 toggle_set_term_title(True)
141 toggle_set_term_title(True)
133 set_term_title('IPython: ' + abbrev_cwd())
142 set_term_title('IPython: ' + abbrev_cwd())
134 else:
143 else:
135 toggle_set_term_title(False)
144 toggle_set_term_title(False)
136
145
137 def get_prompt_tokens(self, cli):
146 def get_prompt_tokens(self, cli):
138 return [
147 return [
139 (Token.Prompt, 'In ['),
148 (Token.Prompt, 'In ['),
140 (Token.PromptNum, str(self.execution_count)),
149 (Token.PromptNum, str(self.execution_count)),
141 (Token.Prompt, ']: '),
150 (Token.Prompt, ']: '),
142 ]
151 ]
143
152
144 def get_continuation_tokens(self, cli, width):
153 def get_continuation_tokens(self, cli, width):
145 return [
154 return [
146 (Token.Prompt, (' ' * (width - 5)) + '...: '),
155 (Token.Prompt, (' ' * (width - 5)) + '...: '),
147 ]
156 ]
148
157
149 def init_prompt_toolkit_cli(self):
158 def init_prompt_toolkit_cli(self):
150 if ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or not sys.stdin.isatty():
159 if ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or not sys.stdin.isatty():
151 # Fall back to plain non-interactive output for tests.
160 # Fall back to plain non-interactive output for tests.
152 # This is very limited, and only accepts a single line.
161 # This is very limited, and only accepts a single line.
153 def prompt():
162 def prompt():
154 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
163 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
155 self.prompt_for_code = prompt
164 self.prompt_for_code = prompt
156 return
165 return
157
166
158 kbmanager = KeyBindingManager.for_prompt(enable_vi_mode=self.vi_mode)
167 kbmanager = KeyBindingManager.for_prompt(enable_vi_mode=self.vi_mode)
159 insert_mode = ViStateFilter(kbmanager.get_vi_state, InputMode.INSERT)
168 insert_mode = ViStateFilter(kbmanager.get_vi_state, InputMode.INSERT)
160 # Ctrl+J == Enter, seemingly
169 # Ctrl+J == Enter, seemingly
161 @kbmanager.registry.add_binding(Keys.ControlJ,
170 @kbmanager.registry.add_binding(Keys.ControlJ,
162 filter=(HasFocus(DEFAULT_BUFFER)
171 filter=(HasFocus(DEFAULT_BUFFER)
163 & ~HasSelection()
172 & ~HasSelection()
164 & insert_mode
173 & insert_mode
165 ))
174 ))
166 def _(event):
175 def _(event):
167 b = event.current_buffer
176 b = event.current_buffer
168 d = b.document
177 d = b.document
169 if not (d.on_last_line or d.cursor_position_row >= d.line_count
178 if not (d.on_last_line or d.cursor_position_row >= d.line_count
170 - d.empty_line_count_at_the_end()):
179 - d.empty_line_count_at_the_end()):
171 b.newline()
180 b.newline()
172 return
181 return
173
182
174 status, indent = self.input_splitter.check_complete(d.text)
183 status, indent = self.input_splitter.check_complete(d.text)
175
184
176 if (status != 'incomplete') and b.accept_action.is_returnable:
185 if (status != 'incomplete') and b.accept_action.is_returnable:
177 b.accept_action.validate_and_handle(event.cli, b)
186 b.accept_action.validate_and_handle(event.cli, b)
178 else:
187 else:
179 b.insert_text('\n' + (' ' * (indent or 0)))
188 b.insert_text('\n' + (' ' * (indent or 0)))
180
189
181 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
190 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
182 def _(event):
191 def _(event):
183 event.current_buffer.reset()
192 event.current_buffer.reset()
184
193
185 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER))
194 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER))
186 def _(event):
195 def _(event):
187 if event.current_buffer.document.text:
196 if event.current_buffer.document.text:
188 event.current_buffer.reset()
197 event.current_buffer.reset()
189 else:
198 else:
190 event.cli.push_focus(DEFAULT_BUFFER)
199 event.cli.push_focus(DEFAULT_BUFFER)
191
200
192 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
201 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
193
202
194 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
203 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
195 def _(event):
204 def _(event):
196 event.cli.suspend_to_background()
205 event.cli.suspend_to_background()
197
206
198 @Condition
207 @Condition
199 def cursor_in_leading_ws(cli):
208 def cursor_in_leading_ws(cli):
200 before = cli.application.buffer.document.current_line_before_cursor
209 before = cli.application.buffer.document.current_line_before_cursor
201 return (not before) or before.isspace()
210 return (not before) or before.isspace()
202
211
203 # Ctrl+I == Tab
212 # Ctrl+I == Tab
204 @kbmanager.registry.add_binding(Keys.ControlI,
213 @kbmanager.registry.add_binding(Keys.ControlI,
205 filter=(HasFocus(DEFAULT_BUFFER)
214 filter=(HasFocus(DEFAULT_BUFFER)
206 & ~HasSelection()
215 & ~HasSelection()
207 & insert_mode
216 & insert_mode
208 & cursor_in_leading_ws
217 & cursor_in_leading_ws
209 ))
218 ))
210 def _(event):
219 def _(event):
211 event.current_buffer.insert_text(' ' * 4)
220 event.current_buffer.insert_text(' ' * 4)
212
221
213 # Pre-populate history from IPython's history database
222 # Pre-populate history from IPython's history database
214 history = InMemoryHistory()
223 history = InMemoryHistory()
215 last_cell = u""
224 last_cell = u""
216 for _, _, cell in self.history_manager.get_tail(self.history_load_length,
225 for _, _, cell in self.history_manager.get_tail(self.history_load_length,
217 include_latest=True):
226 include_latest=True):
218 # Ignore blank lines and consecutive duplicates
227 # Ignore blank lines and consecutive duplicates
219 cell = cell.rstrip()
228 cell = cell.rstrip()
220 if cell and (cell != last_cell):
229 if cell and (cell != last_cell):
221 history.append(cell)
230 history.append(cell)
222
231
232 self._style = self._make_style_from_name(self.highlighting_style)
233 style = DynamicStyle(lambda: self._style)
234
235 self._app = create_prompt_application(
236 key_bindings_registry=kbmanager.registry,
237 history=history,
238 completer=IPythonPTCompleter(self.Completer),
239 enable_history_search=True,
240 style=style,
241 mouse_support=self.mouse_support,
242 **self._layout_options()
243 )
244 self.pt_cli = CommandLineInterface(self._app,
245 eventloop=create_eventloop(self.inputhook))
246
247 def _make_style_from_name(self, name):
248 """
249 Small wrapper that make an IPython compatible style from a style name
250
251 We need that to add style for prompt ... etc.
252 """
253 style_cls = get_style_by_name(name)
223 style_overrides = {
254 style_overrides = {
224 Token.Prompt: '#009900',
255 Token.Prompt: style_cls.styles.get( Token.Keyword, '#009900'),
225 Token.PromptNum: '#00ff00 bold',
256 Token.PromptNum: style_cls.styles.get( Token.Literal.Number, '#00ff00 bold')
226 }
257 }
227 if self.highlighting_style:
258 if name is 'default':
228 style_cls = get_style_by_name(self.highlighting_style)
229 else:
230 style_cls = get_style_by_name('default')
259 style_cls = get_style_by_name('default')
231 # The default theme needs to be visible on both a dark background
260 # The default theme needs to be visible on both a dark background
232 # and a light background, because we can't tell what the terminal
261 # and a light background, because we can't tell what the terminal
233 # looks like. These tweaks to the default theme help with that.
262 # looks like. These tweaks to the default theme help with that.
234 style_overrides.update({
263 style_overrides.update({
235 Token.Number: '#007700',
264 Token.Number: '#007700',
236 Token.Operator: 'noinherit',
265 Token.Operator: 'noinherit',
237 Token.String: '#BB6622',
266 Token.String: '#BB6622',
238 Token.Name.Function: '#2080D0',
267 Token.Name.Function: '#2080D0',
239 Token.Name.Class: 'bold #2080D0',
268 Token.Name.Class: 'bold #2080D0',
240 Token.Name.Namespace: 'bold #2080D0',
269 Token.Name.Namespace: 'bold #2080D0',
241 })
270 })
242 style_overrides.update(self.highlighting_style_overrides)
271 style_overrides.update(self.highlighting_style_overrides)
243 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
272 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
244 style_dict=style_overrides)
273 style_dict=style_overrides)
245
274
246 app = create_prompt_application(multiline=True,
275 return style
247 lexer=IPythonPTLexer(),
248 get_prompt_tokens=self.get_prompt_tokens,
249 get_continuation_tokens=self.get_continuation_tokens,
250 key_bindings_registry=kbmanager.registry,
251 history=history,
252 completer=IPythonPTCompleter(self.Completer),
253 enable_history_search=True,
254 style=style,
255 mouse_support=self.mouse_support,
256 reserve_space_for_menu=6,
257 )
258
276
259 self.pt_cli = CommandLineInterface(app,
277 def _layout_options(self):
260 eventloop=create_eventloop(self.inputhook))
278 """
279 Return the current layout option for the current Terminal InteractiveShell
280 """
281 return {
282 'lexer':IPythonPTLexer(),
283 'reserve_space_for_menu':self.space_for_menu,
284 'get_prompt_tokens':self.get_prompt_tokens,
285 'get_continuation_tokens':self.get_continuation_tokens,
286 'multiline':False,
287 }
288
289
290 def _update_layout(self):
291 """
292 Ask for a re computation of the application layout, if for example ,
293 some configuration options have changed.
294 """
295 self._app.layout = create_prompt_layout(**self._layout_options())
261
296
262 def prompt_for_code(self):
297 def prompt_for_code(self):
263 document = self.pt_cli.run(pre_run=self.pre_prompt)
298 document = self.pt_cli.run(pre_run=self.pre_prompt)
264 return document.text
299 return document.text
265
300
266 def init_io(self):
301 def init_io(self):
267 if sys.platform not in {'win32', 'cli'}:
302 if sys.platform not in {'win32', 'cli'}:
268 return
303 return
269
304
270 import colorama
305 import colorama
271 colorama.init()
306 colorama.init()
272
307
273 # For some reason we make these wrappers around stdout/stderr.
308 # For some reason we make these wrappers around stdout/stderr.
274 # For now, we need to reset them so all output gets coloured.
309 # For now, we need to reset them so all output gets coloured.
275 # https://github.com/ipython/ipython/issues/8669
310 # https://github.com/ipython/ipython/issues/8669
276 from IPython.utils import io
311 from IPython.utils import io
277 io.stdout = io.IOStream(sys.stdout)
312 io.stdout = io.IOStream(sys.stdout)
278 io.stderr = io.IOStream(sys.stderr)
313 io.stderr = io.IOStream(sys.stderr)
279
314
280 def init_magics(self):
315 def init_magics(self):
281 super(TerminalInteractiveShell, self).init_magics()
316 super(TerminalInteractiveShell, self).init_magics()
282 self.register_magics(TerminalMagics)
317 self.register_magics(TerminalMagics)
283
318
284 def init_alias(self):
319 def init_alias(self):
285 # The parent class defines aliases that can be safely used with any
320 # The parent class defines aliases that can be safely used with any
286 # frontend.
321 # frontend.
287 super(TerminalInteractiveShell, self).init_alias()
322 super(TerminalInteractiveShell, self).init_alias()
288
323
289 # Now define aliases that only make sense on the terminal, because they
324 # Now define aliases that only make sense on the terminal, because they
290 # need direct access to the console in a way that we can't emulate in
325 # need direct access to the console in a way that we can't emulate in
291 # GUI or web frontend
326 # GUI or web frontend
292 if os.name == 'posix':
327 if os.name == 'posix':
293 for cmd in ['clear', 'more', 'less', 'man']:
328 for cmd in ['clear', 'more', 'less', 'man']:
294 self.alias_manager.soft_define_alias(cmd, cmd)
329 self.alias_manager.soft_define_alias(cmd, cmd)
295
330
296
331
297 def __init__(self, *args, **kwargs):
332 def __init__(self, *args, **kwargs):
298 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
333 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
299 self.init_prompt_toolkit_cli()
334 self.init_prompt_toolkit_cli()
300 self.init_term_title()
335 self.init_term_title()
301 self.keep_running = True
336 self.keep_running = True
302
337
303 def ask_exit(self):
338 def ask_exit(self):
304 self.keep_running = False
339 self.keep_running = False
305
340
306 rl_next_input = None
341 rl_next_input = None
307
342
308 def pre_prompt(self):
343 def pre_prompt(self):
309 if self.rl_next_input:
344 if self.rl_next_input:
310 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
345 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
311 self.rl_next_input = None
346 self.rl_next_input = None
312
347
313 def interact(self):
348 def interact(self):
314 while self.keep_running:
349 while self.keep_running:
315 print(self.separate_in, end='')
350 print(self.separate_in, end='')
316
351
317 try:
352 try:
318 code = self.prompt_for_code()
353 code = self.prompt_for_code()
319 except EOFError:
354 except EOFError:
320 if (not self.confirm_exit) \
355 if (not self.confirm_exit) \
321 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
356 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
322 self.ask_exit()
357 self.ask_exit()
323
358
324 else:
359 else:
325 if code:
360 if code:
326 self.run_cell(code, store_history=True)
361 self.run_cell(code, store_history=True)
327 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
362 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
328 self.edit_syntax_error()
363 self.edit_syntax_error()
329
364
330 def mainloop(self):
365 def mainloop(self):
331 # An extra layer of protection in case someone mashing Ctrl-C breaks
366 # An extra layer of protection in case someone mashing Ctrl-C breaks
332 # out of our internal code.
367 # out of our internal code.
333 while True:
368 while True:
334 try:
369 try:
335 self.interact()
370 self.interact()
336 break
371 break
337 except KeyboardInterrupt:
372 except KeyboardInterrupt:
338 print("\nKeyboardInterrupt escaped interact()\n")
373 print("\nKeyboardInterrupt escaped interact()\n")
339
374
340 _inputhook = None
375 _inputhook = None
341 def inputhook(self, context):
376 def inputhook(self, context):
342 if self._inputhook is not None:
377 if self._inputhook is not None:
343 self._inputhook(context)
378 self._inputhook(context)
344
379
345 def enable_gui(self, gui=None):
380 def enable_gui(self, gui=None):
346 if gui:
381 if gui:
347 self._inputhook = get_inputhook_func(gui)
382 self._inputhook = get_inputhook_func(gui)
348 else:
383 else:
349 self._inputhook = None
384 self._inputhook = None
350
385
351 # Methods to support auto-editing of SyntaxErrors:
386 # Methods to support auto-editing of SyntaxErrors:
352
387
353 def edit_syntax_error(self):
388 def edit_syntax_error(self):
354 """The bottom half of the syntax error handler called in the main loop.
389 """The bottom half of the syntax error handler called in the main loop.
355
390
356 Loop until syntax error is fixed or user cancels.
391 Loop until syntax error is fixed or user cancels.
357 """
392 """
358
393
359 while self.SyntaxTB.last_syntax_error:
394 while self.SyntaxTB.last_syntax_error:
360 # copy and clear last_syntax_error
395 # copy and clear last_syntax_error
361 err = self.SyntaxTB.clear_err_state()
396 err = self.SyntaxTB.clear_err_state()
362 if not self._should_recompile(err):
397 if not self._should_recompile(err):
363 return
398 return
364 try:
399 try:
365 # may set last_syntax_error again if a SyntaxError is raised
400 # may set last_syntax_error again if a SyntaxError is raised
366 self.safe_execfile(err.filename, self.user_ns)
401 self.safe_execfile(err.filename, self.user_ns)
367 except:
402 except:
368 self.showtraceback()
403 self.showtraceback()
369 else:
404 else:
370 try:
405 try:
371 with open(err.filename) as f:
406 with open(err.filename) as f:
372 # This should be inside a display_trap block and I
407 # This should be inside a display_trap block and I
373 # think it is.
408 # think it is.
374 sys.displayhook(f.read())
409 sys.displayhook(f.read())
375 except:
410 except:
376 self.showtraceback()
411 self.showtraceback()
377
412
378 def _should_recompile(self, e):
413 def _should_recompile(self, e):
379 """Utility routine for edit_syntax_error"""
414 """Utility routine for edit_syntax_error"""
380
415
381 if e.filename in ('<ipython console>', '<input>', '<string>',
416 if e.filename in ('<ipython console>', '<input>', '<string>',
382 '<console>', '<BackgroundJob compilation>',
417 '<console>', '<BackgroundJob compilation>',
383 None):
418 None):
384 return False
419 return False
385 try:
420 try:
386 if (self.autoedit_syntax and
421 if (self.autoedit_syntax and
387 not self.ask_yes_no(
422 not self.ask_yes_no(
388 'Return to editor to correct syntax error? '
423 'Return to editor to correct syntax error? '
389 '[Y/n] ', 'y')):
424 '[Y/n] ', 'y')):
390 return False
425 return False
391 except EOFError:
426 except EOFError:
392 return False
427 return False
393
428
394 def int0(x):
429 def int0(x):
395 try:
430 try:
396 return int(x)
431 return int(x)
397 except TypeError:
432 except TypeError:
398 return 0
433 return 0
399
434
400 # always pass integer line and offset values to editor hook
435 # always pass integer line and offset values to editor hook
401 try:
436 try:
402 self.hooks.fix_error_editor(e.filename,
437 self.hooks.fix_error_editor(e.filename,
403 int0(e.lineno), int0(e.offset),
438 int0(e.lineno), int0(e.offset),
404 e.msg)
439 e.msg)
405 except TryNext:
440 except TryNext:
406 warn('Could not open editor')
441 warn('Could not open editor')
407 return False
442 return False
408 return True
443 return True
409
444
410 # Run !system commands directly, not through pipes, so terminal programs
445 # Run !system commands directly, not through pipes, so terminal programs
411 # work correctly.
446 # work correctly.
412 system = InteractiveShell.system_raw
447 system = InteractiveShell.system_raw
413
448
414
449
415 if __name__ == '__main__':
450 if __name__ == '__main__':
416 TerminalInteractiveShell.instance().interact()
451 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now