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