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