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