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