##// END OF EJS Templates
Reenable non-text formatter on terminal....
Matthias Bussonnier -
Show More
@@ -1,641 +1,639 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2
2
3 import asyncio
3 import asyncio
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 input
11 from IPython.utils.py3compat import input
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
13 from IPython.utils.process import abbrev_cwd
13 from IPython.utils.process import abbrev_cwd
14 from traitlets import (
14 from traitlets import (
15 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
15 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
16 Any, validate
16 Any, validate
17 )
17 )
18
18
19 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
19 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
20 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
20 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
21 from prompt_toolkit.formatted_text import PygmentsTokens
21 from prompt_toolkit.formatted_text import PygmentsTokens
22 from prompt_toolkit.history import InMemoryHistory
22 from prompt_toolkit.history import InMemoryHistory
23 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
24 from prompt_toolkit.output import ColorDepth
24 from prompt_toolkit.output import ColorDepth
25 from prompt_toolkit.patch_stdout import patch_stdout
25 from prompt_toolkit.patch_stdout import patch_stdout
26 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
26 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
27 from prompt_toolkit.styles import DynamicStyle, merge_styles
27 from prompt_toolkit.styles import DynamicStyle, merge_styles
28 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
28 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
29 from prompt_toolkit import __version__ as ptk_version
29 from prompt_toolkit import __version__ as ptk_version
30
30
31 from pygments.styles import get_style_by_name
31 from pygments.styles import get_style_by_name
32 from pygments.style import Style
32 from pygments.style import Style
33 from pygments.token import Token
33 from pygments.token import Token
34
34
35 from .debugger import TerminalPdb, Pdb
35 from .debugger import TerminalPdb, Pdb
36 from .magics import TerminalMagics
36 from .magics import TerminalMagics
37 from .pt_inputhooks import get_inputhook_name_and_func
37 from .pt_inputhooks import get_inputhook_name_and_func
38 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
38 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
39 from .ptutils import IPythonPTCompleter, IPythonPTLexer
39 from .ptutils import IPythonPTCompleter, IPythonPTLexer
40 from .shortcuts import create_ipython_shortcuts
40 from .shortcuts import create_ipython_shortcuts
41
41
42 DISPLAY_BANNER_DEPRECATED = object()
42 DISPLAY_BANNER_DEPRECATED = object()
43 PTK3 = ptk_version.startswith('3.')
43 PTK3 = ptk_version.startswith('3.')
44
44
45
45
46 class _NoStyle(Style): pass
46 class _NoStyle(Style): pass
47
47
48
48
49
49
50 _style_overrides_light_bg = {
50 _style_overrides_light_bg = {
51 Token.Prompt: '#0000ff',
51 Token.Prompt: '#0000ff',
52 Token.PromptNum: '#0000ee bold',
52 Token.PromptNum: '#0000ee bold',
53 Token.OutPrompt: '#cc0000',
53 Token.OutPrompt: '#cc0000',
54 Token.OutPromptNum: '#bb0000 bold',
54 Token.OutPromptNum: '#bb0000 bold',
55 }
55 }
56
56
57 _style_overrides_linux = {
57 _style_overrides_linux = {
58 Token.Prompt: '#00cc00',
58 Token.Prompt: '#00cc00',
59 Token.PromptNum: '#00bb00 bold',
59 Token.PromptNum: '#00bb00 bold',
60 Token.OutPrompt: '#cc0000',
60 Token.OutPrompt: '#cc0000',
61 Token.OutPromptNum: '#bb0000 bold',
61 Token.OutPromptNum: '#bb0000 bold',
62 }
62 }
63
63
64 def get_default_editor():
64 def get_default_editor():
65 try:
65 try:
66 return os.environ['EDITOR']
66 return os.environ['EDITOR']
67 except KeyError:
67 except KeyError:
68 pass
68 pass
69 except UnicodeError:
69 except UnicodeError:
70 warn("$EDITOR environment variable is not pure ASCII. Using platform "
70 warn("$EDITOR environment variable is not pure ASCII. Using platform "
71 "default editor.")
71 "default editor.")
72
72
73 if os.name == 'posix':
73 if os.name == 'posix':
74 return 'vi' # the only one guaranteed to be there!
74 return 'vi' # the only one guaranteed to be there!
75 else:
75 else:
76 return 'notepad' # same in Windows!
76 return 'notepad' # same in Windows!
77
77
78 # conservatively check for tty
78 # conservatively check for tty
79 # overridden streams can result in things like:
79 # overridden streams can result in things like:
80 # - sys.stdin = None
80 # - sys.stdin = None
81 # - no isatty method
81 # - no isatty method
82 for _name in ('stdin', 'stdout', 'stderr'):
82 for _name in ('stdin', 'stdout', 'stderr'):
83 _stream = getattr(sys, _name)
83 _stream = getattr(sys, _name)
84 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
84 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
85 _is_tty = False
85 _is_tty = False
86 break
86 break
87 else:
87 else:
88 _is_tty = True
88 _is_tty = True
89
89
90
90
91 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
91 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
92
92
93 def black_reformat_handler(text_before_cursor):
93 def black_reformat_handler(text_before_cursor):
94 import black
94 import black
95 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
95 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
96 if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'):
96 if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'):
97 formatted_text = formatted_text[:-1]
97 formatted_text = formatted_text[:-1]
98 return formatted_text
98 return formatted_text
99
99
100
100
101 class TerminalInteractiveShell(InteractiveShell):
101 class TerminalInteractiveShell(InteractiveShell):
102 mime_renderers = Dict().tag(config=True)
102 mime_renderers = Dict().tag(config=True)
103
103
104 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
104 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
105 'to reserve for the completion menu'
105 'to reserve for the completion menu'
106 ).tag(config=True)
106 ).tag(config=True)
107
107
108 pt_app = None
108 pt_app = None
109 debugger_history = None
109 debugger_history = None
110
110
111 simple_prompt = Bool(_use_simple_prompt,
111 simple_prompt = Bool(_use_simple_prompt,
112 help="""Use `raw_input` for the REPL, without completion and prompt colors.
112 help="""Use `raw_input` for the REPL, without completion and prompt colors.
113
113
114 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
114 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
115 IPython own testing machinery, and emacs inferior-shell integration through elpy.
115 IPython own testing machinery, and emacs inferior-shell integration through elpy.
116
116
117 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
117 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
118 environment variable is set, or the current terminal is not a tty."""
118 environment variable is set, or the current terminal is not a tty."""
119 ).tag(config=True)
119 ).tag(config=True)
120
120
121 @property
121 @property
122 def debugger_cls(self):
122 def debugger_cls(self):
123 return Pdb if self.simple_prompt else TerminalPdb
123 return Pdb if self.simple_prompt else TerminalPdb
124
124
125 confirm_exit = Bool(True,
125 confirm_exit = Bool(True,
126 help="""
126 help="""
127 Set to confirm when you try to exit IPython with an EOF (Control-D
127 Set to confirm when you try to exit IPython with an EOF (Control-D
128 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
128 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
129 you can force a direct exit without any confirmation.""",
129 you can force a direct exit without any confirmation.""",
130 ).tag(config=True)
130 ).tag(config=True)
131
131
132 editing_mode = Unicode('emacs',
132 editing_mode = Unicode('emacs',
133 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
133 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
134 ).tag(config=True)
134 ).tag(config=True)
135
135
136 autoformatter = Unicode(None,
136 autoformatter = Unicode(None,
137 help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`",
137 help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`",
138 allow_none=True
138 allow_none=True
139 ).tag(config=True)
139 ).tag(config=True)
140
140
141 mouse_support = Bool(False,
141 mouse_support = Bool(False,
142 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
142 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
143 ).tag(config=True)
143 ).tag(config=True)
144
144
145 # We don't load the list of styles for the help string, because loading
145 # We don't load the list of styles for the help string, because loading
146 # Pygments plugins takes time and can cause unexpected errors.
146 # Pygments plugins takes time and can cause unexpected errors.
147 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
147 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
148 help="""The name or class of a Pygments style to use for syntax
148 help="""The name or class of a Pygments style to use for syntax
149 highlighting. To see available styles, run `pygmentize -L styles`."""
149 highlighting. To see available styles, run `pygmentize -L styles`."""
150 ).tag(config=True)
150 ).tag(config=True)
151
151
152 @validate('editing_mode')
152 @validate('editing_mode')
153 def _validate_editing_mode(self, proposal):
153 def _validate_editing_mode(self, proposal):
154 if proposal['value'].lower() == 'vim':
154 if proposal['value'].lower() == 'vim':
155 proposal['value']= 'vi'
155 proposal['value']= 'vi'
156 elif proposal['value'].lower() == 'default':
156 elif proposal['value'].lower() == 'default':
157 proposal['value']= 'emacs'
157 proposal['value']= 'emacs'
158
158
159 if hasattr(EditingMode, proposal['value'].upper()):
159 if hasattr(EditingMode, proposal['value'].upper()):
160 return proposal['value'].lower()
160 return proposal['value'].lower()
161
161
162 return self.editing_mode
162 return self.editing_mode
163
163
164
164
165 @observe('editing_mode')
165 @observe('editing_mode')
166 def _editing_mode(self, change):
166 def _editing_mode(self, change):
167 u_mode = change.new.upper()
167 u_mode = change.new.upper()
168 if self.pt_app:
168 if self.pt_app:
169 self.pt_app.editing_mode = u_mode
169 self.pt_app.editing_mode = u_mode
170
170
171 @observe('autoformatter')
171 @observe('autoformatter')
172 def _autoformatter_changed(self, change):
172 def _autoformatter_changed(self, change):
173 formatter = change.new
173 formatter = change.new
174 if formatter is None:
174 if formatter is None:
175 self.reformat_handler = lambda x:x
175 self.reformat_handler = lambda x:x
176 elif formatter == 'black':
176 elif formatter == 'black':
177 self.reformat_handler = black_reformat_handler
177 self.reformat_handler = black_reformat_handler
178 else:
178 else:
179 raise ValueError
179 raise ValueError
180
180
181 @observe('highlighting_style')
181 @observe('highlighting_style')
182 @observe('colors')
182 @observe('colors')
183 def _highlighting_style_changed(self, change):
183 def _highlighting_style_changed(self, change):
184 self.refresh_style()
184 self.refresh_style()
185
185
186 def refresh_style(self):
186 def refresh_style(self):
187 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
187 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
188
188
189
189
190 highlighting_style_overrides = Dict(
190 highlighting_style_overrides = Dict(
191 help="Override highlighting format for specific tokens"
191 help="Override highlighting format for specific tokens"
192 ).tag(config=True)
192 ).tag(config=True)
193
193
194 true_color = Bool(False,
194 true_color = Bool(False,
195 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
195 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
196 "If your terminal supports true color, the following command "
196 "If your terminal supports true color, the following command "
197 "should print 'TRUECOLOR' in orange: "
197 "should print 'TRUECOLOR' in orange: "
198 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
198 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
199 ).tag(config=True)
199 ).tag(config=True)
200
200
201 editor = Unicode(get_default_editor(),
201 editor = Unicode(get_default_editor(),
202 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
202 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
203 ).tag(config=True)
203 ).tag(config=True)
204
204
205 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
205 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
206
206
207 prompts = Instance(Prompts)
207 prompts = Instance(Prompts)
208
208
209 @default('prompts')
209 @default('prompts')
210 def _prompts_default(self):
210 def _prompts_default(self):
211 return self.prompts_class(self)
211 return self.prompts_class(self)
212
212
213 # @observe('prompts')
213 # @observe('prompts')
214 # def _(self, change):
214 # def _(self, change):
215 # self._update_layout()
215 # self._update_layout()
216
216
217 @default('displayhook_class')
217 @default('displayhook_class')
218 def _displayhook_class_default(self):
218 def _displayhook_class_default(self):
219 return RichPromptDisplayHook
219 return RichPromptDisplayHook
220
220
221 term_title = Bool(True,
221 term_title = Bool(True,
222 help="Automatically set the terminal title"
222 help="Automatically set the terminal title"
223 ).tag(config=True)
223 ).tag(config=True)
224
224
225 term_title_format = Unicode("IPython: {cwd}",
225 term_title_format = Unicode("IPython: {cwd}",
226 help="Customize the terminal title format. This is a python format string. " +
226 help="Customize the terminal title format. This is a python format string. " +
227 "Available substitutions are: {cwd}."
227 "Available substitutions are: {cwd}."
228 ).tag(config=True)
228 ).tag(config=True)
229
229
230 display_completions = Enum(('column', 'multicolumn','readlinelike'),
230 display_completions = Enum(('column', 'multicolumn','readlinelike'),
231 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
231 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
232 "'readlinelike'. These options are for `prompt_toolkit`, see "
232 "'readlinelike'. These options are for `prompt_toolkit`, see "
233 "`prompt_toolkit` documentation for more information."
233 "`prompt_toolkit` documentation for more information."
234 ),
234 ),
235 default_value='multicolumn').tag(config=True)
235 default_value='multicolumn').tag(config=True)
236
236
237 highlight_matching_brackets = Bool(True,
237 highlight_matching_brackets = Bool(True,
238 help="Highlight matching brackets.",
238 help="Highlight matching brackets.",
239 ).tag(config=True)
239 ).tag(config=True)
240
240
241 extra_open_editor_shortcuts = Bool(False,
241 extra_open_editor_shortcuts = Bool(False,
242 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
242 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
243 "This is in addition to the F2 binding, which is always enabled."
243 "This is in addition to the F2 binding, which is always enabled."
244 ).tag(config=True)
244 ).tag(config=True)
245
245
246 handle_return = Any(None,
246 handle_return = Any(None,
247 help="Provide an alternative handler to be called when the user presses "
247 help="Provide an alternative handler to be called when the user presses "
248 "Return. This is an advanced option intended for debugging, which "
248 "Return. This is an advanced option intended for debugging, which "
249 "may be changed or removed in later releases."
249 "may be changed or removed in later releases."
250 ).tag(config=True)
250 ).tag(config=True)
251
251
252 enable_history_search = Bool(True,
252 enable_history_search = Bool(True,
253 help="Allows to enable/disable the prompt toolkit history search"
253 help="Allows to enable/disable the prompt toolkit history search"
254 ).tag(config=True)
254 ).tag(config=True)
255
255
256 prompt_includes_vi_mode = Bool(True,
256 prompt_includes_vi_mode = Bool(True,
257 help="Display the current vi mode (when using vi editing mode)."
257 help="Display the current vi mode (when using vi editing mode)."
258 ).tag(config=True)
258 ).tag(config=True)
259
259
260 @observe('term_title')
260 @observe('term_title')
261 def init_term_title(self, change=None):
261 def init_term_title(self, change=None):
262 # Enable or disable the terminal title.
262 # Enable or disable the terminal title.
263 if self.term_title:
263 if self.term_title:
264 toggle_set_term_title(True)
264 toggle_set_term_title(True)
265 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
265 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
266 else:
266 else:
267 toggle_set_term_title(False)
267 toggle_set_term_title(False)
268
268
269 def restore_term_title(self):
269 def restore_term_title(self):
270 if self.term_title:
270 if self.term_title:
271 restore_term_title()
271 restore_term_title()
272
272
273 def init_display_formatter(self):
273 def init_display_formatter(self):
274 super(TerminalInteractiveShell, self).init_display_formatter()
274 super(TerminalInteractiveShell, self).init_display_formatter()
275 # terminal only supports plain text
275 # terminal only supports plain text
276 self.display_formatter.active_types = ['text/plain']
276 self.display_formatter.active_types = ['text/plain']
277 # disable `_ipython_display_`
278 self.display_formatter.ipython_display_formatter.enabled = False
279
277
280 def init_prompt_toolkit_cli(self):
278 def init_prompt_toolkit_cli(self):
281 if self.simple_prompt:
279 if self.simple_prompt:
282 # Fall back to plain non-interactive output for tests.
280 # Fall back to plain non-interactive output for tests.
283 # This is very limited.
281 # This is very limited.
284 def prompt():
282 def prompt():
285 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
283 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
286 lines = [input(prompt_text)]
284 lines = [input(prompt_text)]
287 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
285 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
288 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
286 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
289 lines.append( input(prompt_continuation) )
287 lines.append( input(prompt_continuation) )
290 return '\n'.join(lines)
288 return '\n'.join(lines)
291 self.prompt_for_code = prompt
289 self.prompt_for_code = prompt
292 return
290 return
293
291
294 # Set up keyboard shortcuts
292 # Set up keyboard shortcuts
295 key_bindings = create_ipython_shortcuts(self)
293 key_bindings = create_ipython_shortcuts(self)
296
294
297 # Pre-populate history from IPython's history database
295 # Pre-populate history from IPython's history database
298 history = InMemoryHistory()
296 history = InMemoryHistory()
299 last_cell = u""
297 last_cell = u""
300 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
298 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
301 include_latest=True):
299 include_latest=True):
302 # Ignore blank lines and consecutive duplicates
300 # Ignore blank lines and consecutive duplicates
303 cell = cell.rstrip()
301 cell = cell.rstrip()
304 if cell and (cell != last_cell):
302 if cell and (cell != last_cell):
305 history.append_string(cell)
303 history.append_string(cell)
306 last_cell = cell
304 last_cell = cell
307
305
308 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
306 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
309 self.style = DynamicStyle(lambda: self._style)
307 self.style = DynamicStyle(lambda: self._style)
310
308
311 editing_mode = getattr(EditingMode, self.editing_mode.upper())
309 editing_mode = getattr(EditingMode, self.editing_mode.upper())
312
310
313 self.pt_loop = asyncio.new_event_loop()
311 self.pt_loop = asyncio.new_event_loop()
314 self.pt_app = PromptSession(
312 self.pt_app = PromptSession(
315 editing_mode=editing_mode,
313 editing_mode=editing_mode,
316 key_bindings=key_bindings,
314 key_bindings=key_bindings,
317 history=history,
315 history=history,
318 completer=IPythonPTCompleter(shell=self),
316 completer=IPythonPTCompleter(shell=self),
319 enable_history_search = self.enable_history_search,
317 enable_history_search = self.enable_history_search,
320 style=self.style,
318 style=self.style,
321 include_default_pygments_style=False,
319 include_default_pygments_style=False,
322 mouse_support=self.mouse_support,
320 mouse_support=self.mouse_support,
323 enable_open_in_editor=self.extra_open_editor_shortcuts,
321 enable_open_in_editor=self.extra_open_editor_shortcuts,
324 color_depth=self.color_depth,
322 color_depth=self.color_depth,
325 tempfile_suffix=".py",
323 tempfile_suffix=".py",
326 **self._extra_prompt_options())
324 **self._extra_prompt_options())
327
325
328 def _make_style_from_name_or_cls(self, name_or_cls):
326 def _make_style_from_name_or_cls(self, name_or_cls):
329 """
327 """
330 Small wrapper that make an IPython compatible style from a style name
328 Small wrapper that make an IPython compatible style from a style name
331
329
332 We need that to add style for prompt ... etc.
330 We need that to add style for prompt ... etc.
333 """
331 """
334 style_overrides = {}
332 style_overrides = {}
335 if name_or_cls == 'legacy':
333 if name_or_cls == 'legacy':
336 legacy = self.colors.lower()
334 legacy = self.colors.lower()
337 if legacy == 'linux':
335 if legacy == 'linux':
338 style_cls = get_style_by_name('monokai')
336 style_cls = get_style_by_name('monokai')
339 style_overrides = _style_overrides_linux
337 style_overrides = _style_overrides_linux
340 elif legacy == 'lightbg':
338 elif legacy == 'lightbg':
341 style_overrides = _style_overrides_light_bg
339 style_overrides = _style_overrides_light_bg
342 style_cls = get_style_by_name('pastie')
340 style_cls = get_style_by_name('pastie')
343 elif legacy == 'neutral':
341 elif legacy == 'neutral':
344 # The default theme needs to be visible on both a dark background
342 # The default theme needs to be visible on both a dark background
345 # and a light background, because we can't tell what the terminal
343 # and a light background, because we can't tell what the terminal
346 # looks like. These tweaks to the default theme help with that.
344 # looks like. These tweaks to the default theme help with that.
347 style_cls = get_style_by_name('default')
345 style_cls = get_style_by_name('default')
348 style_overrides.update({
346 style_overrides.update({
349 Token.Number: '#007700',
347 Token.Number: '#007700',
350 Token.Operator: 'noinherit',
348 Token.Operator: 'noinherit',
351 Token.String: '#BB6622',
349 Token.String: '#BB6622',
352 Token.Name.Function: '#2080D0',
350 Token.Name.Function: '#2080D0',
353 Token.Name.Class: 'bold #2080D0',
351 Token.Name.Class: 'bold #2080D0',
354 Token.Name.Namespace: 'bold #2080D0',
352 Token.Name.Namespace: 'bold #2080D0',
355 Token.Prompt: '#009900',
353 Token.Prompt: '#009900',
356 Token.PromptNum: '#ansibrightgreen bold',
354 Token.PromptNum: '#ansibrightgreen bold',
357 Token.OutPrompt: '#990000',
355 Token.OutPrompt: '#990000',
358 Token.OutPromptNum: '#ansibrightred bold',
356 Token.OutPromptNum: '#ansibrightred bold',
359 })
357 })
360
358
361 # Hack: Due to limited color support on the Windows console
359 # Hack: Due to limited color support on the Windows console
362 # the prompt colors will be wrong without this
360 # the prompt colors will be wrong without this
363 if os.name == 'nt':
361 if os.name == 'nt':
364 style_overrides.update({
362 style_overrides.update({
365 Token.Prompt: '#ansidarkgreen',
363 Token.Prompt: '#ansidarkgreen',
366 Token.PromptNum: '#ansigreen bold',
364 Token.PromptNum: '#ansigreen bold',
367 Token.OutPrompt: '#ansidarkred',
365 Token.OutPrompt: '#ansidarkred',
368 Token.OutPromptNum: '#ansired bold',
366 Token.OutPromptNum: '#ansired bold',
369 })
367 })
370 elif legacy =='nocolor':
368 elif legacy =='nocolor':
371 style_cls=_NoStyle
369 style_cls=_NoStyle
372 style_overrides = {}
370 style_overrides = {}
373 else :
371 else :
374 raise ValueError('Got unknown colors: ', legacy)
372 raise ValueError('Got unknown colors: ', legacy)
375 else :
373 else :
376 if isinstance(name_or_cls, str):
374 if isinstance(name_or_cls, str):
377 style_cls = get_style_by_name(name_or_cls)
375 style_cls = get_style_by_name(name_or_cls)
378 else:
376 else:
379 style_cls = name_or_cls
377 style_cls = name_or_cls
380 style_overrides = {
378 style_overrides = {
381 Token.Prompt: '#009900',
379 Token.Prompt: '#009900',
382 Token.PromptNum: '#ansibrightgreen bold',
380 Token.PromptNum: '#ansibrightgreen bold',
383 Token.OutPrompt: '#990000',
381 Token.OutPrompt: '#990000',
384 Token.OutPromptNum: '#ansibrightred bold',
382 Token.OutPromptNum: '#ansibrightred bold',
385 }
383 }
386 style_overrides.update(self.highlighting_style_overrides)
384 style_overrides.update(self.highlighting_style_overrides)
387 style = merge_styles([
385 style = merge_styles([
388 style_from_pygments_cls(style_cls),
386 style_from_pygments_cls(style_cls),
389 style_from_pygments_dict(style_overrides),
387 style_from_pygments_dict(style_overrides),
390 ])
388 ])
391
389
392 return style
390 return style
393
391
394 @property
392 @property
395 def pt_complete_style(self):
393 def pt_complete_style(self):
396 return {
394 return {
397 'multicolumn': CompleteStyle.MULTI_COLUMN,
395 'multicolumn': CompleteStyle.MULTI_COLUMN,
398 'column': CompleteStyle.COLUMN,
396 'column': CompleteStyle.COLUMN,
399 'readlinelike': CompleteStyle.READLINE_LIKE,
397 'readlinelike': CompleteStyle.READLINE_LIKE,
400 }[self.display_completions]
398 }[self.display_completions]
401
399
402 @property
400 @property
403 def color_depth(self):
401 def color_depth(self):
404 return (ColorDepth.TRUE_COLOR if self.true_color else None)
402 return (ColorDepth.TRUE_COLOR if self.true_color else None)
405
403
406 def _extra_prompt_options(self):
404 def _extra_prompt_options(self):
407 """
405 """
408 Return the current layout option for the current Terminal InteractiveShell
406 Return the current layout option for the current Terminal InteractiveShell
409 """
407 """
410 def get_message():
408 def get_message():
411 return PygmentsTokens(self.prompts.in_prompt_tokens())
409 return PygmentsTokens(self.prompts.in_prompt_tokens())
412
410
413 if self.editing_mode == 'emacs':
411 if self.editing_mode == 'emacs':
414 # with emacs mode the prompt is (usually) static, so we call only
412 # with emacs mode the prompt is (usually) static, so we call only
415 # the function once. With VI mode it can toggle between [ins] and
413 # the function once. With VI mode it can toggle between [ins] and
416 # [nor] so we can't precompute.
414 # [nor] so we can't precompute.
417 # here I'm going to favor the default keybinding which almost
415 # here I'm going to favor the default keybinding which almost
418 # everybody uses to decrease CPU usage.
416 # everybody uses to decrease CPU usage.
419 # if we have issues with users with custom Prompts we can see how to
417 # if we have issues with users with custom Prompts we can see how to
420 # work around this.
418 # work around this.
421 get_message = get_message()
419 get_message = get_message()
422
420
423 options = {
421 options = {
424 'complete_in_thread': False,
422 'complete_in_thread': False,
425 'lexer':IPythonPTLexer(),
423 'lexer':IPythonPTLexer(),
426 'reserve_space_for_menu':self.space_for_menu,
424 'reserve_space_for_menu':self.space_for_menu,
427 'message': get_message,
425 'message': get_message,
428 'prompt_continuation': (
426 'prompt_continuation': (
429 lambda width, lineno, is_soft_wrap:
427 lambda width, lineno, is_soft_wrap:
430 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
428 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
431 'multiline': True,
429 'multiline': True,
432 'complete_style': self.pt_complete_style,
430 'complete_style': self.pt_complete_style,
433
431
434 # Highlight matching brackets, but only when this setting is
432 # Highlight matching brackets, but only when this setting is
435 # enabled, and only when the DEFAULT_BUFFER has the focus.
433 # enabled, and only when the DEFAULT_BUFFER has the focus.
436 'input_processors': [ConditionalProcessor(
434 'input_processors': [ConditionalProcessor(
437 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
435 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
438 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
436 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
439 Condition(lambda: self.highlight_matching_brackets))],
437 Condition(lambda: self.highlight_matching_brackets))],
440 }
438 }
441 if not PTK3:
439 if not PTK3:
442 options['inputhook'] = self.inputhook
440 options['inputhook'] = self.inputhook
443
441
444 return options
442 return options
445
443
446 def prompt_for_code(self):
444 def prompt_for_code(self):
447 if self.rl_next_input:
445 if self.rl_next_input:
448 default = self.rl_next_input
446 default = self.rl_next_input
449 self.rl_next_input = None
447 self.rl_next_input = None
450 else:
448 else:
451 default = ''
449 default = ''
452
450
453 # In order to make sure that asyncio code written in the
451 # In order to make sure that asyncio code written in the
454 # interactive shell doesn't interfere with the prompt, we run the
452 # interactive shell doesn't interfere with the prompt, we run the
455 # prompt in a different event loop.
453 # prompt in a different event loop.
456 # If we don't do this, people could spawn coroutine with a
454 # If we don't do this, people could spawn coroutine with a
457 # while/true inside which will freeze the prompt.
455 # while/true inside which will freeze the prompt.
458
456
459 try:
457 try:
460 old_loop = asyncio.get_event_loop()
458 old_loop = asyncio.get_event_loop()
461 except RuntimeError:
459 except RuntimeError:
462 # This happens when the user used `asyncio.run()`.
460 # This happens when the user used `asyncio.run()`.
463 old_loop = None
461 old_loop = None
464
462
465 asyncio.set_event_loop(self.pt_loop)
463 asyncio.set_event_loop(self.pt_loop)
466 try:
464 try:
467 with patch_stdout(raw=True):
465 with patch_stdout(raw=True):
468 text = self.pt_app.prompt(
466 text = self.pt_app.prompt(
469 default=default,
467 default=default,
470 **self._extra_prompt_options())
468 **self._extra_prompt_options())
471 finally:
469 finally:
472 # Restore the original event loop.
470 # Restore the original event loop.
473 asyncio.set_event_loop(old_loop)
471 asyncio.set_event_loop(old_loop)
474
472
475 return text
473 return text
476
474
477 def enable_win_unicode_console(self):
475 def enable_win_unicode_console(self):
478 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
476 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
479 # console by default, so WUC shouldn't be needed.
477 # console by default, so WUC shouldn't be needed.
480 from warnings import warn
478 from warnings import warn
481 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
479 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
482 DeprecationWarning,
480 DeprecationWarning,
483 stacklevel=2)
481 stacklevel=2)
484
482
485 def init_io(self):
483 def init_io(self):
486 if sys.platform not in {'win32', 'cli'}:
484 if sys.platform not in {'win32', 'cli'}:
487 return
485 return
488
486
489 import colorama
487 import colorama
490 colorama.init()
488 colorama.init()
491
489
492 # For some reason we make these wrappers around stdout/stderr.
490 # For some reason we make these wrappers around stdout/stderr.
493 # For now, we need to reset them so all output gets coloured.
491 # For now, we need to reset them so all output gets coloured.
494 # https://github.com/ipython/ipython/issues/8669
492 # https://github.com/ipython/ipython/issues/8669
495 # io.std* are deprecated, but don't show our own deprecation warnings
493 # io.std* are deprecated, but don't show our own deprecation warnings
496 # during initialization of the deprecated API.
494 # during initialization of the deprecated API.
497 with warnings.catch_warnings():
495 with warnings.catch_warnings():
498 warnings.simplefilter('ignore', DeprecationWarning)
496 warnings.simplefilter('ignore', DeprecationWarning)
499 io.stdout = io.IOStream(sys.stdout)
497 io.stdout = io.IOStream(sys.stdout)
500 io.stderr = io.IOStream(sys.stderr)
498 io.stderr = io.IOStream(sys.stderr)
501
499
502 def init_magics(self):
500 def init_magics(self):
503 super(TerminalInteractiveShell, self).init_magics()
501 super(TerminalInteractiveShell, self).init_magics()
504 self.register_magics(TerminalMagics)
502 self.register_magics(TerminalMagics)
505
503
506 def init_alias(self):
504 def init_alias(self):
507 # The parent class defines aliases that can be safely used with any
505 # The parent class defines aliases that can be safely used with any
508 # frontend.
506 # frontend.
509 super(TerminalInteractiveShell, self).init_alias()
507 super(TerminalInteractiveShell, self).init_alias()
510
508
511 # Now define aliases that only make sense on the terminal, because they
509 # Now define aliases that only make sense on the terminal, because they
512 # need direct access to the console in a way that we can't emulate in
510 # need direct access to the console in a way that we can't emulate in
513 # GUI or web frontend
511 # GUI or web frontend
514 if os.name == 'posix':
512 if os.name == 'posix':
515 for cmd in ('clear', 'more', 'less', 'man'):
513 for cmd in ('clear', 'more', 'less', 'man'):
516 self.alias_manager.soft_define_alias(cmd, cmd)
514 self.alias_manager.soft_define_alias(cmd, cmd)
517
515
518
516
519 def __init__(self, *args, **kwargs):
517 def __init__(self, *args, **kwargs):
520 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
518 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
521 self.init_prompt_toolkit_cli()
519 self.init_prompt_toolkit_cli()
522 self.init_term_title()
520 self.init_term_title()
523 self.keep_running = True
521 self.keep_running = True
524
522
525 self.debugger_history = InMemoryHistory()
523 self.debugger_history = InMemoryHistory()
526
524
527 def ask_exit(self):
525 def ask_exit(self):
528 self.keep_running = False
526 self.keep_running = False
529
527
530 rl_next_input = None
528 rl_next_input = None
531
529
532 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
530 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
533
531
534 if display_banner is not DISPLAY_BANNER_DEPRECATED:
532 if display_banner is not DISPLAY_BANNER_DEPRECATED:
535 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
533 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
536
534
537 self.keep_running = True
535 self.keep_running = True
538 while self.keep_running:
536 while self.keep_running:
539 print(self.separate_in, end='')
537 print(self.separate_in, end='')
540
538
541 try:
539 try:
542 code = self.prompt_for_code()
540 code = self.prompt_for_code()
543 except EOFError:
541 except EOFError:
544 if (not self.confirm_exit) \
542 if (not self.confirm_exit) \
545 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
543 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
546 self.ask_exit()
544 self.ask_exit()
547
545
548 else:
546 else:
549 if code:
547 if code:
550 self.run_cell(code, store_history=True)
548 self.run_cell(code, store_history=True)
551
549
552 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
550 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
553 # An extra layer of protection in case someone mashing Ctrl-C breaks
551 # An extra layer of protection in case someone mashing Ctrl-C breaks
554 # out of our internal code.
552 # out of our internal code.
555 if display_banner is not DISPLAY_BANNER_DEPRECATED:
553 if display_banner is not DISPLAY_BANNER_DEPRECATED:
556 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
554 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
557 while True:
555 while True:
558 try:
556 try:
559 self.interact()
557 self.interact()
560 break
558 break
561 except KeyboardInterrupt as e:
559 except KeyboardInterrupt as e:
562 print("\n%s escaped interact()\n" % type(e).__name__)
560 print("\n%s escaped interact()\n" % type(e).__name__)
563 finally:
561 finally:
564 # An interrupt during the eventloop will mess up the
562 # An interrupt during the eventloop will mess up the
565 # internal state of the prompt_toolkit library.
563 # internal state of the prompt_toolkit library.
566 # Stopping the eventloop fixes this, see
564 # Stopping the eventloop fixes this, see
567 # https://github.com/ipython/ipython/pull/9867
565 # https://github.com/ipython/ipython/pull/9867
568 if hasattr(self, '_eventloop'):
566 if hasattr(self, '_eventloop'):
569 self._eventloop.stop()
567 self._eventloop.stop()
570
568
571 self.restore_term_title()
569 self.restore_term_title()
572
570
573
571
574 _inputhook = None
572 _inputhook = None
575 def inputhook(self, context):
573 def inputhook(self, context):
576 if self._inputhook is not None:
574 if self._inputhook is not None:
577 self._inputhook(context)
575 self._inputhook(context)
578
576
579 active_eventloop = None
577 active_eventloop = None
580 def enable_gui(self, gui=None):
578 def enable_gui(self, gui=None):
581 if gui and (gui != 'inline') :
579 if gui and (gui != 'inline') :
582 self.active_eventloop, self._inputhook =\
580 self.active_eventloop, self._inputhook =\
583 get_inputhook_name_and_func(gui)
581 get_inputhook_name_and_func(gui)
584 else:
582 else:
585 self.active_eventloop = self._inputhook = None
583 self.active_eventloop = self._inputhook = None
586
584
587 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
585 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
588 # this inputhook.
586 # this inputhook.
589 if PTK3:
587 if PTK3:
590 import asyncio
588 import asyncio
591 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
589 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
592
590
593 if gui == 'asyncio':
591 if gui == 'asyncio':
594 # When we integrate the asyncio event loop, run the UI in the
592 # When we integrate the asyncio event loop, run the UI in the
595 # same event loop as the rest of the code. don't use an actual
593 # same event loop as the rest of the code. don't use an actual
596 # input hook. (Asyncio is not made for nesting event loops.)
594 # input hook. (Asyncio is not made for nesting event loops.)
597 self.pt_loop = asyncio.get_event_loop()
595 self.pt_loop = asyncio.get_event_loop()
598
596
599 elif self._inputhook:
597 elif self._inputhook:
600 # If an inputhook was set, create a new asyncio event loop with
598 # If an inputhook was set, create a new asyncio event loop with
601 # this inputhook for the prompt.
599 # this inputhook for the prompt.
602 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
600 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
603 else:
601 else:
604 # When there's no inputhook, run the prompt in a separate
602 # When there's no inputhook, run the prompt in a separate
605 # asyncio event loop.
603 # asyncio event loop.
606 self.pt_loop = asyncio.new_event_loop()
604 self.pt_loop = asyncio.new_event_loop()
607
605
608 # Run !system commands directly, not through pipes, so terminal programs
606 # Run !system commands directly, not through pipes, so terminal programs
609 # work correctly.
607 # work correctly.
610 system = InteractiveShell.system_raw
608 system = InteractiveShell.system_raw
611
609
612 def auto_rewrite_input(self, cmd):
610 def auto_rewrite_input(self, cmd):
613 """Overridden from the parent class to use fancy rewriting prompt"""
611 """Overridden from the parent class to use fancy rewriting prompt"""
614 if not self.show_rewritten_input:
612 if not self.show_rewritten_input:
615 return
613 return
616
614
617 tokens = self.prompts.rewrite_prompt_tokens()
615 tokens = self.prompts.rewrite_prompt_tokens()
618 if self.pt_app:
616 if self.pt_app:
619 print_formatted_text(PygmentsTokens(tokens), end='',
617 print_formatted_text(PygmentsTokens(tokens), end='',
620 style=self.pt_app.app.style)
618 style=self.pt_app.app.style)
621 print(cmd)
619 print(cmd)
622 else:
620 else:
623 prompt = ''.join(s for t, s in tokens)
621 prompt = ''.join(s for t, s in tokens)
624 print(prompt, cmd, sep='')
622 print(prompt, cmd, sep='')
625
623
626 _prompts_before = None
624 _prompts_before = None
627 def switch_doctest_mode(self, mode):
625 def switch_doctest_mode(self, mode):
628 """Switch prompts to classic for %doctest_mode"""
626 """Switch prompts to classic for %doctest_mode"""
629 if mode:
627 if mode:
630 self._prompts_before = self.prompts
628 self._prompts_before = self.prompts
631 self.prompts = ClassicPrompts(self)
629 self.prompts = ClassicPrompts(self)
632 elif self._prompts_before:
630 elif self._prompts_before:
633 self.prompts = self._prompts_before
631 self.prompts = self._prompts_before
634 self._prompts_before = None
632 self._prompts_before = None
635 # self._update_layout()
633 # self._update_layout()
636
634
637
635
638 InteractiveShellABC.register(TerminalInteractiveShell)
636 InteractiveShellABC.register(TerminalInteractiveShell)
639
637
640 if __name__ == '__main__':
638 if __name__ == '__main__':
641 TerminalInteractiveShell.instance().interact()
639 TerminalInteractiveShell.instance().interact()
@@ -1,193 +1,207 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 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import sys
6 import sys
7 import unittest
7 import unittest
8 import os
8 import os
9
9
10 from IPython.core.inputtransformer import InputTransformer
10 from IPython.core.inputtransformer import InputTransformer
11 from IPython.testing import tools as tt
11 from IPython.testing import tools as tt
12 from IPython.utils.capture import capture_output
12 from IPython.utils.capture import capture_output
13
13
14 from IPython.terminal.ptutils import _elide, _adjust_completion_text_based_on_context
14 from IPython.terminal.ptutils import _elide, _adjust_completion_text_based_on_context
15 import nose.tools as nt
15 import nose.tools as nt
16
16
17 class TestElide(unittest.TestCase):
17 class TestElide(unittest.TestCase):
18
18
19 def test_elide(self):
19 def test_elide(self):
20 _elide('concatenate((a1, a2, ...), axis', '') # do not raise
20 _elide('concatenate((a1, a2, ...), axis', '') # do not raise
21 _elide('concatenate((a1, a2, ..), . axis', '') # do not raise
21 _elide('concatenate((a1, a2, ..), . axis', '') # do not raise
22 nt.assert_equal(_elide('aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh',''), 'aaaa.b…g.hhhhhh')
22 nt.assert_equal(_elide('aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh',''), 'aaaa.b…g.hhhhhh')
23
23
24 test_string = os.sep.join(['', 10*'a', 10*'b', 10*'c', ''])
24 test_string = os.sep.join(['', 10*'a', 10*'b', 10*'c', ''])
25 expect_stirng = os.sep + 'a' + '\N{HORIZONTAL ELLIPSIS}' + 'b' + os.sep + 10*'c'
25 expect_stirng = os.sep + 'a' + '\N{HORIZONTAL ELLIPSIS}' + 'b' + os.sep + 10*'c'
26 nt.assert_equal(_elide(test_string, ''), expect_stirng)
26 nt.assert_equal(_elide(test_string, ''), expect_stirng)
27
27
28 def test_elide_typed_normal(self):
28 def test_elide_typed_normal(self):
29 nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the quick brown fox', min_elide=10), 'the…fox jumped over the lazy dog')
29 nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the quick brown fox', min_elide=10), 'the…fox jumped over the lazy dog')
30
30
31
31
32 def test_elide_typed_short_match(self):
32 def test_elide_typed_short_match(self):
33 """
33 """
34 if the match is too short we don't elide.
34 if the match is too short we don't elide.
35 avoid the "the...the"
35 avoid the "the...the"
36 """
36 """
37 nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the', min_elide=10), 'the quick brown fox jumped over the lazy dog')
37 nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the', min_elide=10), 'the quick brown fox jumped over the lazy dog')
38
38
39 def test_elide_typed_no_match(self):
39 def test_elide_typed_no_match(self):
40 """
40 """
41 if the match is too short we don't elide.
41 if the match is too short we don't elide.
42 avoid the "the...the"
42 avoid the "the...the"
43 """
43 """
44 # here we typed red instead of brown
44 # here we typed red instead of brown
45 nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the quick red fox', min_elide=10), 'the quick brown fox jumped over the lazy dog')
45 nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the quick red fox', min_elide=10), 'the quick brown fox jumped over the lazy dog')
46
46
47 class TestContextAwareCompletion(unittest.TestCase):
47 class TestContextAwareCompletion(unittest.TestCase):
48
48
49 def test_adjust_completion_text_based_on_context(self):
49 def test_adjust_completion_text_based_on_context(self):
50 # Adjusted case
50 # Adjusted case
51 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a=)', 7), 'arg1')
51 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a=)', 7), 'arg1')
52
52
53 # Untouched cases
53 # Untouched cases
54 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a)', 7), 'arg1=')
54 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a)', 7), 'arg1=')
55 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a', 7), 'arg1=')
55 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a', 7), 'arg1=')
56 nt.assert_equal(_adjust_completion_text_based_on_context('%magic', 'func1(a=)', 7), '%magic')
56 nt.assert_equal(_adjust_completion_text_based_on_context('%magic', 'func1(a=)', 7), '%magic')
57 nt.assert_equal(_adjust_completion_text_based_on_context('func2', 'func1(a=)', 7), 'func2')
57 nt.assert_equal(_adjust_completion_text_based_on_context('func2', 'func1(a=)', 7), 'func2')
58
58
59 # Decorator for interaction loop tests -----------------------------------------
59 # Decorator for interaction loop tests -----------------------------------------
60
60
61 class mock_input_helper(object):
61 class mock_input_helper(object):
62 """Machinery for tests of the main interact loop.
62 """Machinery for tests of the main interact loop.
63
63
64 Used by the mock_input decorator.
64 Used by the mock_input decorator.
65 """
65 """
66 def __init__(self, testgen):
66 def __init__(self, testgen):
67 self.testgen = testgen
67 self.testgen = testgen
68 self.exception = None
68 self.exception = None
69 self.ip = get_ipython()
69 self.ip = get_ipython()
70
70
71 def __enter__(self):
71 def __enter__(self):
72 self.orig_prompt_for_code = self.ip.prompt_for_code
72 self.orig_prompt_for_code = self.ip.prompt_for_code
73 self.ip.prompt_for_code = self.fake_input
73 self.ip.prompt_for_code = self.fake_input
74 return self
74 return self
75
75
76 def __exit__(self, etype, value, tb):
76 def __exit__(self, etype, value, tb):
77 self.ip.prompt_for_code = self.orig_prompt_for_code
77 self.ip.prompt_for_code = self.orig_prompt_for_code
78
78
79 def fake_input(self):
79 def fake_input(self):
80 try:
80 try:
81 return next(self.testgen)
81 return next(self.testgen)
82 except StopIteration:
82 except StopIteration:
83 self.ip.keep_running = False
83 self.ip.keep_running = False
84 return u''
84 return u''
85 except:
85 except:
86 self.exception = sys.exc_info()
86 self.exception = sys.exc_info()
87 self.ip.keep_running = False
87 self.ip.keep_running = False
88 return u''
88 return u''
89
89
90 def mock_input(testfunc):
90 def mock_input(testfunc):
91 """Decorator for tests of the main interact loop.
91 """Decorator for tests of the main interact loop.
92
92
93 Write the test as a generator, yield-ing the input strings, which IPython
93 Write the test as a generator, yield-ing the input strings, which IPython
94 will see as if they were typed in at the prompt.
94 will see as if they were typed in at the prompt.
95 """
95 """
96 def test_method(self):
96 def test_method(self):
97 testgen = testfunc(self)
97 testgen = testfunc(self)
98 with mock_input_helper(testgen) as mih:
98 with mock_input_helper(testgen) as mih:
99 mih.ip.interact()
99 mih.ip.interact()
100
100
101 if mih.exception is not None:
101 if mih.exception is not None:
102 # Re-raise captured exception
102 # Re-raise captured exception
103 etype, value, tb = mih.exception
103 etype, value, tb = mih.exception
104 import traceback
104 import traceback
105 traceback.print_tb(tb, file=sys.stdout)
105 traceback.print_tb(tb, file=sys.stdout)
106 del tb # Avoid reference loop
106 del tb # Avoid reference loop
107 raise value
107 raise value
108
108
109 return test_method
109 return test_method
110
110
111 # Test classes -----------------------------------------------------------------
111 # Test classes -----------------------------------------------------------------
112
112
113 class InteractiveShellTestCase(unittest.TestCase):
113 class InteractiveShellTestCase(unittest.TestCase):
114 def rl_hist_entries(self, rl, n):
114 def rl_hist_entries(self, rl, n):
115 """Get last n readline history entries as a list"""
115 """Get last n readline history entries as a list"""
116 return [rl.get_history_item(rl.get_current_history_length() - x)
116 return [rl.get_history_item(rl.get_current_history_length() - x)
117 for x in range(n - 1, -1, -1)]
117 for x in range(n - 1, -1, -1)]
118
118
119 @mock_input
119 @mock_input
120 def test_inputtransformer_syntaxerror(self):
120 def test_inputtransformer_syntaxerror(self):
121 ip = get_ipython()
121 ip = get_ipython()
122 ip.input_transformers_post.append(syntax_error_transformer)
122 ip.input_transformers_post.append(syntax_error_transformer)
123
123
124 try:
124 try:
125 #raise Exception
125 #raise Exception
126 with tt.AssertPrints('4', suppress=False):
126 with tt.AssertPrints('4', suppress=False):
127 yield u'print(2*2)'
127 yield u'print(2*2)'
128
128
129 with tt.AssertPrints('SyntaxError: input contains', suppress=False):
129 with tt.AssertPrints('SyntaxError: input contains', suppress=False):
130 yield u'print(2345) # syntaxerror'
130 yield u'print(2345) # syntaxerror'
131
131
132 with tt.AssertPrints('16', suppress=False):
132 with tt.AssertPrints('16', suppress=False):
133 yield u'print(4*4)'
133 yield u'print(4*4)'
134
134
135 finally:
135 finally:
136 ip.input_transformers_post.remove(syntax_error_transformer)
136 ip.input_transformers_post.remove(syntax_error_transformer)
137
137
138 def test_plain_text_only(self):
138 def test_repl_not_plain_text(self):
139 ip = get_ipython()
139 ip = get_ipython()
140 formatter = ip.display_formatter
140 formatter = ip.display_formatter
141 assert formatter.active_types == ['text/plain']
141 assert formatter.active_types == ['text/plain']
142 assert not formatter.ipython_display_formatter.enabled
142
143 # terminal may have arbitrary mimetype handler to open external viewer
144 # or inline images.
145 assert formatter.ipython_display_formatter.enabled
143
146
144 class Test(object):
147 class Test(object):
145 def __repr__(self):
148 def __repr__(self):
146 return "<Test %i>" % id(self)
149 return "<Test %i>" % id(self)
147
150
148 def _repr_html_(self):
151 def _repr_html_(self):
149 return '<html>'
152 return '<html>'
150
153
151 # verify that HTML repr isn't computed
154 # verify that HTML repr isn't computed
152 obj = Test()
155 obj = Test()
153 data, _ = formatter.format(obj)
156 data, _ = formatter.format(obj)
154 self.assertEqual(data, {'text/plain': repr(obj)})
157 self.assertEqual(data, {'text/plain': repr(obj)})
155
158
156 class Test2(Test):
159 class Test2(Test):
157 def _ipython_display_(self):
160 def _ipython_display_(self):
158 from IPython.display import display
161 from IPython.display import display, HTML
159 display('<custom>')
162 display(HTML('<custom>'))
160
163
161 # verify that _ipython_display_ shortcut isn't called
164 # verify that mimehandlers are called
162 obj = Test2()
165 called = False
163 with capture_output() as captured:
164 data, _ = formatter.format(obj)
165
166
166 self.assertEqual(data, {'text/plain': repr(obj)})
167 def handler(data, metadata):
167 assert captured.stdout == ''
168 print('Handler called')
169 nonlocal called
170 called = True
171
172 ip.display_formatter.active_types.append("text/html")
173 ip.display_formatter.formatters["text/html"].enabled = True
174 ip.mime_renderers["text/html"] = handler
175
176
177 obj = Test()
178 display(obj)
179
180 assert called == True
181
168
182
169 def syntax_error_transformer(lines):
183 def syntax_error_transformer(lines):
170 """Transformer that throws SyntaxError if 'syntaxerror' is in the code."""
184 """Transformer that throws SyntaxError if 'syntaxerror' is in the code."""
171 for line in lines:
185 for line in lines:
172 pos = line.find('syntaxerror')
186 pos = line.find('syntaxerror')
173 if pos >= 0:
187 if pos >= 0:
174 e = SyntaxError('input contains "syntaxerror"')
188 e = SyntaxError('input contains "syntaxerror"')
175 e.text = line
189 e.text = line
176 e.offset = pos + 1
190 e.offset = pos + 1
177 raise e
191 raise e
178 return lines
192 return lines
179
193
180
194
181 class TerminalMagicsTestCase(unittest.TestCase):
195 class TerminalMagicsTestCase(unittest.TestCase):
182 def test_paste_magics_blankline(self):
196 def test_paste_magics_blankline(self):
183 """Test that code with a blank line doesn't get split (gh-3246)."""
197 """Test that code with a blank line doesn't get split (gh-3246)."""
184 ip = get_ipython()
198 ip = get_ipython()
185 s = ('def pasted_func(a):\n'
199 s = ('def pasted_func(a):\n'
186 ' b = a+1\n'
200 ' b = a+1\n'
187 '\n'
201 '\n'
188 ' return b')
202 ' return b')
189
203
190 tm = ip.magics_manager.registry['TerminalMagics']
204 tm = ip.magics_manager.registry['TerminalMagics']
191 tm.store_or_execute(s, name=None)
205 tm.store_or_execute(s, name=None)
192
206
193 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
207 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
General Comments 0
You need to be logged in to leave comments. Login now