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