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