##// END OF EJS Templates
give PTCompleter InteractiveShell, not Completer...
Min RK -
Show More
@@ -1,488 +1,488 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
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 warnings
6 import warnings
7 from warnings import warn
7 from warnings import warn
8
8
9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 from IPython.utils import io
10 from IPython.utils import io
11 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
11 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title
13 from IPython.utils.process import abbrev_cwd
13 from IPython.utils.process import abbrev_cwd
14 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum
14 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum
15
15
16 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
16 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
17 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
17 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
18 from prompt_toolkit.history import InMemoryHistory
18 from prompt_toolkit.history import InMemoryHistory
19 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
19 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
20 from prompt_toolkit.interface import CommandLineInterface
20 from prompt_toolkit.interface import CommandLineInterface
21 from prompt_toolkit.key_binding.manager import KeyBindingManager
21 from prompt_toolkit.key_binding.manager import KeyBindingManager
22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
23 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
24
24
25 from pygments.styles import get_style_by_name, get_all_styles
25 from pygments.styles import get_style_by_name, get_all_styles
26 from pygments.token import Token
26 from pygments.token import Token
27
27
28 from .debugger import TerminalPdb, Pdb
28 from .debugger import TerminalPdb, Pdb
29 from .magics import TerminalMagics
29 from .magics import TerminalMagics
30 from .pt_inputhooks import get_inputhook_func
30 from .pt_inputhooks import get_inputhook_func
31 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
31 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
32 from .ptutils import IPythonPTCompleter, IPythonPTLexer
32 from .ptutils import IPythonPTCompleter, IPythonPTLexer
33 from .shortcuts import register_ipython_shortcuts
33 from .shortcuts import register_ipython_shortcuts
34
34
35 DISPLAY_BANNER_DEPRECATED = object()
35 DISPLAY_BANNER_DEPRECATED = object()
36
36
37
37
38 from pygments.style import Style
38 from pygments.style import Style
39
39
40 class _NoStyle(Style): pass
40 class _NoStyle(Style): pass
41
41
42
42
43
43
44 _style_overrides_light_bg = {
44 _style_overrides_light_bg = {
45 Token.Prompt: '#0000ff',
45 Token.Prompt: '#0000ff',
46 Token.PromptNum: '#0000ee bold',
46 Token.PromptNum: '#0000ee bold',
47 Token.OutPrompt: '#cc0000',
47 Token.OutPrompt: '#cc0000',
48 Token.OutPromptNum: '#bb0000 bold',
48 Token.OutPromptNum: '#bb0000 bold',
49 }
49 }
50
50
51 _style_overrides_linux = {
51 _style_overrides_linux = {
52 Token.Prompt: '#00cc00',
52 Token.Prompt: '#00cc00',
53 Token.PromptNum: '#00bb00 bold',
53 Token.PromptNum: '#00bb00 bold',
54 Token.OutPrompt: '#cc0000',
54 Token.OutPrompt: '#cc0000',
55 Token.OutPromptNum: '#bb0000 bold',
55 Token.OutPromptNum: '#bb0000 bold',
56 }
56 }
57
57
58
58
59
59
60 def get_default_editor():
60 def get_default_editor():
61 try:
61 try:
62 ed = os.environ['EDITOR']
62 ed = os.environ['EDITOR']
63 if not PY3:
63 if not PY3:
64 ed = ed.decode()
64 ed = ed.decode()
65 return ed
65 return ed
66 except KeyError:
66 except KeyError:
67 pass
67 pass
68 except UnicodeError:
68 except UnicodeError:
69 warn("$EDITOR environment variable is not pure ASCII. Using platform "
69 warn("$EDITOR environment variable is not pure ASCII. Using platform "
70 "default editor.")
70 "default editor.")
71
71
72 if os.name == 'posix':
72 if os.name == 'posix':
73 return 'vi' # the only one guaranteed to be there!
73 return 'vi' # the only one guaranteed to be there!
74 else:
74 else:
75 return 'notepad' # same in Windows!
75 return 'notepad' # same in Windows!
76
76
77 # conservatively check for tty
77 # conservatively check for tty
78 # overridden streams can result in things like:
78 # overridden streams can result in things like:
79 # - sys.stdin = None
79 # - sys.stdin = None
80 # - no isatty method
80 # - no isatty method
81 for _name in ('stdin', 'stdout', 'stderr'):
81 for _name in ('stdin', 'stdout', 'stderr'):
82 _stream = getattr(sys, _name)
82 _stream = getattr(sys, _name)
83 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
83 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
84 _is_tty = False
84 _is_tty = False
85 break
85 break
86 else:
86 else:
87 _is_tty = True
87 _is_tty = True
88
88
89
89
90 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
90 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
91
91
92 class TerminalInteractiveShell(InteractiveShell):
92 class TerminalInteractiveShell(InteractiveShell):
93 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
93 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
94 'to reserve for the completion menu'
94 'to reserve for the completion menu'
95 ).tag(config=True)
95 ).tag(config=True)
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 debugger_history = None
101 debugger_history = None
102 _pt_app = None
102 _pt_app = None
103
103
104 simple_prompt = Bool(_use_simple_prompt,
104 simple_prompt = Bool(_use_simple_prompt,
105 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
105 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
106
106
107 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
107 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
108 IPython own testing machinery, and emacs inferior-shell integration through elpy.
108 IPython own testing machinery, and emacs inferior-shell integration through elpy.
109
109
110 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
110 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
111 environment variable is set, or the current terminal is not a tty.
111 environment variable is set, or the current terminal is not a tty.
112
112
113 """
113 """
114 ).tag(config=True)
114 ).tag(config=True)
115
115
116 @property
116 @property
117 def debugger_cls(self):
117 def debugger_cls(self):
118 return Pdb if self.simple_prompt else TerminalPdb
118 return Pdb if self.simple_prompt else TerminalPdb
119
119
120 confirm_exit = Bool(True,
120 confirm_exit = Bool(True,
121 help="""
121 help="""
122 Set to confirm when you try to exit IPython with an EOF (Control-D
122 Set to confirm when you try to exit IPython with an EOF (Control-D
123 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
123 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
124 you can force a direct exit without any confirmation.""",
124 you can force a direct exit without any confirmation.""",
125 ).tag(config=True)
125 ).tag(config=True)
126
126
127 editing_mode = Unicode('emacs',
127 editing_mode = Unicode('emacs',
128 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
128 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
129 ).tag(config=True)
129 ).tag(config=True)
130
130
131 mouse_support = Bool(False,
131 mouse_support = Bool(False,
132 help="Enable mouse support in the prompt"
132 help="Enable mouse support in the prompt"
133 ).tag(config=True)
133 ).tag(config=True)
134
134
135 highlighting_style = Unicode('legacy',
135 highlighting_style = Unicode('legacy',
136 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
136 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
137 ).tag(config=True)
137 ).tag(config=True)
138
138
139
139
140 @observe('highlighting_style')
140 @observe('highlighting_style')
141 @observe('colors')
141 @observe('colors')
142 def _highlighting_style_changed(self, change):
142 def _highlighting_style_changed(self, change):
143 self.refresh_style()
143 self.refresh_style()
144
144
145 def refresh_style(self):
145 def refresh_style(self):
146 self._style = self._make_style_from_name(self.highlighting_style)
146 self._style = self._make_style_from_name(self.highlighting_style)
147
147
148
148
149 highlighting_style_overrides = Dict(
149 highlighting_style_overrides = Dict(
150 help="Override highlighting format for specific tokens"
150 help="Override highlighting format for specific tokens"
151 ).tag(config=True)
151 ).tag(config=True)
152
152
153 true_color = Bool(False,
153 true_color = Bool(False,
154 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
154 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
155 "If your terminal supports true color, the following command "
155 "If your terminal supports true color, the following command "
156 "should print 'TRUECOLOR' in orange: "
156 "should print 'TRUECOLOR' in orange: "
157 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
157 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
158 ).tag(config=True)
158 ).tag(config=True)
159
159
160 editor = Unicode(get_default_editor(),
160 editor = Unicode(get_default_editor(),
161 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
161 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
162 ).tag(config=True)
162 ).tag(config=True)
163
163
164 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
164 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
165
165
166 prompts = Instance(Prompts)
166 prompts = Instance(Prompts)
167
167
168 @default('prompts')
168 @default('prompts')
169 def _prompts_default(self):
169 def _prompts_default(self):
170 return self.prompts_class(self)
170 return self.prompts_class(self)
171
171
172 @observe('prompts')
172 @observe('prompts')
173 def _(self, change):
173 def _(self, change):
174 self._update_layout()
174 self._update_layout()
175
175
176 @default('displayhook_class')
176 @default('displayhook_class')
177 def _displayhook_class_default(self):
177 def _displayhook_class_default(self):
178 return RichPromptDisplayHook
178 return RichPromptDisplayHook
179
179
180 term_title = Bool(True,
180 term_title = Bool(True,
181 help="Automatically set the terminal title"
181 help="Automatically set the terminal title"
182 ).tag(config=True)
182 ).tag(config=True)
183
183
184 display_completions = Enum(('column', 'multicolumn','readlinelike'),
184 display_completions = Enum(('column', 'multicolumn','readlinelike'),
185 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
185 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
186 "'readlinelike'. These options are for `prompt_toolkit`, see "
186 "'readlinelike'. These options are for `prompt_toolkit`, see "
187 "`prompt_toolkit` documentation for more information."
187 "`prompt_toolkit` documentation for more information."
188 ),
188 ),
189 default_value='multicolumn').tag(config=True)
189 default_value='multicolumn').tag(config=True)
190
190
191 highlight_matching_brackets = Bool(True,
191 highlight_matching_brackets = Bool(True,
192 help="Highlight matching brackets .",
192 help="Highlight matching brackets .",
193 ).tag(config=True)
193 ).tag(config=True)
194
194
195 @observe('term_title')
195 @observe('term_title')
196 def init_term_title(self, change=None):
196 def init_term_title(self, change=None):
197 # Enable or disable the terminal title.
197 # Enable or disable the terminal title.
198 if self.term_title:
198 if self.term_title:
199 toggle_set_term_title(True)
199 toggle_set_term_title(True)
200 set_term_title('IPython: ' + abbrev_cwd())
200 set_term_title('IPython: ' + abbrev_cwd())
201 else:
201 else:
202 toggle_set_term_title(False)
202 toggle_set_term_title(False)
203
203
204 def init_display_formatter(self):
204 def init_display_formatter(self):
205 super(TerminalInteractiveShell, self).init_display_formatter()
205 super(TerminalInteractiveShell, self).init_display_formatter()
206 # terminal only supports plain text
206 # terminal only supports plain text
207 self.display_formatter.active_types = ['text/plain']
207 self.display_formatter.active_types = ['text/plain']
208
208
209 def init_prompt_toolkit_cli(self):
209 def init_prompt_toolkit_cli(self):
210 if self.simple_prompt:
210 if self.simple_prompt:
211 # Fall back to plain non-interactive output for tests.
211 # Fall back to plain non-interactive output for tests.
212 # This is very limited, and only accepts a single line.
212 # This is very limited, and only accepts a single line.
213 def prompt():
213 def prompt():
214 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
214 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
215 self.prompt_for_code = prompt
215 self.prompt_for_code = prompt
216 return
216 return
217
217
218 # Set up keyboard shortcuts
218 # Set up keyboard shortcuts
219 kbmanager = KeyBindingManager.for_prompt()
219 kbmanager = KeyBindingManager.for_prompt()
220 register_ipython_shortcuts(kbmanager.registry, self)
220 register_ipython_shortcuts(kbmanager.registry, self)
221
221
222 # Pre-populate history from IPython's history database
222 # Pre-populate history from IPython's history database
223 history = InMemoryHistory()
223 history = InMemoryHistory()
224 last_cell = u""
224 last_cell = u""
225 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
225 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
226 include_latest=True):
226 include_latest=True):
227 # Ignore blank lines and consecutive duplicates
227 # Ignore blank lines and consecutive duplicates
228 cell = cell.rstrip()
228 cell = cell.rstrip()
229 if cell and (cell != last_cell):
229 if cell and (cell != last_cell):
230 history.append(cell)
230 history.append(cell)
231
231
232 self._style = self._make_style_from_name(self.highlighting_style)
232 self._style = self._make_style_from_name(self.highlighting_style)
233 style = DynamicStyle(lambda: self._style)
233 style = DynamicStyle(lambda: self._style)
234
234
235 editing_mode = getattr(EditingMode, self.editing_mode.upper())
235 editing_mode = getattr(EditingMode, self.editing_mode.upper())
236
236
237 self._pt_app = create_prompt_application(
237 self._pt_app = create_prompt_application(
238 editing_mode=editing_mode,
238 editing_mode=editing_mode,
239 key_bindings_registry=kbmanager.registry,
239 key_bindings_registry=kbmanager.registry,
240 history=history,
240 history=history,
241 completer=IPythonPTCompleter(self.Completer),
241 completer=IPythonPTCompleter(self),
242 enable_history_search=True,
242 enable_history_search=True,
243 style=style,
243 style=style,
244 mouse_support=self.mouse_support,
244 mouse_support=self.mouse_support,
245 **self._layout_options()
245 **self._layout_options()
246 )
246 )
247 self._eventloop = create_eventloop(self.inputhook)
247 self._eventloop = create_eventloop(self.inputhook)
248 self.pt_cli = CommandLineInterface(
248 self.pt_cli = CommandLineInterface(
249 self._pt_app, eventloop=self._eventloop,
249 self._pt_app, eventloop=self._eventloop,
250 output=create_output(true_color=self.true_color))
250 output=create_output(true_color=self.true_color))
251
251
252 def _make_style_from_name(self, name):
252 def _make_style_from_name(self, name):
253 """
253 """
254 Small wrapper that make an IPython compatible style from a style name
254 Small wrapper that make an IPython compatible style from a style name
255
255
256 We need that to add style for prompt ... etc.
256 We need that to add style for prompt ... etc.
257 """
257 """
258 style_overrides = {}
258 style_overrides = {}
259 if name == 'legacy':
259 if name == 'legacy':
260 legacy = self.colors.lower()
260 legacy = self.colors.lower()
261 if legacy == 'linux':
261 if legacy == 'linux':
262 style_cls = get_style_by_name('monokai')
262 style_cls = get_style_by_name('monokai')
263 style_overrides = _style_overrides_linux
263 style_overrides = _style_overrides_linux
264 elif legacy == 'lightbg':
264 elif legacy == 'lightbg':
265 style_overrides = _style_overrides_light_bg
265 style_overrides = _style_overrides_light_bg
266 style_cls = get_style_by_name('pastie')
266 style_cls = get_style_by_name('pastie')
267 elif legacy == 'neutral':
267 elif legacy == 'neutral':
268 # The default theme needs to be visible on both a dark background
268 # The default theme needs to be visible on both a dark background
269 # and a light background, because we can't tell what the terminal
269 # and a light background, because we can't tell what the terminal
270 # looks like. These tweaks to the default theme help with that.
270 # looks like. These tweaks to the default theme help with that.
271 style_cls = get_style_by_name('default')
271 style_cls = get_style_by_name('default')
272 style_overrides.update({
272 style_overrides.update({
273 Token.Number: '#007700',
273 Token.Number: '#007700',
274 Token.Operator: 'noinherit',
274 Token.Operator: 'noinherit',
275 Token.String: '#BB6622',
275 Token.String: '#BB6622',
276 Token.Name.Function: '#2080D0',
276 Token.Name.Function: '#2080D0',
277 Token.Name.Class: 'bold #2080D0',
277 Token.Name.Class: 'bold #2080D0',
278 Token.Name.Namespace: 'bold #2080D0',
278 Token.Name.Namespace: 'bold #2080D0',
279 Token.Prompt: '#009900',
279 Token.Prompt: '#009900',
280 Token.PromptNum: '#00ff00 bold',
280 Token.PromptNum: '#00ff00 bold',
281 Token.OutPrompt: '#990000',
281 Token.OutPrompt: '#990000',
282 Token.OutPromptNum: '#ff0000 bold',
282 Token.OutPromptNum: '#ff0000 bold',
283 })
283 })
284 elif legacy =='nocolor':
284 elif legacy =='nocolor':
285 style_cls=_NoStyle
285 style_cls=_NoStyle
286 style_overrides = {}
286 style_overrides = {}
287 else :
287 else :
288 raise ValueError('Got unknown colors: ', legacy)
288 raise ValueError('Got unknown colors: ', legacy)
289 else :
289 else :
290 style_cls = get_style_by_name(name)
290 style_cls = get_style_by_name(name)
291 style_overrides = {
291 style_overrides = {
292 Token.Prompt: '#009900',
292 Token.Prompt: '#009900',
293 Token.PromptNum: '#00ff00 bold',
293 Token.PromptNum: '#00ff00 bold',
294 Token.OutPrompt: '#990000',
294 Token.OutPrompt: '#990000',
295 Token.OutPromptNum: '#ff0000 bold',
295 Token.OutPromptNum: '#ff0000 bold',
296 }
296 }
297 style_overrides.update(self.highlighting_style_overrides)
297 style_overrides.update(self.highlighting_style_overrides)
298 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
298 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
299 style_dict=style_overrides)
299 style_dict=style_overrides)
300
300
301 return style
301 return style
302
302
303 def _layout_options(self):
303 def _layout_options(self):
304 """
304 """
305 Return the current layout option for the current Terminal InteractiveShell
305 Return the current layout option for the current Terminal InteractiveShell
306 """
306 """
307 return {
307 return {
308 'lexer':IPythonPTLexer(),
308 'lexer':IPythonPTLexer(),
309 'reserve_space_for_menu':self.space_for_menu,
309 'reserve_space_for_menu':self.space_for_menu,
310 'get_prompt_tokens':self.prompts.in_prompt_tokens,
310 'get_prompt_tokens':self.prompts.in_prompt_tokens,
311 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
311 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
312 'multiline':True,
312 'multiline':True,
313 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
313 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
314
314
315 # Highlight matching brackets, but only when this setting is
315 # Highlight matching brackets, but only when this setting is
316 # enabled, and only when the DEFAULT_BUFFER has the focus.
316 # enabled, and only when the DEFAULT_BUFFER has the focus.
317 'extra_input_processors': [ConditionalProcessor(
317 'extra_input_processors': [ConditionalProcessor(
318 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
318 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
319 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
319 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
320 Condition(lambda cli: self.highlight_matching_brackets))],
320 Condition(lambda cli: self.highlight_matching_brackets))],
321 }
321 }
322
322
323 def _update_layout(self):
323 def _update_layout(self):
324 """
324 """
325 Ask for a re computation of the application layout, if for example ,
325 Ask for a re computation of the application layout, if for example ,
326 some configuration options have changed.
326 some configuration options have changed.
327 """
327 """
328 if self._pt_app:
328 if self._pt_app:
329 self._pt_app.layout = create_prompt_layout(**self._layout_options())
329 self._pt_app.layout = create_prompt_layout(**self._layout_options())
330
330
331 def prompt_for_code(self):
331 def prompt_for_code(self):
332 document = self.pt_cli.run(
332 document = self.pt_cli.run(
333 pre_run=self.pre_prompt, reset_current_buffer=True)
333 pre_run=self.pre_prompt, reset_current_buffer=True)
334 return document.text
334 return document.text
335
335
336 def enable_win_unicode_console(self):
336 def enable_win_unicode_console(self):
337 import win_unicode_console
337 import win_unicode_console
338
338
339 if PY3:
339 if PY3:
340 win_unicode_console.enable()
340 win_unicode_console.enable()
341 else:
341 else:
342 # https://github.com/ipython/ipython/issues/9768
342 # https://github.com/ipython/ipython/issues/9768
343 from win_unicode_console.streams import (TextStreamWrapper,
343 from win_unicode_console.streams import (TextStreamWrapper,
344 stdout_text_transcoded, stderr_text_transcoded)
344 stdout_text_transcoded, stderr_text_transcoded)
345
345
346 class LenientStrStreamWrapper(TextStreamWrapper):
346 class LenientStrStreamWrapper(TextStreamWrapper):
347 def write(self, s):
347 def write(self, s):
348 if isinstance(s, bytes):
348 if isinstance(s, bytes):
349 s = s.decode(self.encoding, 'replace')
349 s = s.decode(self.encoding, 'replace')
350
350
351 self.base.write(s)
351 self.base.write(s)
352
352
353 stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded)
353 stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded)
354 stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded)
354 stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded)
355
355
356 win_unicode_console.enable(stdout=stdout_text_str,
356 win_unicode_console.enable(stdout=stdout_text_str,
357 stderr=stderr_text_str)
357 stderr=stderr_text_str)
358
358
359 def init_io(self):
359 def init_io(self):
360 if sys.platform not in {'win32', 'cli'}:
360 if sys.platform not in {'win32', 'cli'}:
361 return
361 return
362
362
363 self.enable_win_unicode_console()
363 self.enable_win_unicode_console()
364
364
365 import colorama
365 import colorama
366 colorama.init()
366 colorama.init()
367
367
368 # For some reason we make these wrappers around stdout/stderr.
368 # For some reason we make these wrappers around stdout/stderr.
369 # For now, we need to reset them so all output gets coloured.
369 # For now, we need to reset them so all output gets coloured.
370 # https://github.com/ipython/ipython/issues/8669
370 # https://github.com/ipython/ipython/issues/8669
371 # io.std* are deprecated, but don't show our own deprecation warnings
371 # io.std* are deprecated, but don't show our own deprecation warnings
372 # during initialization of the deprecated API.
372 # during initialization of the deprecated API.
373 with warnings.catch_warnings():
373 with warnings.catch_warnings():
374 warnings.simplefilter('ignore', DeprecationWarning)
374 warnings.simplefilter('ignore', DeprecationWarning)
375 io.stdout = io.IOStream(sys.stdout)
375 io.stdout = io.IOStream(sys.stdout)
376 io.stderr = io.IOStream(sys.stderr)
376 io.stderr = io.IOStream(sys.stderr)
377
377
378 def init_magics(self):
378 def init_magics(self):
379 super(TerminalInteractiveShell, self).init_magics()
379 super(TerminalInteractiveShell, self).init_magics()
380 self.register_magics(TerminalMagics)
380 self.register_magics(TerminalMagics)
381
381
382 def init_alias(self):
382 def init_alias(self):
383 # The parent class defines aliases that can be safely used with any
383 # The parent class defines aliases that can be safely used with any
384 # frontend.
384 # frontend.
385 super(TerminalInteractiveShell, self).init_alias()
385 super(TerminalInteractiveShell, self).init_alias()
386
386
387 # Now define aliases that only make sense on the terminal, because they
387 # Now define aliases that only make sense on the terminal, because they
388 # need direct access to the console in a way that we can't emulate in
388 # need direct access to the console in a way that we can't emulate in
389 # GUI or web frontend
389 # GUI or web frontend
390 if os.name == 'posix':
390 if os.name == 'posix':
391 for cmd in ['clear', 'more', 'less', 'man']:
391 for cmd in ['clear', 'more', 'less', 'man']:
392 self.alias_manager.soft_define_alias(cmd, cmd)
392 self.alias_manager.soft_define_alias(cmd, cmd)
393
393
394
394
395 def __init__(self, *args, **kwargs):
395 def __init__(self, *args, **kwargs):
396 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
396 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
397 self.init_prompt_toolkit_cli()
397 self.init_prompt_toolkit_cli()
398 self.init_term_title()
398 self.init_term_title()
399 self.keep_running = True
399 self.keep_running = True
400
400
401 self.debugger_history = InMemoryHistory()
401 self.debugger_history = InMemoryHistory()
402
402
403 def ask_exit(self):
403 def ask_exit(self):
404 self.keep_running = False
404 self.keep_running = False
405
405
406 rl_next_input = None
406 rl_next_input = None
407
407
408 def pre_prompt(self):
408 def pre_prompt(self):
409 if self.rl_next_input:
409 if self.rl_next_input:
410 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
410 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
411 self.rl_next_input = None
411 self.rl_next_input = None
412
412
413 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
413 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
414
414
415 if display_banner is not DISPLAY_BANNER_DEPRECATED:
415 if display_banner is not DISPLAY_BANNER_DEPRECATED:
416 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
416 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
417
417
418 self.keep_running = True
418 self.keep_running = True
419 while self.keep_running:
419 while self.keep_running:
420 print(self.separate_in, end='')
420 print(self.separate_in, end='')
421
421
422 try:
422 try:
423 code = self.prompt_for_code()
423 code = self.prompt_for_code()
424 except EOFError:
424 except EOFError:
425 if (not self.confirm_exit) \
425 if (not self.confirm_exit) \
426 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
426 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
427 self.ask_exit()
427 self.ask_exit()
428
428
429 else:
429 else:
430 if code:
430 if code:
431 self.run_cell(code, store_history=True)
431 self.run_cell(code, store_history=True)
432
432
433 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
433 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
434 # An extra layer of protection in case someone mashing Ctrl-C breaks
434 # An extra layer of protection in case someone mashing Ctrl-C breaks
435 # out of our internal code.
435 # out of our internal code.
436 if display_banner is not DISPLAY_BANNER_DEPRECATED:
436 if display_banner is not DISPLAY_BANNER_DEPRECATED:
437 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
437 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
438 while True:
438 while True:
439 try:
439 try:
440 self.interact()
440 self.interact()
441 break
441 break
442 except KeyboardInterrupt:
442 except KeyboardInterrupt:
443 print("\nKeyboardInterrupt escaped interact()\n")
443 print("\nKeyboardInterrupt escaped interact()\n")
444
444
445 _inputhook = None
445 _inputhook = None
446 def inputhook(self, context):
446 def inputhook(self, context):
447 if self._inputhook is not None:
447 if self._inputhook is not None:
448 self._inputhook(context)
448 self._inputhook(context)
449
449
450 def enable_gui(self, gui=None):
450 def enable_gui(self, gui=None):
451 if gui:
451 if gui:
452 self._inputhook = get_inputhook_func(gui)
452 self._inputhook = get_inputhook_func(gui)
453 else:
453 else:
454 self._inputhook = None
454 self._inputhook = None
455
455
456 # Run !system commands directly, not through pipes, so terminal programs
456 # Run !system commands directly, not through pipes, so terminal programs
457 # work correctly.
457 # work correctly.
458 system = InteractiveShell.system_raw
458 system = InteractiveShell.system_raw
459
459
460 def auto_rewrite_input(self, cmd):
460 def auto_rewrite_input(self, cmd):
461 """Overridden from the parent class to use fancy rewriting prompt"""
461 """Overridden from the parent class to use fancy rewriting prompt"""
462 if not self.show_rewritten_input:
462 if not self.show_rewritten_input:
463 return
463 return
464
464
465 tokens = self.prompts.rewrite_prompt_tokens()
465 tokens = self.prompts.rewrite_prompt_tokens()
466 if self.pt_cli:
466 if self.pt_cli:
467 self.pt_cli.print_tokens(tokens)
467 self.pt_cli.print_tokens(tokens)
468 print(cmd)
468 print(cmd)
469 else:
469 else:
470 prompt = ''.join(s for t, s in tokens)
470 prompt = ''.join(s for t, s in tokens)
471 print(prompt, cmd, sep='')
471 print(prompt, cmd, sep='')
472
472
473 _prompts_before = None
473 _prompts_before = None
474 def switch_doctest_mode(self, mode):
474 def switch_doctest_mode(self, mode):
475 """Switch prompts to classic for %doctest_mode"""
475 """Switch prompts to classic for %doctest_mode"""
476 if mode:
476 if mode:
477 self._prompts_before = self.prompts
477 self._prompts_before = self.prompts
478 self.prompts = ClassicPrompts(self)
478 self.prompts = ClassicPrompts(self)
479 elif self._prompts_before:
479 elif self._prompts_before:
480 self.prompts = self._prompts_before
480 self.prompts = self._prompts_before
481 self._prompts_before = None
481 self._prompts_before = None
482 self._update_layout()
482 self._update_layout()
483
483
484
484
485 InteractiveShellABC.register(TerminalInteractiveShell)
485 InteractiveShellABC.register(TerminalInteractiveShell)
486
486
487 if __name__ == '__main__':
487 if __name__ == '__main__':
488 TerminalInteractiveShell.instance().interact()
488 TerminalInteractiveShell.instance().interact()
@@ -1,88 +1,92 b''
1 import unicodedata
1 import unicodedata
2 from wcwidth import wcwidth
2 from wcwidth import wcwidth
3
3
4 from IPython.utils.py3compat import PY3
4 from IPython.utils.py3compat import PY3
5
5
6 from prompt_toolkit.completion import Completer, Completion
6 from prompt_toolkit.completion import Completer, Completion
7 from prompt_toolkit.layout.lexers import Lexer
7 from prompt_toolkit.layout.lexers import Lexer
8 from prompt_toolkit.layout.lexers import PygmentsLexer
8 from prompt_toolkit.layout.lexers import PygmentsLexer
9
9
10 import pygments.lexers as pygments_lexers
10 import pygments.lexers as pygments_lexers
11
11
12
12
13 class IPythonPTCompleter(Completer):
13 class IPythonPTCompleter(Completer):
14 """Adaptor to provide IPython completions to prompt_toolkit"""
14 """Adaptor to provide IPython completions to prompt_toolkit"""
15 def __init__(self, ipy_completer):
15 def __init__(self, shell):
16 self.ipy_completer = ipy_completer
16 self.shell = shell
17
18 @property
19 def ipy_completer(self):
20 return self.shell.Completer
17
21
18 def get_completions(self, document, complete_event):
22 def get_completions(self, document, complete_event):
19 if not document.current_line.strip():
23 if not document.current_line.strip():
20 return
24 return
21
25
22 used, matches = self.ipy_completer.complete(
26 used, matches = self.ipy_completer.complete(
23 line_buffer=document.current_line,
27 line_buffer=document.current_line,
24 cursor_pos=document.cursor_position_col
28 cursor_pos=document.cursor_position_col
25 )
29 )
26 start_pos = -len(used)
30 start_pos = -len(used)
27 for m in matches:
31 for m in matches:
28 if not m:
32 if not m:
29 # Guard against completion machinery giving us an empty string.
33 # Guard against completion machinery giving us an empty string.
30 continue
34 continue
31
35
32 m = unicodedata.normalize('NFC', m)
36 m = unicodedata.normalize('NFC', m)
33
37
34 # When the first character of the completion has a zero length,
38 # When the first character of the completion has a zero length,
35 # then it's probably a decomposed unicode character. E.g. caused by
39 # then it's probably a decomposed unicode character. E.g. caused by
36 # the "\dot" completion. Try to compose again with the previous
40 # the "\dot" completion. Try to compose again with the previous
37 # character.
41 # character.
38 if wcwidth(m[0]) == 0:
42 if wcwidth(m[0]) == 0:
39 if document.cursor_position + start_pos > 0:
43 if document.cursor_position + start_pos > 0:
40 char_before = document.text[document.cursor_position + start_pos - 1]
44 char_before = document.text[document.cursor_position + start_pos - 1]
41 m = unicodedata.normalize('NFC', char_before + m)
45 m = unicodedata.normalize('NFC', char_before + m)
42
46
43 # Yield the modified completion instead, if this worked.
47 # Yield the modified completion instead, if this worked.
44 if wcwidth(m[0:1]) == 1:
48 if wcwidth(m[0:1]) == 1:
45 yield Completion(m, start_position=start_pos - 1)
49 yield Completion(m, start_position=start_pos - 1)
46 continue
50 continue
47
51
48 # TODO: Use Jedi to determine meta_text
52 # TODO: Use Jedi to determine meta_text
49 # (Jedi currently has a bug that results in incorrect information.)
53 # (Jedi currently has a bug that results in incorrect information.)
50 # meta_text = ''
54 # meta_text = ''
51 # yield Completion(m, start_position=start_pos,
55 # yield Completion(m, start_position=start_pos,
52 # display_meta=meta_text)
56 # display_meta=meta_text)
53 yield Completion(m, start_position=start_pos)
57 yield Completion(m, start_position=start_pos)
54
58
55 class IPythonPTLexer(Lexer):
59 class IPythonPTLexer(Lexer):
56 """
60 """
57 Wrapper around PythonLexer and BashLexer.
61 Wrapper around PythonLexer and BashLexer.
58 """
62 """
59 def __init__(self):
63 def __init__(self):
60 l = pygments_lexers
64 l = pygments_lexers
61 self.python_lexer = PygmentsLexer(l.Python3Lexer if PY3 else l.PythonLexer)
65 self.python_lexer = PygmentsLexer(l.Python3Lexer if PY3 else l.PythonLexer)
62 self.shell_lexer = PygmentsLexer(l.BashLexer)
66 self.shell_lexer = PygmentsLexer(l.BashLexer)
63
67
64 self.magic_lexers = {
68 self.magic_lexers = {
65 'HTML': PygmentsLexer(l.HtmlLexer),
69 'HTML': PygmentsLexer(l.HtmlLexer),
66 'html': PygmentsLexer(l.HtmlLexer),
70 'html': PygmentsLexer(l.HtmlLexer),
67 'javascript': PygmentsLexer(l.JavascriptLexer),
71 'javascript': PygmentsLexer(l.JavascriptLexer),
68 'js': PygmentsLexer(l.JavascriptLexer),
72 'js': PygmentsLexer(l.JavascriptLexer),
69 'perl': PygmentsLexer(l.PerlLexer),
73 'perl': PygmentsLexer(l.PerlLexer),
70 'ruby': PygmentsLexer(l.RubyLexer),
74 'ruby': PygmentsLexer(l.RubyLexer),
71 'latex': PygmentsLexer(l.TexLexer),
75 'latex': PygmentsLexer(l.TexLexer),
72 }
76 }
73
77
74 def lex_document(self, cli, document):
78 def lex_document(self, cli, document):
75 text = document.text.lstrip()
79 text = document.text.lstrip()
76
80
77 lexer = self.python_lexer
81 lexer = self.python_lexer
78
82
79 if text.startswith('!') or text.startswith('%%bash'):
83 if text.startswith('!') or text.startswith('%%bash'):
80 lexer = self.shell_lexer
84 lexer = self.shell_lexer
81
85
82 elif text.startswith('%%'):
86 elif text.startswith('%%'):
83 for magic, l in self.magic_lexers.items():
87 for magic, l in self.magic_lexers.items():
84 if text.startswith('%%' + magic):
88 if text.startswith('%%' + magic):
85 lexer = l
89 lexer = l
86 break
90 break
87
91
88 return lexer.lex_document(cli, document)
92 return lexer.lex_document(cli, document)
General Comments 0
You need to be logged in to leave comments. Login now