##// END OF EJS Templates
disable `_ipython_display_` in terminal IPython...
Min RK -
Show More
@@ -1,489 +1,491 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2
2
3 import os
3 import os
4 import sys
4 import sys
5 import warnings
5 import warnings
6 from warnings import warn
6 from warnings import warn
7
7
8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 from IPython.utils import io
9 from IPython.utils import io
10 from IPython.utils.py3compat import cast_unicode_py2, input
10 from IPython.utils.py3compat import cast_unicode_py2, input
11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 from IPython.utils.process import abbrev_cwd
12 from IPython.utils.process import abbrev_cwd
13 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union
13 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union
14
14
15 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
15 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
16 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
16 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
17 from prompt_toolkit.history import InMemoryHistory
17 from prompt_toolkit.history import InMemoryHistory
18 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
18 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
19 from prompt_toolkit.interface import CommandLineInterface
19 from prompt_toolkit.interface import CommandLineInterface
20 from prompt_toolkit.key_binding.manager import KeyBindingManager
20 from prompt_toolkit.key_binding.manager import KeyBindingManager
21 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
21 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
22 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
22 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
23
23
24 from pygments.styles import get_style_by_name, get_all_styles
24 from pygments.styles import get_style_by_name, get_all_styles
25 from pygments.style import Style
25 from pygments.style import Style
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_name_and_func
30 from .pt_inputhooks import get_inputhook_name_and_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 return os.environ['EDITOR']
62 return os.environ['EDITOR']
63 except KeyError:
63 except KeyError:
64 pass
64 pass
65 except UnicodeError:
65 except UnicodeError:
66 warn("$EDITOR environment variable is not pure ASCII. Using platform "
66 warn("$EDITOR environment variable is not pure ASCII. Using platform "
67 "default editor.")
67 "default editor.")
68
68
69 if os.name == 'posix':
69 if os.name == 'posix':
70 return 'vi' # the only one guaranteed to be there!
70 return 'vi' # the only one guaranteed to be there!
71 else:
71 else:
72 return 'notepad' # same in Windows!
72 return 'notepad' # same in Windows!
73
73
74 # conservatively check for tty
74 # conservatively check for tty
75 # overridden streams can result in things like:
75 # overridden streams can result in things like:
76 # - sys.stdin = None
76 # - sys.stdin = None
77 # - no isatty method
77 # - no isatty method
78 for _name in ('stdin', 'stdout', 'stderr'):
78 for _name in ('stdin', 'stdout', 'stderr'):
79 _stream = getattr(sys, _name)
79 _stream = getattr(sys, _name)
80 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
80 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
81 _is_tty = False
81 _is_tty = False
82 break
82 break
83 else:
83 else:
84 _is_tty = True
84 _is_tty = True
85
85
86
86
87 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
87 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
88
88
89 class TerminalInteractiveShell(InteractiveShell):
89 class TerminalInteractiveShell(InteractiveShell):
90 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
90 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
91 'to reserve for the completion menu'
91 'to reserve for the completion menu'
92 ).tag(config=True)
92 ).tag(config=True)
93
93
94 def _space_for_menu_changed(self, old, new):
94 def _space_for_menu_changed(self, old, new):
95 self._update_layout()
95 self._update_layout()
96
96
97 pt_cli = None
97 pt_cli = None
98 debugger_history = None
98 debugger_history = None
99 _pt_app = None
99 _pt_app = None
100
100
101 simple_prompt = Bool(_use_simple_prompt,
101 simple_prompt = Bool(_use_simple_prompt,
102 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
102 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
103
103
104 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
104 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
105 IPython own testing machinery, and emacs inferior-shell integration through elpy.
105 IPython own testing machinery, and emacs inferior-shell integration through elpy.
106
106
107 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
107 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
108 environment variable is set, or the current terminal is not a tty.
108 environment variable is set, or the current terminal is not a tty.
109
109
110 """
110 """
111 ).tag(config=True)
111 ).tag(config=True)
112
112
113 @property
113 @property
114 def debugger_cls(self):
114 def debugger_cls(self):
115 return Pdb if self.simple_prompt else TerminalPdb
115 return Pdb if self.simple_prompt else TerminalPdb
116
116
117 confirm_exit = Bool(True,
117 confirm_exit = Bool(True,
118 help="""
118 help="""
119 Set to confirm when you try to exit IPython with an EOF (Control-D
119 Set to confirm when you try to exit IPython with an EOF (Control-D
120 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
120 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
121 you can force a direct exit without any confirmation.""",
121 you can force a direct exit without any confirmation.""",
122 ).tag(config=True)
122 ).tag(config=True)
123
123
124 editing_mode = Unicode('emacs',
124 editing_mode = Unicode('emacs',
125 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
125 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
126 ).tag(config=True)
126 ).tag(config=True)
127
127
128 mouse_support = Bool(False,
128 mouse_support = Bool(False,
129 help="Enable mouse support in the prompt"
129 help="Enable mouse support in the prompt"
130 ).tag(config=True)
130 ).tag(config=True)
131
131
132 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
132 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
133 help="""The name or class of a Pygments style to use for syntax
133 help="""The name or class of a Pygments style to use for syntax
134 highlighting: \n %s""" % ', '.join(get_all_styles())
134 highlighting: \n %s""" % ', '.join(get_all_styles())
135 ).tag(config=True)
135 ).tag(config=True)
136
136
137
137
138 @observe('highlighting_style')
138 @observe('highlighting_style')
139 @observe('colors')
139 @observe('colors')
140 def _highlighting_style_changed(self, change):
140 def _highlighting_style_changed(self, change):
141 self.refresh_style()
141 self.refresh_style()
142
142
143 def refresh_style(self):
143 def refresh_style(self):
144 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
144 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
145
145
146
146
147 highlighting_style_overrides = Dict(
147 highlighting_style_overrides = Dict(
148 help="Override highlighting format for specific tokens"
148 help="Override highlighting format for specific tokens"
149 ).tag(config=True)
149 ).tag(config=True)
150
150
151 true_color = Bool(False,
151 true_color = Bool(False,
152 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
152 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
153 "If your terminal supports true color, the following command "
153 "If your terminal supports true color, the following command "
154 "should print 'TRUECOLOR' in orange: "
154 "should print 'TRUECOLOR' in orange: "
155 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
155 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
156 ).tag(config=True)
156 ).tag(config=True)
157
157
158 editor = Unicode(get_default_editor(),
158 editor = Unicode(get_default_editor(),
159 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
159 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
160 ).tag(config=True)
160 ).tag(config=True)
161
161
162 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
162 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
163
163
164 prompts = Instance(Prompts)
164 prompts = Instance(Prompts)
165
165
166 @default('prompts')
166 @default('prompts')
167 def _prompts_default(self):
167 def _prompts_default(self):
168 return self.prompts_class(self)
168 return self.prompts_class(self)
169
169
170 @observe('prompts')
170 @observe('prompts')
171 def _(self, change):
171 def _(self, change):
172 self._update_layout()
172 self._update_layout()
173
173
174 @default('displayhook_class')
174 @default('displayhook_class')
175 def _displayhook_class_default(self):
175 def _displayhook_class_default(self):
176 return RichPromptDisplayHook
176 return RichPromptDisplayHook
177
177
178 term_title = Bool(True,
178 term_title = Bool(True,
179 help="Automatically set the terminal title"
179 help="Automatically set the terminal title"
180 ).tag(config=True)
180 ).tag(config=True)
181
181
182 display_completions = Enum(('column', 'multicolumn','readlinelike'),
182 display_completions = Enum(('column', 'multicolumn','readlinelike'),
183 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
183 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
184 "'readlinelike'. These options are for `prompt_toolkit`, see "
184 "'readlinelike'. These options are for `prompt_toolkit`, see "
185 "`prompt_toolkit` documentation for more information."
185 "`prompt_toolkit` documentation for more information."
186 ),
186 ),
187 default_value='multicolumn').tag(config=True)
187 default_value='multicolumn').tag(config=True)
188
188
189 highlight_matching_brackets = Bool(True,
189 highlight_matching_brackets = Bool(True,
190 help="Highlight matching brackets .",
190 help="Highlight matching brackets .",
191 ).tag(config=True)
191 ).tag(config=True)
192
192
193 @observe('term_title')
193 @observe('term_title')
194 def init_term_title(self, change=None):
194 def init_term_title(self, change=None):
195 # Enable or disable the terminal title.
195 # Enable or disable the terminal title.
196 if self.term_title:
196 if self.term_title:
197 toggle_set_term_title(True)
197 toggle_set_term_title(True)
198 set_term_title('IPython: ' + abbrev_cwd())
198 set_term_title('IPython: ' + abbrev_cwd())
199 else:
199 else:
200 toggle_set_term_title(False)
200 toggle_set_term_title(False)
201
201
202 def init_display_formatter(self):
202 def init_display_formatter(self):
203 super(TerminalInteractiveShell, self).init_display_formatter()
203 super(TerminalInteractiveShell, self).init_display_formatter()
204 # terminal only supports plain text
204 # terminal only supports plain text
205 self.display_formatter.active_types = ['text/plain']
205 self.display_formatter.active_types = ['text/plain']
206 # disable `_ipython_display_`
207 self.display_formatter.ipython_display_formatter.enabled = False
206
208
207 def init_prompt_toolkit_cli(self):
209 def init_prompt_toolkit_cli(self):
208 if self.simple_prompt:
210 if self.simple_prompt:
209 # Fall back to plain non-interactive output for tests.
211 # Fall back to plain non-interactive output for tests.
210 # This is very limited, and only accepts a single line.
212 # This is very limited, and only accepts a single line.
211 def prompt():
213 def prompt():
212 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
214 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
213 self.prompt_for_code = prompt
215 self.prompt_for_code = prompt
214 return
216 return
215
217
216 # Set up keyboard shortcuts
218 # Set up keyboard shortcuts
217 kbmanager = KeyBindingManager.for_prompt()
219 kbmanager = KeyBindingManager.for_prompt()
218 register_ipython_shortcuts(kbmanager.registry, self)
220 register_ipython_shortcuts(kbmanager.registry, self)
219
221
220 # Pre-populate history from IPython's history database
222 # Pre-populate history from IPython's history database
221 history = InMemoryHistory()
223 history = InMemoryHistory()
222 last_cell = u""
224 last_cell = u""
223 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,
224 include_latest=True):
226 include_latest=True):
225 # Ignore blank lines and consecutive duplicates
227 # Ignore blank lines and consecutive duplicates
226 cell = cell.rstrip()
228 cell = cell.rstrip()
227 if cell and (cell != last_cell):
229 if cell and (cell != last_cell):
228 history.append(cell)
230 history.append(cell)
229 last_cell = cell
231 last_cell = cell
230
232
231 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
233 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
232 style = DynamicStyle(lambda: self._style)
234 style = DynamicStyle(lambda: self._style)
233
235
234 editing_mode = getattr(EditingMode, self.editing_mode.upper())
236 editing_mode = getattr(EditingMode, self.editing_mode.upper())
235
237
236 def patch_stdout(**kwargs):
238 def patch_stdout(**kwargs):
237 return self.pt_cli.patch_stdout_context(**kwargs)
239 return self.pt_cli.patch_stdout_context(**kwargs)
238
240
239 self._pt_app = create_prompt_application(
241 self._pt_app = create_prompt_application(
240 editing_mode=editing_mode,
242 editing_mode=editing_mode,
241 key_bindings_registry=kbmanager.registry,
243 key_bindings_registry=kbmanager.registry,
242 history=history,
244 history=history,
243 completer=IPythonPTCompleter(shell=self,
245 completer=IPythonPTCompleter(shell=self,
244 patch_stdout=patch_stdout),
246 patch_stdout=patch_stdout),
245 enable_history_search=True,
247 enable_history_search=True,
246 style=style,
248 style=style,
247 mouse_support=self.mouse_support,
249 mouse_support=self.mouse_support,
248 **self._layout_options()
250 **self._layout_options()
249 )
251 )
250 self._eventloop = create_eventloop(self.inputhook)
252 self._eventloop = create_eventloop(self.inputhook)
251 self.pt_cli = CommandLineInterface(
253 self.pt_cli = CommandLineInterface(
252 self._pt_app, eventloop=self._eventloop,
254 self._pt_app, eventloop=self._eventloop,
253 output=create_output(true_color=self.true_color))
255 output=create_output(true_color=self.true_color))
254
256
255 def _make_style_from_name_or_cls(self, name_or_cls):
257 def _make_style_from_name_or_cls(self, name_or_cls):
256 """
258 """
257 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
258
260
259 We need that to add style for prompt ... etc.
261 We need that to add style for prompt ... etc.
260 """
262 """
261 style_overrides = {}
263 style_overrides = {}
262 if name_or_cls == 'legacy':
264 if name_or_cls == 'legacy':
263 legacy = self.colors.lower()
265 legacy = self.colors.lower()
264 if legacy == 'linux':
266 if legacy == 'linux':
265 style_cls = get_style_by_name('monokai')
267 style_cls = get_style_by_name('monokai')
266 style_overrides = _style_overrides_linux
268 style_overrides = _style_overrides_linux
267 elif legacy == 'lightbg':
269 elif legacy == 'lightbg':
268 style_overrides = _style_overrides_light_bg
270 style_overrides = _style_overrides_light_bg
269 style_cls = get_style_by_name('pastie')
271 style_cls = get_style_by_name('pastie')
270 elif legacy == 'neutral':
272 elif legacy == 'neutral':
271 # The default theme needs to be visible on both a dark background
273 # The default theme needs to be visible on both a dark background
272 # and a light background, because we can't tell what the terminal
274 # and a light background, because we can't tell what the terminal
273 # looks like. These tweaks to the default theme help with that.
275 # looks like. These tweaks to the default theme help with that.
274 style_cls = get_style_by_name('default')
276 style_cls = get_style_by_name('default')
275 style_overrides.update({
277 style_overrides.update({
276 Token.Number: '#007700',
278 Token.Number: '#007700',
277 Token.Operator: 'noinherit',
279 Token.Operator: 'noinherit',
278 Token.String: '#BB6622',
280 Token.String: '#BB6622',
279 Token.Name.Function: '#2080D0',
281 Token.Name.Function: '#2080D0',
280 Token.Name.Class: 'bold #2080D0',
282 Token.Name.Class: 'bold #2080D0',
281 Token.Name.Namespace: 'bold #2080D0',
283 Token.Name.Namespace: 'bold #2080D0',
282 Token.Prompt: '#009900',
284 Token.Prompt: '#009900',
283 Token.PromptNum: '#00ff00 bold',
285 Token.PromptNum: '#00ff00 bold',
284 Token.OutPrompt: '#990000',
286 Token.OutPrompt: '#990000',
285 Token.OutPromptNum: '#ff0000 bold',
287 Token.OutPromptNum: '#ff0000 bold',
286 })
288 })
287 elif legacy =='nocolor':
289 elif legacy =='nocolor':
288 style_cls=_NoStyle
290 style_cls=_NoStyle
289 style_overrides = {}
291 style_overrides = {}
290 else :
292 else :
291 raise ValueError('Got unknown colors: ', legacy)
293 raise ValueError('Got unknown colors: ', legacy)
292 else :
294 else :
293 if isinstance(name_or_cls, str):
295 if isinstance(name_or_cls, str):
294 style_cls = get_style_by_name(name_or_cls)
296 style_cls = get_style_by_name(name_or_cls)
295 else:
297 else:
296 style_cls = name_or_cls
298 style_cls = name_or_cls
297 style_overrides = {
299 style_overrides = {
298 Token.Prompt: '#009900',
300 Token.Prompt: '#009900',
299 Token.PromptNum: '#00ff00 bold',
301 Token.PromptNum: '#00ff00 bold',
300 Token.OutPrompt: '#990000',
302 Token.OutPrompt: '#990000',
301 Token.OutPromptNum: '#ff0000 bold',
303 Token.OutPromptNum: '#ff0000 bold',
302 }
304 }
303 style_overrides.update(self.highlighting_style_overrides)
305 style_overrides.update(self.highlighting_style_overrides)
304 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
306 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
305 style_dict=style_overrides)
307 style_dict=style_overrides)
306
308
307 return style
309 return style
308
310
309 def _layout_options(self):
311 def _layout_options(self):
310 """
312 """
311 Return the current layout option for the current Terminal InteractiveShell
313 Return the current layout option for the current Terminal InteractiveShell
312 """
314 """
313 return {
315 return {
314 'lexer':IPythonPTLexer(),
316 'lexer':IPythonPTLexer(),
315 'reserve_space_for_menu':self.space_for_menu,
317 'reserve_space_for_menu':self.space_for_menu,
316 'get_prompt_tokens':self.prompts.in_prompt_tokens,
318 'get_prompt_tokens':self.prompts.in_prompt_tokens,
317 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
319 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
318 'multiline':True,
320 'multiline':True,
319 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
321 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
320
322
321 # Highlight matching brackets, but only when this setting is
323 # Highlight matching brackets, but only when this setting is
322 # enabled, and only when the DEFAULT_BUFFER has the focus.
324 # enabled, and only when the DEFAULT_BUFFER has the focus.
323 'extra_input_processors': [ConditionalProcessor(
325 'extra_input_processors': [ConditionalProcessor(
324 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
326 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
325 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
327 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
326 Condition(lambda cli: self.highlight_matching_brackets))],
328 Condition(lambda cli: self.highlight_matching_brackets))],
327 }
329 }
328
330
329 def _update_layout(self):
331 def _update_layout(self):
330 """
332 """
331 Ask for a re computation of the application layout, if for example ,
333 Ask for a re computation of the application layout, if for example ,
332 some configuration options have changed.
334 some configuration options have changed.
333 """
335 """
334 if self._pt_app:
336 if self._pt_app:
335 self._pt_app.layout = create_prompt_layout(**self._layout_options())
337 self._pt_app.layout = create_prompt_layout(**self._layout_options())
336
338
337 def prompt_for_code(self):
339 def prompt_for_code(self):
338 document = self.pt_cli.run(
340 document = self.pt_cli.run(
339 pre_run=self.pre_prompt, reset_current_buffer=True)
341 pre_run=self.pre_prompt, reset_current_buffer=True)
340 return document.text
342 return document.text
341
343
342 def enable_win_unicode_console(self):
344 def enable_win_unicode_console(self):
343 if sys.version_info >= (3, 6):
345 if sys.version_info >= (3, 6):
344 # Since PEP 528, Python uses the unicode APIs for the Windows
346 # Since PEP 528, Python uses the unicode APIs for the Windows
345 # console by default, so WUC shouldn't be needed.
347 # console by default, so WUC shouldn't be needed.
346 return
348 return
347
349
348 import win_unicode_console
350 import win_unicode_console
349 win_unicode_console.enable()
351 win_unicode_console.enable()
350
352
351 def init_io(self):
353 def init_io(self):
352 if sys.platform not in {'win32', 'cli'}:
354 if sys.platform not in {'win32', 'cli'}:
353 return
355 return
354
356
355 self.enable_win_unicode_console()
357 self.enable_win_unicode_console()
356
358
357 import colorama
359 import colorama
358 colorama.init()
360 colorama.init()
359
361
360 # For some reason we make these wrappers around stdout/stderr.
362 # For some reason we make these wrappers around stdout/stderr.
361 # For now, we need to reset them so all output gets coloured.
363 # For now, we need to reset them so all output gets coloured.
362 # https://github.com/ipython/ipython/issues/8669
364 # https://github.com/ipython/ipython/issues/8669
363 # io.std* are deprecated, but don't show our own deprecation warnings
365 # io.std* are deprecated, but don't show our own deprecation warnings
364 # during initialization of the deprecated API.
366 # during initialization of the deprecated API.
365 with warnings.catch_warnings():
367 with warnings.catch_warnings():
366 warnings.simplefilter('ignore', DeprecationWarning)
368 warnings.simplefilter('ignore', DeprecationWarning)
367 io.stdout = io.IOStream(sys.stdout)
369 io.stdout = io.IOStream(sys.stdout)
368 io.stderr = io.IOStream(sys.stderr)
370 io.stderr = io.IOStream(sys.stderr)
369
371
370 def init_magics(self):
372 def init_magics(self):
371 super(TerminalInteractiveShell, self).init_magics()
373 super(TerminalInteractiveShell, self).init_magics()
372 self.register_magics(TerminalMagics)
374 self.register_magics(TerminalMagics)
373
375
374 def init_alias(self):
376 def init_alias(self):
375 # The parent class defines aliases that can be safely used with any
377 # The parent class defines aliases that can be safely used with any
376 # frontend.
378 # frontend.
377 super(TerminalInteractiveShell, self).init_alias()
379 super(TerminalInteractiveShell, self).init_alias()
378
380
379 # Now define aliases that only make sense on the terminal, because they
381 # Now define aliases that only make sense on the terminal, because they
380 # need direct access to the console in a way that we can't emulate in
382 # need direct access to the console in a way that we can't emulate in
381 # GUI or web frontend
383 # GUI or web frontend
382 if os.name == 'posix':
384 if os.name == 'posix':
383 for cmd in ['clear', 'more', 'less', 'man']:
385 for cmd in ['clear', 'more', 'less', 'man']:
384 self.alias_manager.soft_define_alias(cmd, cmd)
386 self.alias_manager.soft_define_alias(cmd, cmd)
385
387
386
388
387 def __init__(self, *args, **kwargs):
389 def __init__(self, *args, **kwargs):
388 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
390 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
389 self.init_prompt_toolkit_cli()
391 self.init_prompt_toolkit_cli()
390 self.init_term_title()
392 self.init_term_title()
391 self.keep_running = True
393 self.keep_running = True
392
394
393 self.debugger_history = InMemoryHistory()
395 self.debugger_history = InMemoryHistory()
394
396
395 def ask_exit(self):
397 def ask_exit(self):
396 self.keep_running = False
398 self.keep_running = False
397
399
398 rl_next_input = None
400 rl_next_input = None
399
401
400 def pre_prompt(self):
402 def pre_prompt(self):
401 if self.rl_next_input:
403 if self.rl_next_input:
402 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
404 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
403 self.rl_next_input = None
405 self.rl_next_input = None
404
406
405 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
407 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
406
408
407 if display_banner is not DISPLAY_BANNER_DEPRECATED:
409 if display_banner is not DISPLAY_BANNER_DEPRECATED:
408 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
410 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
409
411
410 self.keep_running = True
412 self.keep_running = True
411 while self.keep_running:
413 while self.keep_running:
412 print(self.separate_in, end='')
414 print(self.separate_in, end='')
413
415
414 try:
416 try:
415 code = self.prompt_for_code()
417 code = self.prompt_for_code()
416 except EOFError:
418 except EOFError:
417 if (not self.confirm_exit) \
419 if (not self.confirm_exit) \
418 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
420 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
419 self.ask_exit()
421 self.ask_exit()
420
422
421 else:
423 else:
422 if code:
424 if code:
423 self.run_cell(code, store_history=True)
425 self.run_cell(code, store_history=True)
424
426
425 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
427 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
426 # An extra layer of protection in case someone mashing Ctrl-C breaks
428 # An extra layer of protection in case someone mashing Ctrl-C breaks
427 # out of our internal code.
429 # out of our internal code.
428 if display_banner is not DISPLAY_BANNER_DEPRECATED:
430 if display_banner is not DISPLAY_BANNER_DEPRECATED:
429 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
431 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
430 while True:
432 while True:
431 try:
433 try:
432 self.interact()
434 self.interact()
433 break
435 break
434 except KeyboardInterrupt as e:
436 except KeyboardInterrupt as e:
435 print("\n%s escaped interact()\n" % type(e).__name__)
437 print("\n%s escaped interact()\n" % type(e).__name__)
436 finally:
438 finally:
437 # An interrupt during the eventloop will mess up the
439 # An interrupt during the eventloop will mess up the
438 # internal state of the prompt_toolkit library.
440 # internal state of the prompt_toolkit library.
439 # Stopping the eventloop fixes this, see
441 # Stopping the eventloop fixes this, see
440 # https://github.com/ipython/ipython/pull/9867
442 # https://github.com/ipython/ipython/pull/9867
441 if hasattr(self, '_eventloop'):
443 if hasattr(self, '_eventloop'):
442 self._eventloop.stop()
444 self._eventloop.stop()
443
445
444 _inputhook = None
446 _inputhook = None
445 def inputhook(self, context):
447 def inputhook(self, context):
446 if self._inputhook is not None:
448 if self._inputhook is not None:
447 self._inputhook(context)
449 self._inputhook(context)
448
450
449 active_eventloop = None
451 active_eventloop = None
450 def enable_gui(self, gui=None):
452 def enable_gui(self, gui=None):
451 if gui:
453 if gui:
452 self.active_eventloop, self._inputhook =\
454 self.active_eventloop, self._inputhook =\
453 get_inputhook_name_and_func(gui)
455 get_inputhook_name_and_func(gui)
454 else:
456 else:
455 self.active_eventloop = self._inputhook = None
457 self.active_eventloop = self._inputhook = None
456
458
457 # Run !system commands directly, not through pipes, so terminal programs
459 # Run !system commands directly, not through pipes, so terminal programs
458 # work correctly.
460 # work correctly.
459 system = InteractiveShell.system_raw
461 system = InteractiveShell.system_raw
460
462
461 def auto_rewrite_input(self, cmd):
463 def auto_rewrite_input(self, cmd):
462 """Overridden from the parent class to use fancy rewriting prompt"""
464 """Overridden from the parent class to use fancy rewriting prompt"""
463 if not self.show_rewritten_input:
465 if not self.show_rewritten_input:
464 return
466 return
465
467
466 tokens = self.prompts.rewrite_prompt_tokens()
468 tokens = self.prompts.rewrite_prompt_tokens()
467 if self.pt_cli:
469 if self.pt_cli:
468 self.pt_cli.print_tokens(tokens)
470 self.pt_cli.print_tokens(tokens)
469 print(cmd)
471 print(cmd)
470 else:
472 else:
471 prompt = ''.join(s for t, s in tokens)
473 prompt = ''.join(s for t, s in tokens)
472 print(prompt, cmd, sep='')
474 print(prompt, cmd, sep='')
473
475
474 _prompts_before = None
476 _prompts_before = None
475 def switch_doctest_mode(self, mode):
477 def switch_doctest_mode(self, mode):
476 """Switch prompts to classic for %doctest_mode"""
478 """Switch prompts to classic for %doctest_mode"""
477 if mode:
479 if mode:
478 self._prompts_before = self.prompts
480 self._prompts_before = self.prompts
479 self.prompts = ClassicPrompts(self)
481 self.prompts = ClassicPrompts(self)
480 elif self._prompts_before:
482 elif self._prompts_before:
481 self.prompts = self._prompts_before
483 self.prompts = self._prompts_before
482 self._prompts_before = None
484 self._prompts_before = None
483 self._update_layout()
485 self._update_layout()
484
486
485
487
486 InteractiveShellABC.register(TerminalInteractiveShell)
488 InteractiveShellABC.register(TerminalInteractiveShell)
487
489
488 if __name__ == '__main__':
490 if __name__ == '__main__':
489 TerminalInteractiveShell.instance().interact()
491 TerminalInteractiveShell.instance().interact()
@@ -1,129 +1,153 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the TerminalInteractiveShell and related pieces."""
2 """Tests for the TerminalInteractiveShell and related pieces."""
3 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Copyright (C) 2011 The IPython Development Team
4 # Distributed under the terms of the Modified BSD License.
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
5
10 import sys
6 import sys
11 import unittest
7 import unittest
12
8
13 from IPython.core.inputtransformer import InputTransformer
9 from IPython.core.inputtransformer import InputTransformer
14 from IPython.testing import tools as tt
10 from IPython.testing import tools as tt
11 from IPython.utils.capture import capture_output
15
12
16 # Decorator for interaction loop tests -----------------------------------------
13 # Decorator for interaction loop tests -----------------------------------------
17
14
18 class mock_input_helper(object):
15 class mock_input_helper(object):
19 """Machinery for tests of the main interact loop.
16 """Machinery for tests of the main interact loop.
20
17
21 Used by the mock_input decorator.
18 Used by the mock_input decorator.
22 """
19 """
23 def __init__(self, testgen):
20 def __init__(self, testgen):
24 self.testgen = testgen
21 self.testgen = testgen
25 self.exception = None
22 self.exception = None
26 self.ip = get_ipython()
23 self.ip = get_ipython()
27
24
28 def __enter__(self):
25 def __enter__(self):
29 self.orig_prompt_for_code = self.ip.prompt_for_code
26 self.orig_prompt_for_code = self.ip.prompt_for_code
30 self.ip.prompt_for_code = self.fake_input
27 self.ip.prompt_for_code = self.fake_input
31 return self
28 return self
32
29
33 def __exit__(self, etype, value, tb):
30 def __exit__(self, etype, value, tb):
34 self.ip.prompt_for_code = self.orig_prompt_for_code
31 self.ip.prompt_for_code = self.orig_prompt_for_code
35
32
36 def fake_input(self):
33 def fake_input(self):
37 try:
34 try:
38 return next(self.testgen)
35 return next(self.testgen)
39 except StopIteration:
36 except StopIteration:
40 self.ip.keep_running = False
37 self.ip.keep_running = False
41 return u''
38 return u''
42 except:
39 except:
43 self.exception = sys.exc_info()
40 self.exception = sys.exc_info()
44 self.ip.keep_running = False
41 self.ip.keep_running = False
45 return u''
42 return u''
46
43
47 def mock_input(testfunc):
44 def mock_input(testfunc):
48 """Decorator for tests of the main interact loop.
45 """Decorator for tests of the main interact loop.
49
46
50 Write the test as a generator, yield-ing the input strings, which IPython
47 Write the test as a generator, yield-ing the input strings, which IPython
51 will see as if they were typed in at the prompt.
48 will see as if they were typed in at the prompt.
52 """
49 """
53 def test_method(self):
50 def test_method(self):
54 testgen = testfunc(self)
51 testgen = testfunc(self)
55 with mock_input_helper(testgen) as mih:
52 with mock_input_helper(testgen) as mih:
56 mih.ip.interact()
53 mih.ip.interact()
57
54
58 if mih.exception is not None:
55 if mih.exception is not None:
59 # Re-raise captured exception
56 # Re-raise captured exception
60 etype, value, tb = mih.exception
57 etype, value, tb = mih.exception
61 import traceback
58 import traceback
62 traceback.print_tb(tb, file=sys.stdout)
59 traceback.print_tb(tb, file=sys.stdout)
63 del tb # Avoid reference loop
60 del tb # Avoid reference loop
64 raise value
61 raise value
65
62
66 return test_method
63 return test_method
67
64
68 # Test classes -----------------------------------------------------------------
65 # Test classes -----------------------------------------------------------------
69
66
70 class InteractiveShellTestCase(unittest.TestCase):
67 class InteractiveShellTestCase(unittest.TestCase):
71 def rl_hist_entries(self, rl, n):
68 def rl_hist_entries(self, rl, n):
72 """Get last n readline history entries as a list"""
69 """Get last n readline history entries as a list"""
73 return [rl.get_history_item(rl.get_current_history_length() - x)
70 return [rl.get_history_item(rl.get_current_history_length() - x)
74 for x in range(n - 1, -1, -1)]
71 for x in range(n - 1, -1, -1)]
75
72
76 @mock_input
73 @mock_input
77 def test_inputtransformer_syntaxerror(self):
74 def test_inputtransformer_syntaxerror(self):
78 ip = get_ipython()
75 ip = get_ipython()
79 transformer = SyntaxErrorTransformer()
76 transformer = SyntaxErrorTransformer()
80 ip.input_splitter.python_line_transforms.append(transformer)
77 ip.input_splitter.python_line_transforms.append(transformer)
81 ip.input_transformer_manager.python_line_transforms.append(transformer)
78 ip.input_transformer_manager.python_line_transforms.append(transformer)
82
79
83 try:
80 try:
84 #raise Exception
81 #raise Exception
85 with tt.AssertPrints('4', suppress=False):
82 with tt.AssertPrints('4', suppress=False):
86 yield u'print(2*2)'
83 yield u'print(2*2)'
87
84
88 with tt.AssertPrints('SyntaxError: input contains', suppress=False):
85 with tt.AssertPrints('SyntaxError: input contains', suppress=False):
89 yield u'print(2345) # syntaxerror'
86 yield u'print(2345) # syntaxerror'
90
87
91 with tt.AssertPrints('16', suppress=False):
88 with tt.AssertPrints('16', suppress=False):
92 yield u'print(4*4)'
89 yield u'print(4*4)'
93
90
94 finally:
91 finally:
95 ip.input_splitter.python_line_transforms.remove(transformer)
92 ip.input_splitter.python_line_transforms.remove(transformer)
96 ip.input_transformer_manager.python_line_transforms.remove(transformer)
93 ip.input_transformer_manager.python_line_transforms.remove(transformer)
97
94
98 def test_plain_text_only(self):
95 def test_plain_text_only(self):
99 ip = get_ipython()
96 ip = get_ipython()
100 formatter = ip.display_formatter
97 formatter = ip.display_formatter
101 assert formatter.active_types == ['text/plain']
98 assert formatter.active_types == ['text/plain']
99 assert not formatter.ipython_display_formatter.enabled
100
101 class Test(object):
102 def __repr__(self):
103 return "<Test %i>" % id(self)
104
105 def _repr_html_(self):
106 return '<html>'
107
108 # verify that HTML repr isn't computed
109 obj = Test()
110 data, _ = formatter.format(obj)
111 self.assertEqual(data, {'text/plain': repr(obj)})
112
113 class Test2(Test):
114 def _ipython_display_(self):
115 from IPython.display import display
116 display('<custom>')
117
118 # verify that _ipython_display_ shortcut isn't called
119 obj = Test2()
120 with capture_output() as captured:
121 data, _ = formatter.format(obj)
122
123 self.assertEqual(data, {'text/plain': repr(obj)})
124 assert captured.stdout == ''
125
102
126
103
127
104 class SyntaxErrorTransformer(InputTransformer):
128 class SyntaxErrorTransformer(InputTransformer):
105 def push(self, line):
129 def push(self, line):
106 pos = line.find('syntaxerror')
130 pos = line.find('syntaxerror')
107 if pos >= 0:
131 if pos >= 0:
108 e = SyntaxError('input contains "syntaxerror"')
132 e = SyntaxError('input contains "syntaxerror"')
109 e.text = line
133 e.text = line
110 e.offset = pos + 1
134 e.offset = pos + 1
111 raise e
135 raise e
112 return line
136 return line
113
137
114 def reset(self):
138 def reset(self):
115 pass
139 pass
116
140
117 class TerminalMagicsTestCase(unittest.TestCase):
141 class TerminalMagicsTestCase(unittest.TestCase):
118 def test_paste_magics_blankline(self):
142 def test_paste_magics_blankline(self):
119 """Test that code with a blank line doesn't get split (gh-3246)."""
143 """Test that code with a blank line doesn't get split (gh-3246)."""
120 ip = get_ipython()
144 ip = get_ipython()
121 s = ('def pasted_func(a):\n'
145 s = ('def pasted_func(a):\n'
122 ' b = a+1\n'
146 ' b = a+1\n'
123 '\n'
147 '\n'
124 ' return b')
148 ' return b')
125
149
126 tm = ip.magics_manager.registry['TerminalMagics']
150 tm = ip.magics_manager.registry['TerminalMagics']
127 tm.store_or_execute(s, name=None)
151 tm.store_or_execute(s, name=None)
128
152
129 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
153 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
General Comments 0
You need to be logged in to leave comments. Login now