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