##// END OF EJS Templates
Eventloops are not created if IPY_TEST_SIMPLE_PROMPT set, or not a tty.
Matthias Bussonnier -
Show More
@@ -1,464 +1,465
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._eventloop = create_eventloop(self.inputhook)
254 self._eventloop = create_eventloop(self.inputhook)
255 self.pt_cli = CommandLineInterface(self._app, eventloop=self._eventloop)
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 if hasattr(self, '_eventloop'):
386 self._eventloop.close()
387 self._eventloop.close()
387
388
388 _inputhook = None
389 _inputhook = None
389 def inputhook(self, context):
390 def inputhook(self, context):
390 if self._inputhook is not None:
391 if self._inputhook is not None:
391 self._inputhook(context)
392 self._inputhook(context)
392
393
393 def enable_gui(self, gui=None):
394 def enable_gui(self, gui=None):
394 if gui:
395 if gui:
395 self._inputhook = get_inputhook_func(gui)
396 self._inputhook = get_inputhook_func(gui)
396 else:
397 else:
397 self._inputhook = None
398 self._inputhook = None
398
399
399 # Methods to support auto-editing of SyntaxErrors:
400 # Methods to support auto-editing of SyntaxErrors:
400
401
401 def edit_syntax_error(self):
402 def edit_syntax_error(self):
402 """The bottom half of the syntax error handler called in the main loop.
403 """The bottom half of the syntax error handler called in the main loop.
403
404
404 Loop until syntax error is fixed or user cancels.
405 Loop until syntax error is fixed or user cancels.
405 """
406 """
406
407
407 while self.SyntaxTB.last_syntax_error:
408 while self.SyntaxTB.last_syntax_error:
408 # copy and clear last_syntax_error
409 # copy and clear last_syntax_error
409 err = self.SyntaxTB.clear_err_state()
410 err = self.SyntaxTB.clear_err_state()
410 if not self._should_recompile(err):
411 if not self._should_recompile(err):
411 return
412 return
412 try:
413 try:
413 # may set last_syntax_error again if a SyntaxError is raised
414 # may set last_syntax_error again if a SyntaxError is raised
414 self.safe_execfile(err.filename, self.user_ns)
415 self.safe_execfile(err.filename, self.user_ns)
415 except:
416 except:
416 self.showtraceback()
417 self.showtraceback()
417 else:
418 else:
418 try:
419 try:
419 with open(err.filename) as f:
420 with open(err.filename) as f:
420 # This should be inside a display_trap block and I
421 # This should be inside a display_trap block and I
421 # think it is.
422 # think it is.
422 sys.displayhook(f.read())
423 sys.displayhook(f.read())
423 except:
424 except:
424 self.showtraceback()
425 self.showtraceback()
425
426
426 def _should_recompile(self, e):
427 def _should_recompile(self, e):
427 """Utility routine for edit_syntax_error"""
428 """Utility routine for edit_syntax_error"""
428
429
429 if e.filename in ('<ipython console>', '<input>', '<string>',
430 if e.filename in ('<ipython console>', '<input>', '<string>',
430 '<console>', '<BackgroundJob compilation>',
431 '<console>', '<BackgroundJob compilation>',
431 None):
432 None):
432 return False
433 return False
433 try:
434 try:
434 if (self.autoedit_syntax and
435 if (self.autoedit_syntax and
435 not self.ask_yes_no(
436 not self.ask_yes_no(
436 'Return to editor to correct syntax error? '
437 'Return to editor to correct syntax error? '
437 '[Y/n] ', 'y')):
438 '[Y/n] ', 'y')):
438 return False
439 return False
439 except EOFError:
440 except EOFError:
440 return False
441 return False
441
442
442 def int0(x):
443 def int0(x):
443 try:
444 try:
444 return int(x)
445 return int(x)
445 except TypeError:
446 except TypeError:
446 return 0
447 return 0
447
448
448 # always pass integer line and offset values to editor hook
449 # always pass integer line and offset values to editor hook
449 try:
450 try:
450 self.hooks.fix_error_editor(e.filename,
451 self.hooks.fix_error_editor(e.filename,
451 int0(e.lineno), int0(e.offset),
452 int0(e.lineno), int0(e.offset),
452 e.msg)
453 e.msg)
453 except TryNext:
454 except TryNext:
454 warn('Could not open editor')
455 warn('Could not open editor')
455 return False
456 return False
456 return True
457 return True
457
458
458 # Run !system commands directly, not through pipes, so terminal programs
459 # Run !system commands directly, not through pipes, so terminal programs
459 # work correctly.
460 # work correctly.
460 system = InteractiveShell.system_raw
461 system = InteractiveShell.system_raw
461
462
462
463
463 if __name__ == '__main__':
464 if __name__ == '__main__':
464 TerminalInteractiveShell.instance().interact()
465 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now