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