##// END OF EJS Templates
pager payload is a mime-bundle
MinRK -
Show More
@@ -1,353 +1,357 b''
1 1 # encoding: utf-8
2 2 """
3 3 Paging capabilities for IPython.core
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Fernando Perez
9 9
10 10 Notes
11 11 -----
12 12
13 13 For now this uses ipapi, so it can't be in IPython.utils. If we can get
14 14 rid of that dependency, we could move it there.
15 15 -----
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Copyright (C) 2008-2011 The IPython Development Team
20 20 #
21 21 # Distributed under the terms of the BSD License. The full license is in
22 22 # the file COPYING, distributed as part of this software.
23 23 #-----------------------------------------------------------------------------
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Imports
27 27 #-----------------------------------------------------------------------------
28 28 from __future__ import print_function
29 29
30 30 import os
31 31 import re
32 32 import sys
33 33 import tempfile
34 34
35 35 from io import UnsupportedOperation
36 36
37 37 from IPython import get_ipython
38 38 from IPython.core.error import TryNext
39 39 from IPython.utils.data import chop
40 40 from IPython.utils import io
41 41 from IPython.utils.process import system
42 42 from IPython.utils.terminal import get_terminal_size
43 43 from IPython.utils import py3compat
44 44
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Classes and functions
48 48 #-----------------------------------------------------------------------------
49 49
50 50 esc_re = re.compile(r"(\x1b[^m]+m)")
51 51
52 52 def page_dumb(strng, start=0, screen_lines=25):
53 53 """Very dumb 'pager' in Python, for when nothing else works.
54 54
55 55 Only moves forward, same interface as page(), except for pager_cmd and
56 56 mode."""
57 57
58 58 out_ln = strng.splitlines()[start:]
59 59 screens = chop(out_ln,screen_lines-1)
60 60 if len(screens) == 1:
61 61 print(os.linesep.join(screens[0]), file=io.stdout)
62 62 else:
63 63 last_escape = ""
64 64 for scr in screens[0:-1]:
65 65 hunk = os.linesep.join(scr)
66 66 print(last_escape + hunk, file=io.stdout)
67 67 if not page_more():
68 68 return
69 69 esc_list = esc_re.findall(hunk)
70 70 if len(esc_list) > 0:
71 71 last_escape = esc_list[-1]
72 72 print(last_escape + os.linesep.join(screens[-1]), file=io.stdout)
73 73
74 74 def _detect_screen_size(screen_lines_def):
75 75 """Attempt to work out the number of lines on the screen.
76 76
77 77 This is called by page(). It can raise an error (e.g. when run in the
78 78 test suite), so it's separated out so it can easily be called in a try block.
79 79 """
80 80 TERM = os.environ.get('TERM',None)
81 81 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
82 82 # curses causes problems on many terminals other than xterm, and
83 83 # some termios calls lock up on Sun OS5.
84 84 return screen_lines_def
85 85
86 86 try:
87 87 import termios
88 88 import curses
89 89 except ImportError:
90 90 return screen_lines_def
91 91
92 92 # There is a bug in curses, where *sometimes* it fails to properly
93 93 # initialize, and then after the endwin() call is made, the
94 94 # terminal is left in an unusable state. Rather than trying to
95 95 # check everytime for this (by requesting and comparing termios
96 96 # flags each time), we just save the initial terminal state and
97 97 # unconditionally reset it every time. It's cheaper than making
98 98 # the checks.
99 99 term_flags = termios.tcgetattr(sys.stdout)
100 100
101 101 # Curses modifies the stdout buffer size by default, which messes
102 102 # up Python's normal stdout buffering. This would manifest itself
103 103 # to IPython users as delayed printing on stdout after having used
104 104 # the pager.
105 105 #
106 106 # We can prevent this by manually setting the NCURSES_NO_SETBUF
107 107 # environment variable. For more details, see:
108 108 # http://bugs.python.org/issue10144
109 109 NCURSES_NO_SETBUF = os.environ.get('NCURSES_NO_SETBUF', None)
110 110 os.environ['NCURSES_NO_SETBUF'] = ''
111 111
112 112 # Proceed with curses initialization
113 113 try:
114 114 scr = curses.initscr()
115 115 except AttributeError:
116 116 # Curses on Solaris may not be complete, so we can't use it there
117 117 return screen_lines_def
118 118
119 119 screen_lines_real,screen_cols = scr.getmaxyx()
120 120 curses.endwin()
121 121
122 122 # Restore environment
123 123 if NCURSES_NO_SETBUF is None:
124 124 del os.environ['NCURSES_NO_SETBUF']
125 125 else:
126 126 os.environ['NCURSES_NO_SETBUF'] = NCURSES_NO_SETBUF
127 127
128 128 # Restore terminal state in case endwin() didn't.
129 129 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
130 130 # Now we have what we needed: the screen size in rows/columns
131 131 return screen_lines_real
132 132 #print '***Screen size:',screen_lines_real,'lines x',\
133 133 #screen_cols,'columns.' # dbg
134 134
135 135 def page(strng, start=0, screen_lines=0, pager_cmd=None):
136 136 """Print a string, piping through a pager after a certain length.
137 137
138 138 The screen_lines parameter specifies the number of *usable* lines of your
139 139 terminal screen (total lines minus lines you need to reserve to show other
140 140 information).
141 141
142 142 If you set screen_lines to a number <=0, page() will try to auto-determine
143 143 your screen size and will only use up to (screen_size+screen_lines) for
144 144 printing, paging after that. That is, if you want auto-detection but need
145 145 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
146 146 auto-detection without any lines reserved simply use screen_lines = 0.
147 147
148 148 If a string won't fit in the allowed lines, it is sent through the
149 149 specified pager command. If none given, look for PAGER in the environment,
150 150 and ultimately default to less.
151 151
152 152 If no system pager works, the string is sent through a 'dumb pager'
153 153 written in python, very simplistic.
154 154 """
155
156 # for compatibility with mime-bundle form:
157 if isinstance(strng, dict):
158 strng = strng['text/plain']
155 159
156 160 # Some routines may auto-compute start offsets incorrectly and pass a
157 161 # negative value. Offset to 0 for robustness.
158 162 start = max(0, start)
159 163
160 164 # first, try the hook
161 165 ip = get_ipython()
162 166 if ip:
163 167 try:
164 168 ip.hooks.show_in_pager(strng)
165 169 return
166 170 except TryNext:
167 171 pass
168 172
169 173 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
170 174 TERM = os.environ.get('TERM','dumb')
171 175 if TERM in ['dumb','emacs'] and os.name != 'nt':
172 176 print(strng)
173 177 return
174 178 # chop off the topmost part of the string we don't want to see
175 179 str_lines = strng.splitlines()[start:]
176 180 str_toprint = os.linesep.join(str_lines)
177 181 num_newlines = len(str_lines)
178 182 len_str = len(str_toprint)
179 183
180 184 # Dumb heuristics to guesstimate number of on-screen lines the string
181 185 # takes. Very basic, but good enough for docstrings in reasonable
182 186 # terminals. If someone later feels like refining it, it's not hard.
183 187 numlines = max(num_newlines,int(len_str/80)+1)
184 188
185 189 screen_lines_def = get_terminal_size()[1]
186 190
187 191 # auto-determine screen size
188 192 if screen_lines <= 0:
189 193 try:
190 194 screen_lines += _detect_screen_size(screen_lines_def)
191 195 except (TypeError, UnsupportedOperation):
192 196 print(str_toprint, file=io.stdout)
193 197 return
194 198
195 199 #print 'numlines',numlines,'screenlines',screen_lines # dbg
196 200 if numlines <= screen_lines :
197 201 #print '*** normal print' # dbg
198 202 print(str_toprint, file=io.stdout)
199 203 else:
200 204 # Try to open pager and default to internal one if that fails.
201 205 # All failure modes are tagged as 'retval=1', to match the return
202 206 # value of a failed system command. If any intermediate attempt
203 207 # sets retval to 1, at the end we resort to our own page_dumb() pager.
204 208 pager_cmd = get_pager_cmd(pager_cmd)
205 209 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
206 210 if os.name == 'nt':
207 211 if pager_cmd.startswith('type'):
208 212 # The default WinXP 'type' command is failing on complex strings.
209 213 retval = 1
210 214 else:
211 215 fd, tmpname = tempfile.mkstemp('.txt')
212 216 try:
213 217 os.close(fd)
214 218 with open(tmpname, 'wt') as tmpfile:
215 219 tmpfile.write(strng)
216 220 cmd = "%s < %s" % (pager_cmd, tmpname)
217 221 # tmpfile needs to be closed for windows
218 222 if os.system(cmd):
219 223 retval = 1
220 224 else:
221 225 retval = None
222 226 finally:
223 227 os.remove(tmpname)
224 228 else:
225 229 try:
226 230 retval = None
227 231 # if I use popen4, things hang. No idea why.
228 232 #pager,shell_out = os.popen4(pager_cmd)
229 233 pager = os.popen(pager_cmd, 'w')
230 234 try:
231 235 pager_encoding = pager.encoding or sys.stdout.encoding
232 236 pager.write(py3compat.cast_bytes_py2(
233 237 strng, encoding=pager_encoding))
234 238 finally:
235 239 retval = pager.close()
236 240 except IOError as msg: # broken pipe when user quits
237 241 if msg.args == (32, 'Broken pipe'):
238 242 retval = None
239 243 else:
240 244 retval = 1
241 245 except OSError:
242 246 # Other strange problems, sometimes seen in Win2k/cygwin
243 247 retval = 1
244 248 if retval is not None:
245 249 page_dumb(strng,screen_lines=screen_lines)
246 250
247 251
248 252 def page_file(fname, start=0, pager_cmd=None):
249 253 """Page a file, using an optional pager command and starting line.
250 254 """
251 255
252 256 pager_cmd = get_pager_cmd(pager_cmd)
253 257 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
254 258
255 259 try:
256 260 if os.environ['TERM'] in ['emacs','dumb']:
257 261 raise EnvironmentError
258 262 system(pager_cmd + ' ' + fname)
259 263 except:
260 264 try:
261 265 if start > 0:
262 266 start -= 1
263 267 page(open(fname).read(),start)
264 268 except:
265 269 print('Unable to show file',repr(fname))
266 270
267 271
268 272 def get_pager_cmd(pager_cmd=None):
269 273 """Return a pager command.
270 274
271 275 Makes some attempts at finding an OS-correct one.
272 276 """
273 277 if os.name == 'posix':
274 278 default_pager_cmd = 'less -r' # -r for color control sequences
275 279 elif os.name in ['nt','dos']:
276 280 default_pager_cmd = 'type'
277 281
278 282 if pager_cmd is None:
279 283 try:
280 284 pager_cmd = os.environ['PAGER']
281 285 except:
282 286 pager_cmd = default_pager_cmd
283 287 return pager_cmd
284 288
285 289
286 290 def get_pager_start(pager, start):
287 291 """Return the string for paging files with an offset.
288 292
289 293 This is the '+N' argument which less and more (under Unix) accept.
290 294 """
291 295
292 296 if pager in ['less','more']:
293 297 if start:
294 298 start_string = '+' + str(start)
295 299 else:
296 300 start_string = ''
297 301 else:
298 302 start_string = ''
299 303 return start_string
300 304
301 305
302 306 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
303 307 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
304 308 import msvcrt
305 309 def page_more():
306 310 """ Smart pausing between pages
307 311
308 312 @return: True if need print more lines, False if quit
309 313 """
310 314 io.stdout.write('---Return to continue, q to quit--- ')
311 315 ans = msvcrt.getwch()
312 316 if ans in ("q", "Q"):
313 317 result = False
314 318 else:
315 319 result = True
316 320 io.stdout.write("\b"*37 + " "*37 + "\b"*37)
317 321 return result
318 322 else:
319 323 def page_more():
320 324 ans = py3compat.input('---Return to continue, q to quit--- ')
321 325 if ans.lower().startswith('q'):
322 326 return False
323 327 else:
324 328 return True
325 329
326 330
327 331 def snip_print(str,width = 75,print_full = 0,header = ''):
328 332 """Print a string snipping the midsection to fit in width.
329 333
330 334 print_full: mode control:
331 335
332 336 - 0: only snip long strings
333 337 - 1: send to page() directly.
334 338 - 2: snip long strings and ask for full length viewing with page()
335 339
336 340 Return 1 if snipping was necessary, 0 otherwise."""
337 341
338 342 if print_full == 1:
339 343 page(header+str)
340 344 return 0
341 345
342 346 print(header, end=' ')
343 347 if len(str) < width:
344 348 print(str)
345 349 snip = 0
346 350 else:
347 351 whalf = int((width -5)/2)
348 352 print(str[:whalf] + ' <...> ' + str[-whalf:])
349 353 snip = 1
350 354 if snip and print_full == 2:
351 355 if py3compat.input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
352 356 page(str)
353 357 return snip
@@ -1,96 +1,50 b''
1 1 # encoding: utf-8
2 """
3 A payload based version of page.
2 """A payload based version of page."""
4 3
5 Authors:
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
6 6
7 * Brian Granger
8 * Fernando Perez
9 """
10
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
17
18 #-----------------------------------------------------------------------------
19 # Imports
20 #-----------------------------------------------------------------------------
21
22 # Third-party
23 try:
24 from docutils.core import publish_string
25 except ImportError:
26 # html paging won't be available, but we don't raise any errors. It's a
27 # purely optional feature.
28 pass
29
30 # Our own
31 from IPython.core.interactiveshell import InteractiveShell
7 from IPython.core.getipython import get_ipython
32 8
33 9 #-----------------------------------------------------------------------------
34 10 # Classes and functions
35 11 #-----------------------------------------------------------------------------
36 12
37 def page(strng, start=0, screen_lines=0, pager_cmd=None,
38 html=None, auto_html=False):
13 def page(strng, start=0, screen_lines=0, pager_cmd=None):
39 14 """Print a string, piping through a pager.
40 15
41 16 This version ignores the screen_lines and pager_cmd arguments and uses
42 17 IPython's payload system instead.
43 18
44 19 Parameters
45 20 ----------
46 strng : str
47 Text to page.
21 strng : str or mime-dict
22 Text to page, or a mime-type keyed dict of already formatted data.
48 23
49 24 start : int
50 25 Starting line at which to place the display.
51
52 html : str, optional
53 If given, an html string to send as well.
54
55 auto_html : bool, optional
56 If true, the input string is assumed to be valid reStructuredText and is
57 converted to HTML with docutils. Note that if docutils is not found,
58 this option is silently ignored.
59
60 Notes
61 -----
62
63 Only one of the ``html`` and ``auto_html`` options can be given, not
64 both.
65 26 """
66 27
67 28 # Some routines may auto-compute start offsets incorrectly and pass a
68 29 # negative value. Offset to 0 for robustness.
69 30 start = max(0, start)
70 shell = InteractiveShell.instance()
71
72 if auto_html:
73 try:
74 # These defaults ensure user configuration variables for docutils
75 # are not loaded, only our config is used here.
76 defaults = {'file_insertion_enabled': 0,
77 'raw_enabled': 0,
78 '_disable_config': 1}
79 html = publish_string(strng, writer_name='html',
80 settings_overrides=defaults)
81 except:
82 pass
83
31 shell = get_ipython()
32
33 if isinstance(strng, dict):
34 data = strng
35 else:
36 data = {'text/plain' : strng}
84 37 payload = dict(
85 38 source='page',
39 data=data,
86 40 text=strng,
87 html=html,
88 start_line_number=start
41 start=start,
42 screen_lines=screen_lines,
89 43 )
90 44 shell.payload_manager.write_payload(payload)
91 45
92 46
93 47 def install_payload_page():
94 48 """Install this version of page as IPython.core.page.page."""
95 49 from IPython.core import page as corepage
96 50 corepage.page = page
@@ -1,178 +1,180 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 4 //============================================================================
9 5 // Pager
10 6 //============================================================================
11 7
12 8 var IPython = (function (IPython) {
13 9 "use strict";
14 10
15 11 var utils = IPython.utils;
16 12
17 13 var Pager = function (pager_selector, pager_splitter_selector) {
18 14 this.pager_element = $(pager_selector);
19 15 this.pager_button_area = $('#pager_button_area');
20 16 var that = this;
21 17 this.percentage_height = 0.40;
22 18 this.pager_splitter_element = $(pager_splitter_selector)
23 19 .draggable({
24 20 containment: 'window',
25 21 axis:'y',
26 22 helper: null ,
27 23 drag: function(event, ui) {
28 24 // recalculate the amount of space the pager should take
29 25 var pheight = ($(document.body).height()-event.clientY-4);
30 26 var downprct = pheight/IPython.layout_manager.app_height();
31 27 downprct = Math.min(0.9, downprct);
32 28 if (downprct < 0.1) {
33 29 that.percentage_height = 0.1;
34 30 that.collapse({'duration':0});
35 31 } else if (downprct > 0.2) {
36 32 that.percentage_height = downprct;
37 33 that.expand({'duration':0});
38 34 }
39 35 IPython.layout_manager.do_resize();
40 36 }
41 37 });
42 38 this.expanded = false;
43 39 this.style();
44 40 this.create_button_area();
45 41 this.bind_events();
46 42 };
47 43
48 44 Pager.prototype.create_button_area = function(){
49 45 var that = this;
50 46 this.pager_button_area.append(
51 47 $('<a>').attr('role', "button")
52 48 .attr('title',"Open the pager in an external window")
53 49 .addClass('ui-button')
54 50 .click(function(){that.detach()})
55 51 .attr('style','position: absolute; right: 20px;')
56 52 .append(
57 53 $('<span>').addClass("ui-icon ui-icon-extlink")
58 54 )
59 55 )
60 56 this.pager_button_area.append(
61 57 $('<a>').attr('role', "button")
62 58 .attr('title',"Close the pager")
63 59 .addClass('ui-button')
64 60 .click(function(){that.collapse()})
65 61 .attr('style','position: absolute; right: 5px;')
66 62 .append(
67 63 $('<span>').addClass("ui-icon ui-icon-close")
68 64 )
69 65 )
70 66 };
71 67
72 68 Pager.prototype.style = function () {
73 69 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
74 70 this.pager_element.addClass('border-box-sizing');
75 71 this.pager_element.find(".container").addClass('border-box-sizing');
76 72 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
77 73 };
78 74
79 75
80 76 Pager.prototype.bind_events = function () {
81 77 var that = this;
82 78
83 79 this.pager_element.bind('collapse_pager', function (event, extrap) {
84 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
80 var time = 'fast';
81 if (extrap && extrap.duration) {
82 time = extrap.duration;
83 }
85 84 that.pager_element.hide(time);
86 85 });
87 86
88 87 this.pager_element.bind('expand_pager', function (event, extrap) {
89 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
88 var time = 'fast';
89 if (extrap && extrap.duration) {
90 time = extrap.duration;
91 }
90 92 that.pager_element.show(time);
91 93 });
92 94
93 95 this.pager_splitter_element.hover(
94 96 function () {
95 97 that.pager_splitter_element.addClass('ui-state-hover');
96 98 },
97 99 function () {
98 100 that.pager_splitter_element.removeClass('ui-state-hover');
99 101 }
100 102 );
101 103
102 104 this.pager_splitter_element.click(function () {
103 105 that.toggle();
104 106 });
105 107
106 $([IPython.events]).on('open_with_text.Pager', function (event, data) {
107 if (data.text.trim() !== '') {
108 $([IPython.events]).on('open_with_text.Pager', function (event, payload) {
109 // FIXME: support other mime types
110 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
108 111 that.clear();
109 112 that.expand();
110 that.append_text(data.text);
111 };
113 that.append_text(payload.data['text/plain']);
114 }
112 115 });
113 116 };
114 117
115 118
116 119 Pager.prototype.collapse = function (extrap) {
117 120 if (this.expanded === true) {
118 121 this.expanded = false;
119 122 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
120 };
123 }
121 124 };
122 125
123 126
124 127 Pager.prototype.expand = function (extrap) {
125 128 if (this.expanded !== true) {
126 129 this.expanded = true;
127 130 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
128 };
131 }
129 132 };
130 133
131 134
132 135 Pager.prototype.toggle = function () {
133 136 if (this.expanded === true) {
134 137 this.collapse();
135 138 } else {
136 139 this.expand();
137 };
140 }
138 141 };
139 142
140 143
141 144 Pager.prototype.clear = function (text) {
142 145 this.pager_element.find(".container").empty();
143 146 };
144 147
145 148 Pager.prototype.detach = function(){
146 149 var w = window.open("","_blank");
147 150 $(w.document.head)
148 151 .append(
149 152 $('<link>')
150 153 .attr('rel',"stylesheet")
151 154 .attr('href',"/static/css/notebook.css")
152 155 .attr('type',"text/css")
153 156 )
154 157 .append(
155 158 $('<title>').text("IPython Pager")
156 159 );
157 160 var pager_body = $(w.document.body);
158 161 pager_body.css('overflow','scroll');
159 162
160 163 pager_body.append(this.pager_element.clone().children());
161 164 w.document.close();
162 165 this.collapse();
163
164 }
166 };
165 167
166 168 Pager.prototype.append_text = function (text) {
167 169 // The only user content injected with this HTML call is escaped by
168 170 // the fixConsole() method.
169 171 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
170 172 };
171 173
172 174
173 175 IPython.Pager = Pager;
174 176
175 177 return IPython;
176 178
177 179 }(IPython));
178 180
@@ -1,570 +1,571 b''
1 1 """A FrontendWidget that emulates the interface of the console IPython.
2 2
3 3 This supports the additional functionality provided by the IPython kernel.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 from collections import namedtuple
10 10 import os.path
11 11 import re
12 12 from subprocess import Popen
13 13 import sys
14 14 import time
15 15 from textwrap import dedent
16 16
17 17 from IPython.external.qt import QtCore, QtGui
18 18
19 19 from IPython.core.inputsplitter import IPythonInputSplitter
20 20 from IPython.core.release import version
21 21 from IPython.core.inputtransformer import ipy_prompt
22 22 from IPython.utils.traitlets import Bool, Unicode
23 23 from .frontend_widget import FrontendWidget
24 24 from . import styles
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Constants
28 28 #-----------------------------------------------------------------------------
29 29
30 30 # Default strings to build and display input and output prompts (and separators
31 31 # in between)
32 32 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
33 33 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
34 34 default_input_sep = '\n'
35 35 default_output_sep = ''
36 36 default_output_sep2 = ''
37 37
38 38 # Base path for most payload sources.
39 39 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
40 40
41 41 if sys.platform.startswith('win'):
42 42 default_editor = 'notepad'
43 43 else:
44 44 default_editor = ''
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # IPythonWidget class
48 48 #-----------------------------------------------------------------------------
49 49
50 50 class IPythonWidget(FrontendWidget):
51 51 """ A FrontendWidget for an IPython kernel.
52 52 """
53 53
54 54 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
55 55 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
56 56 # settings.
57 57 custom_edit = Bool(False)
58 58 custom_edit_requested = QtCore.Signal(object, object)
59 59
60 60 editor = Unicode(default_editor, config=True,
61 61 help="""
62 62 A command for invoking a system text editor. If the string contains a
63 63 {filename} format specifier, it will be used. Otherwise, the filename
64 64 will be appended to the end the command.
65 65 """)
66 66
67 67 editor_line = Unicode(config=True,
68 68 help="""
69 69 The editor command to use when a specific line number is requested. The
70 70 string should contain two format specifiers: {line} and {filename}. If
71 71 this parameter is not specified, the line number option to the %edit
72 72 magic will be ignored.
73 73 """)
74 74
75 75 style_sheet = Unicode(config=True,
76 76 help="""
77 77 A CSS stylesheet. The stylesheet can contain classes for:
78 78 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
79 79 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
80 80 3. IPython: .error, .in-prompt, .out-prompt, etc
81 81 """)
82 82
83 83 syntax_style = Unicode(config=True,
84 84 help="""
85 85 If not empty, use this Pygments style for syntax highlighting.
86 86 Otherwise, the style sheet is queried for Pygments style
87 87 information.
88 88 """)
89 89
90 90 # Prompts.
91 91 in_prompt = Unicode(default_in_prompt, config=True)
92 92 out_prompt = Unicode(default_out_prompt, config=True)
93 93 input_sep = Unicode(default_input_sep, config=True)
94 94 output_sep = Unicode(default_output_sep, config=True)
95 95 output_sep2 = Unicode(default_output_sep2, config=True)
96 96
97 97 # FrontendWidget protected class variables.
98 98 _input_splitter_class = IPythonInputSplitter
99 99 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
100 100 logical_line_transforms=[],
101 101 python_line_transforms=[],
102 102 )
103 103
104 104 # IPythonWidget protected class variables.
105 105 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
106 106 _payload_source_edit = 'edit_magic'
107 107 _payload_source_exit = 'ask_exit'
108 108 _payload_source_next_input = 'set_next_input'
109 109 _payload_source_page = 'page'
110 110 _retrying_history_request = False
111 111 _starting = False
112 112
113 113 #---------------------------------------------------------------------------
114 114 # 'object' interface
115 115 #---------------------------------------------------------------------------
116 116
117 117 def __init__(self, *args, **kw):
118 118 super(IPythonWidget, self).__init__(*args, **kw)
119 119
120 120 # IPythonWidget protected variables.
121 121 self._payload_handlers = {
122 122 self._payload_source_edit : self._handle_payload_edit,
123 123 self._payload_source_exit : self._handle_payload_exit,
124 124 self._payload_source_page : self._handle_payload_page,
125 125 self._payload_source_next_input : self._handle_payload_next_input }
126 126 self._previous_prompt_obj = None
127 127 self._keep_kernel_on_exit = None
128 128
129 129 # Initialize widget styling.
130 130 if self.style_sheet:
131 131 self._style_sheet_changed()
132 132 self._syntax_style_changed()
133 133 else:
134 134 self.set_default_style()
135 135
136 136 self._guiref_loaded = False
137 137
138 138 #---------------------------------------------------------------------------
139 139 # 'BaseFrontendMixin' abstract interface
140 140 #---------------------------------------------------------------------------
141 141 def _handle_complete_reply(self, rep):
142 142 """ Reimplemented to support IPython's improved completion machinery.
143 143 """
144 144 self.log.debug("complete: %s", rep.get('content', ''))
145 145 cursor = self._get_cursor()
146 146 info = self._request_info.get('complete')
147 147 if info and info.id == rep['parent_header']['msg_id'] and \
148 148 info.pos == cursor.position():
149 149 matches = rep['content']['matches']
150 150 text = rep['content']['matched_text']
151 151 offset = len(text)
152 152
153 153 # Clean up matches with period and path separators if the matched
154 154 # text has not been transformed. This is done by truncating all
155 155 # but the last component and then suitably decreasing the offset
156 156 # between the current cursor position and the start of completion.
157 157 if len(matches) > 1 and matches[0][:offset] == text:
158 158 parts = re.split(r'[./\\]', text)
159 159 sep_count = len(parts) - 1
160 160 if sep_count:
161 161 chop_length = sum(map(len, parts[:sep_count])) + sep_count
162 162 matches = [ match[chop_length:] for match in matches ]
163 163 offset -= chop_length
164 164
165 165 # Move the cursor to the start of the match and complete.
166 166 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
167 167 self._complete_with_items(cursor, matches)
168 168
169 169 def _handle_execute_reply(self, msg):
170 170 """ Reimplemented to support prompt requests.
171 171 """
172 172 msg_id = msg['parent_header'].get('msg_id')
173 173 info = self._request_info['execute'].get(msg_id)
174 174 if info and info.kind == 'prompt':
175 175 content = msg['content']
176 176 if content['status'] == 'aborted':
177 177 self._show_interpreter_prompt()
178 178 else:
179 179 number = content['execution_count'] + 1
180 180 self._show_interpreter_prompt(number)
181 181 self._request_info['execute'].pop(msg_id)
182 182 else:
183 183 super(IPythonWidget, self)._handle_execute_reply(msg)
184 184
185 185 def _handle_history_reply(self, msg):
186 186 """ Implemented to handle history tail replies, which are only supported
187 187 by the IPython kernel.
188 188 """
189 189 content = msg['content']
190 190 if 'history' not in content:
191 191 self.log.error("History request failed: %r"%content)
192 192 if content.get('status', '') == 'aborted' and \
193 193 not self._retrying_history_request:
194 194 # a *different* action caused this request to be aborted, so
195 195 # we should try again.
196 196 self.log.error("Retrying aborted history request")
197 197 # prevent multiple retries of aborted requests:
198 198 self._retrying_history_request = True
199 199 # wait out the kernel's queue flush, which is currently timed at 0.1s
200 200 time.sleep(0.25)
201 201 self.kernel_client.shell_channel.history(hist_access_type='tail',n=1000)
202 202 else:
203 203 self._retrying_history_request = False
204 204 return
205 205 # reset retry flag
206 206 self._retrying_history_request = False
207 207 history_items = content['history']
208 208 self.log.debug("Received history reply with %i entries", len(history_items))
209 209 items = []
210 210 last_cell = u""
211 211 for _, _, cell in history_items:
212 212 cell = cell.rstrip()
213 213 if cell != last_cell:
214 214 items.append(cell)
215 215 last_cell = cell
216 216 self._set_history(items)
217 217
218 218 def _handle_execute_result(self, msg):
219 219 """ Reimplemented for IPython-style "display hook".
220 220 """
221 221 self.log.debug("execute_result: %s", msg.get('content', ''))
222 222 if not self._hidden and self._is_from_this_session(msg):
223 223 self.flush_clearoutput()
224 224 content = msg['content']
225 225 prompt_number = content.get('execution_count', 0)
226 226 data = content['data']
227 227 if 'text/plain' in data:
228 228 self._append_plain_text(self.output_sep, True)
229 229 self._append_html(self._make_out_prompt(prompt_number), True)
230 230 text = data['text/plain']
231 231 # If the repr is multiline, make sure we start on a new line,
232 232 # so that its lines are aligned.
233 233 if "\n" in text and not self.output_sep.endswith("\n"):
234 234 self._append_plain_text('\n', True)
235 235 self._append_plain_text(text + self.output_sep2, True)
236 236
237 237 def _handle_display_data(self, msg):
238 238 """ The base handler for the ``display_data`` message.
239 239 """
240 240 self.log.debug("display: %s", msg.get('content', ''))
241 241 # For now, we don't display data from other frontends, but we
242 242 # eventually will as this allows all frontends to monitor the display
243 243 # data. But we need to figure out how to handle this in the GUI.
244 244 if not self._hidden and self._is_from_this_session(msg):
245 245 self.flush_clearoutput()
246 246 source = msg['content']['source']
247 247 data = msg['content']['data']
248 248 metadata = msg['content']['metadata']
249 249 # In the regular IPythonWidget, we simply print the plain text
250 250 # representation.
251 251 if 'text/plain' in data:
252 252 text = data['text/plain']
253 253 self._append_plain_text(text, True)
254 254 # This newline seems to be needed for text and html output.
255 255 self._append_plain_text(u'\n', True)
256 256
257 257 def _handle_kernel_info_reply(self, rep):
258 258 """Handle kernel info replies."""
259 259 content = rep['content']
260 260 if not self._guiref_loaded:
261 261 if content.get('language') == 'python':
262 262 self._load_guiref_magic()
263 263 self._guiref_loaded = True
264 264
265 265 self.kernel_banner = content.get('banner', '')
266 266 if self._starting:
267 267 # finish handling started channels
268 268 self._starting = False
269 269 super(IPythonWidget, self)._started_channels()
270 270
271 271 def _started_channels(self):
272 272 """Reimplemented to make a history request and load %guiref."""
273 273 self._starting = True
274 274 # The reply will trigger %guiref load provided language=='python'
275 275 self.kernel_client.kernel_info()
276 276
277 277 self.kernel_client.shell_channel.history(hist_access_type='tail',
278 278 n=1000)
279 279
280 280 def _load_guiref_magic(self):
281 281 """Load %guiref magic."""
282 282 self.kernel_client.shell_channel.execute('\n'.join([
283 283 "try:",
284 284 " _usage",
285 285 "except:",
286 286 " from IPython.core import usage as _usage",
287 287 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
288 288 " del _usage",
289 289 ]), silent=True)
290 290
291 291 #---------------------------------------------------------------------------
292 292 # 'ConsoleWidget' public interface
293 293 #---------------------------------------------------------------------------
294 294
295 295 #---------------------------------------------------------------------------
296 296 # 'FrontendWidget' public interface
297 297 #---------------------------------------------------------------------------
298 298
299 299 def execute_file(self, path, hidden=False):
300 300 """ Reimplemented to use the 'run' magic.
301 301 """
302 302 # Use forward slashes on Windows to avoid escaping each separator.
303 303 if sys.platform == 'win32':
304 304 path = os.path.normpath(path).replace('\\', '/')
305 305
306 306 # Perhaps we should not be using %run directly, but while we
307 307 # are, it is necessary to quote or escape filenames containing spaces
308 308 # or quotes.
309 309
310 310 # In earlier code here, to minimize escaping, we sometimes quoted the
311 311 # filename with single quotes. But to do this, this code must be
312 312 # platform-aware, because run uses shlex rather than python string
313 313 # parsing, so that:
314 314 # * In Win: single quotes can be used in the filename without quoting,
315 315 # and we cannot use single quotes to quote the filename.
316 316 # * In *nix: we can escape double quotes in a double quoted filename,
317 317 # but can't escape single quotes in a single quoted filename.
318 318
319 319 # So to keep this code non-platform-specific and simple, we now only
320 320 # use double quotes to quote filenames, and escape when needed:
321 321 if ' ' in path or "'" in path or '"' in path:
322 322 path = '"%s"' % path.replace('"', '\\"')
323 323 self.execute('%%run %s' % path, hidden=hidden)
324 324
325 325 #---------------------------------------------------------------------------
326 326 # 'FrontendWidget' protected interface
327 327 #---------------------------------------------------------------------------
328 328
329 329 def _process_execute_error(self, msg):
330 330 """ Reimplemented for IPython-style traceback formatting.
331 331 """
332 332 content = msg['content']
333 333 traceback = '\n'.join(content['traceback']) + '\n'
334 334 if False:
335 335 # FIXME: For now, tracebacks come as plain text, so we can't use
336 336 # the html renderer yet. Once we refactor ultratb to produce
337 337 # properly styled tracebacks, this branch should be the default
338 338 traceback = traceback.replace(' ', '&nbsp;')
339 339 traceback = traceback.replace('\n', '<br/>')
340 340
341 341 ename = content['ename']
342 342 ename_styled = '<span class="error">%s</span>' % ename
343 343 traceback = traceback.replace(ename, ename_styled)
344 344
345 345 self._append_html(traceback)
346 346 else:
347 347 # This is the fallback for now, using plain text with ansi escapes
348 348 self._append_plain_text(traceback)
349 349
350 350 def _process_execute_payload(self, item):
351 351 """ Reimplemented to dispatch payloads to handler methods.
352 352 """
353 353 handler = self._payload_handlers.get(item['source'])
354 354 if handler is None:
355 355 # We have no handler for this type of payload, simply ignore it
356 356 return False
357 357 else:
358 358 handler(item)
359 359 return True
360 360
361 361 def _show_interpreter_prompt(self, number=None):
362 362 """ Reimplemented for IPython-style prompts.
363 363 """
364 364 # If a number was not specified, make a prompt number request.
365 365 if number is None:
366 366 msg_id = self.kernel_client.shell_channel.execute('', silent=True)
367 367 info = self._ExecutionRequest(msg_id, 'prompt')
368 368 self._request_info['execute'][msg_id] = info
369 369 return
370 370
371 371 # Show a new prompt and save information about it so that it can be
372 372 # updated later if the prompt number turns out to be wrong.
373 373 self._prompt_sep = self.input_sep
374 374 self._show_prompt(self._make_in_prompt(number), html=True)
375 375 block = self._control.document().lastBlock()
376 376 length = len(self._prompt)
377 377 self._previous_prompt_obj = self._PromptBlock(block, length, number)
378 378
379 379 # Update continuation prompt to reflect (possibly) new prompt length.
380 380 self._set_continuation_prompt(
381 381 self._make_continuation_prompt(self._prompt), html=True)
382 382
383 383 def _show_interpreter_prompt_for_reply(self, msg):
384 384 """ Reimplemented for IPython-style prompts.
385 385 """
386 386 # Update the old prompt number if necessary.
387 387 content = msg['content']
388 388 # abort replies do not have any keys:
389 389 if content['status'] == 'aborted':
390 390 if self._previous_prompt_obj:
391 391 previous_prompt_number = self._previous_prompt_obj.number
392 392 else:
393 393 previous_prompt_number = 0
394 394 else:
395 395 previous_prompt_number = content['execution_count']
396 396 if self._previous_prompt_obj and \
397 397 self._previous_prompt_obj.number != previous_prompt_number:
398 398 block = self._previous_prompt_obj.block
399 399
400 400 # Make sure the prompt block has not been erased.
401 401 if block.isValid() and block.text():
402 402
403 403 # Remove the old prompt and insert a new prompt.
404 404 cursor = QtGui.QTextCursor(block)
405 405 cursor.movePosition(QtGui.QTextCursor.Right,
406 406 QtGui.QTextCursor.KeepAnchor,
407 407 self._previous_prompt_obj.length)
408 408 prompt = self._make_in_prompt(previous_prompt_number)
409 409 self._prompt = self._insert_html_fetching_plain_text(
410 410 cursor, prompt)
411 411
412 412 # When the HTML is inserted, Qt blows away the syntax
413 413 # highlighting for the line, so we need to rehighlight it.
414 414 self._highlighter.rehighlightBlock(cursor.block())
415 415
416 416 self._previous_prompt_obj = None
417 417
418 418 # Show a new prompt with the kernel's estimated prompt number.
419 419 self._show_interpreter_prompt(previous_prompt_number + 1)
420 420
421 421 #---------------------------------------------------------------------------
422 422 # 'IPythonWidget' interface
423 423 #---------------------------------------------------------------------------
424 424
425 425 def set_default_style(self, colors='lightbg'):
426 426 """ Sets the widget style to the class defaults.
427 427
428 428 Parameters
429 429 ----------
430 430 colors : str, optional (default lightbg)
431 431 Whether to use the default IPython light background or dark
432 432 background or B&W style.
433 433 """
434 434 colors = colors.lower()
435 435 if colors=='lightbg':
436 436 self.style_sheet = styles.default_light_style_sheet
437 437 self.syntax_style = styles.default_light_syntax_style
438 438 elif colors=='linux':
439 439 self.style_sheet = styles.default_dark_style_sheet
440 440 self.syntax_style = styles.default_dark_syntax_style
441 441 elif colors=='nocolor':
442 442 self.style_sheet = styles.default_bw_style_sheet
443 443 self.syntax_style = styles.default_bw_syntax_style
444 444 else:
445 445 raise KeyError("No such color scheme: %s"%colors)
446 446
447 447 #---------------------------------------------------------------------------
448 448 # 'IPythonWidget' protected interface
449 449 #---------------------------------------------------------------------------
450 450
451 451 def _edit(self, filename, line=None):
452 452 """ Opens a Python script for editing.
453 453
454 454 Parameters
455 455 ----------
456 456 filename : str
457 457 A path to a local system file.
458 458
459 459 line : int, optional
460 460 A line of interest in the file.
461 461 """
462 462 if self.custom_edit:
463 463 self.custom_edit_requested.emit(filename, line)
464 464 elif not self.editor:
465 465 self._append_plain_text('No default editor available.\n'
466 466 'Specify a GUI text editor in the `IPythonWidget.editor` '
467 467 'configurable to enable the %edit magic')
468 468 else:
469 469 try:
470 470 filename = '"%s"' % filename
471 471 if line and self.editor_line:
472 472 command = self.editor_line.format(filename=filename,
473 473 line=line)
474 474 else:
475 475 try:
476 476 command = self.editor.format()
477 477 except KeyError:
478 478 command = self.editor.format(filename=filename)
479 479 else:
480 480 command += ' ' + filename
481 481 except KeyError:
482 482 self._append_plain_text('Invalid editor command.\n')
483 483 else:
484 484 try:
485 485 Popen(command, shell=True)
486 486 except OSError:
487 487 msg = 'Opening editor with command "%s" failed.\n'
488 488 self._append_plain_text(msg % command)
489 489
490 490 def _make_in_prompt(self, number):
491 491 """ Given a prompt number, returns an HTML In prompt.
492 492 """
493 493 try:
494 494 body = self.in_prompt % number
495 495 except TypeError:
496 496 # allow in_prompt to leave out number, e.g. '>>> '
497 497 body = self.in_prompt
498 498 return '<span class="in-prompt">%s</span>' % body
499 499
500 500 def _make_continuation_prompt(self, prompt):
501 501 """ Given a plain text version of an In prompt, returns an HTML
502 502 continuation prompt.
503 503 """
504 504 end_chars = '...: '
505 505 space_count = len(prompt.lstrip('\n')) - len(end_chars)
506 506 body = '&nbsp;' * space_count + end_chars
507 507 return '<span class="in-prompt">%s</span>' % body
508 508
509 509 def _make_out_prompt(self, number):
510 510 """ Given a prompt number, returns an HTML Out prompt.
511 511 """
512 512 body = self.out_prompt % number
513 513 return '<span class="out-prompt">%s</span>' % body
514 514
515 515 #------ Payload handlers --------------------------------------------------
516 516
517 517 # Payload handlers with a generic interface: each takes the opaque payload
518 518 # dict, unpacks it and calls the underlying functions with the necessary
519 519 # arguments.
520 520
521 521 def _handle_payload_edit(self, item):
522 522 self._edit(item['filename'], item['line_number'])
523 523
524 524 def _handle_payload_exit(self, item):
525 525 self._keep_kernel_on_exit = item['keepkernel']
526 526 self.exit_requested.emit(self)
527 527
528 528 def _handle_payload_next_input(self, item):
529 529 self.input_buffer = item['text']
530 530
531 531 def _handle_payload_page(self, item):
532 532 # Since the plain text widget supports only a very small subset of HTML
533 533 # and we have no control over the HTML source, we only page HTML
534 534 # payloads in the rich text widget.
535 if item['html'] and self.kind == 'rich':
536 self._page(item['html'], html=True)
535 data = item['data']
536 if 'text/html' in data and self.kind == 'rich':
537 self._page(data['text/html'], html=True)
537 538 else:
538 self._page(item['text'], html=False)
539 self._page(data['text/plain'], html=False)
539 540
540 541 #------ Trait change handlers --------------------------------------------
541 542
542 543 def _style_sheet_changed(self):
543 544 """ Set the style sheets of the underlying widgets.
544 545 """
545 546 self.setStyleSheet(self.style_sheet)
546 547 if self._control is not None:
547 548 self._control.document().setDefaultStyleSheet(self.style_sheet)
548 549 bg_color = self._control.palette().window().color()
549 550 self._ansi_processor.set_background_color(bg_color)
550 551
551 552 if self._page_control is not None:
552 553 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
553 554
554 555
555 556
556 557 def _syntax_style_changed(self):
557 558 """ Set the style for the syntax highlighter.
558 559 """
559 560 if self._highlighter is None:
560 561 # ignore premature calls
561 562 return
562 563 if self.syntax_style:
563 564 self._highlighter.set_style(self.syntax_style)
564 565 else:
565 566 self._highlighter.set_style_sheet(self.style_sheet)
566 567
567 568 #------ Trait default initializers -----------------------------------------
568 569
569 570 def _banner_default(self):
570 571 return "IPython QtConsole {version}\n".format(version=version)
@@ -1,533 +1,535 b''
1 1 # -*- coding: utf-8 -*-
2 2 """terminal client to the IPython kernel"""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import base64
10 10 import bdb
11 11 import signal
12 12 import os
13 13 import sys
14 14 import time
15 15 import subprocess
16 16 from getpass import getpass
17 17 from io import BytesIO
18 18
19 19 try:
20 20 from queue import Empty # Py 3
21 21 except ImportError:
22 22 from Queue import Empty # Py 2
23 23
24 24 from IPython.core import page
25 25 from IPython.core import release
26 26 from IPython.utils.warn import warn, error
27 27 from IPython.utils import io
28 28 from IPython.utils.py3compat import string_types, input
29 29 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float
30 30 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
31 31
32 32 from IPython.terminal.interactiveshell import TerminalInteractiveShell
33 33 from IPython.terminal.console.completer import ZMQCompleter
34 34
35 35
36 36 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
37 37 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
38 38 _executing = False
39 39 _execution_state = Unicode('')
40 40 _pending_clearoutput = False
41 41 kernel_banner = Unicode('')
42 42 kernel_timeout = Float(60, config=True,
43 43 help="""Timeout for giving up on a kernel (in seconds).
44 44
45 45 On first connect and restart, the console tests whether the
46 46 kernel is running and responsive by sending kernel_info_requests.
47 47 This sets the timeout in seconds for how long the kernel can take
48 48 before being presumed dead.
49 49 """
50 50 )
51 51
52 52 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
53 53 config=True, help=
54 54 """
55 55 Handler for image type output. This is useful, for example,
56 56 when connecting to the kernel in which pylab inline backend is
57 57 activated. There are four handlers defined. 'PIL': Use
58 58 Python Imaging Library to popup image; 'stream': Use an
59 59 external program to show the image. Image will be fed into
60 60 the STDIN of the program. You will need to configure
61 61 `stream_image_handler`; 'tempfile': Use an external program to
62 62 show the image. Image will be saved in a temporally file and
63 63 the program is called with the temporally file. You will need
64 64 to configure `tempfile_image_handler`; 'callable': You can set
65 65 any Python callable which is called with the image data. You
66 66 will need to configure `callable_image_handler`.
67 67 """
68 68 )
69 69
70 70 stream_image_handler = List(config=True, help=
71 71 """
72 72 Command to invoke an image viewer program when you are using
73 73 'stream' image handler. This option is a list of string where
74 74 the first element is the command itself and reminders are the
75 75 options for the command. Raw image data is given as STDIN to
76 76 the program.
77 77 """
78 78 )
79 79
80 80 tempfile_image_handler = List(config=True, help=
81 81 """
82 82 Command to invoke an image viewer program when you are using
83 83 'tempfile' image handler. This option is a list of string
84 84 where the first element is the command itself and reminders
85 85 are the options for the command. You can use {file} and
86 86 {format} in the string to represent the location of the
87 87 generated image file and image format.
88 88 """
89 89 )
90 90
91 91 callable_image_handler = Any(config=True, help=
92 92 """
93 93 Callable object called via 'callable' image handler with one
94 94 argument, `data`, which is `msg["content"]["data"]` where
95 95 `msg` is the message from iopub channel. For exmaple, you can
96 96 find base64 encoded PNG data as `data['image/png']`.
97 97 """
98 98 )
99 99
100 100 mime_preference = List(
101 101 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
102 102 config=True, allow_none=False, help=
103 103 """
104 104 Preferred object representation MIME type in order. First
105 105 matched MIME type will be used.
106 106 """
107 107 )
108 108
109 109 manager = Instance('IPython.kernel.KernelManager')
110 110 client = Instance('IPython.kernel.KernelClient')
111 111 def _client_changed(self, name, old, new):
112 112 self.session_id = new.session.session
113 113 session_id = Unicode()
114 114
115 115 def init_completer(self):
116 116 """Initialize the completion machinery.
117 117
118 118 This creates completion machinery that can be used by client code,
119 119 either interactively in-process (typically triggered by the readline
120 120 library), programmatically (such as in test suites) or out-of-process
121 121 (typically over the network by remote frontends).
122 122 """
123 123 from IPython.core.completerlib import (module_completer,
124 124 magic_run_completer, cd_completer)
125 125
126 126 self.Completer = ZMQCompleter(self, self.client, config=self.config)
127 127
128 128
129 129 self.set_hook('complete_command', module_completer, str_key = 'import')
130 130 self.set_hook('complete_command', module_completer, str_key = 'from')
131 131 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
132 132 self.set_hook('complete_command', cd_completer, str_key = '%cd')
133 133
134 134 # Only configure readline if we truly are using readline. IPython can
135 135 # do tab-completion over the network, in GUIs, etc, where readline
136 136 # itself may be absent
137 137 if self.has_readline:
138 138 self.set_readline_completer()
139 139
140 140 def ask_exit(self):
141 141 super(ZMQTerminalInteractiveShell, self).ask_exit()
142 142 if self.exit_now and self.manager:
143 143 self.client.shutdown()
144 144
145 145 def run_cell(self, cell, store_history=True):
146 146 """Run a complete IPython cell.
147 147
148 148 Parameters
149 149 ----------
150 150 cell : str
151 151 The code (including IPython code such as %magic functions) to run.
152 152 store_history : bool
153 153 If True, the raw and translated cell will be stored in IPython's
154 154 history. For user code calling back into IPython's machinery, this
155 155 should be set to False.
156 156 """
157 157 if (not cell) or cell.isspace():
158 158 # pressing enter flushes any pending display
159 159 self.handle_iopub()
160 160 return
161 161
162 162 if cell.strip() == 'exit':
163 163 # explicitly handle 'exit' command
164 164 return self.ask_exit()
165 165
166 166 # flush stale replies, which could have been ignored, due to missed heartbeats
167 167 while self.client.shell_channel.msg_ready():
168 168 self.client.shell_channel.get_msg()
169 169 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
170 170 msg_id = self.client.shell_channel.execute(cell, not store_history)
171 171
172 172 # first thing is wait for any side effects (output, stdin, etc.)
173 173 self._executing = True
174 174 self._execution_state = "busy"
175 175 while self._execution_state != 'idle' and self.client.is_alive():
176 176 try:
177 177 self.handle_input_request(msg_id, timeout=0.05)
178 178 except Empty:
179 179 # display intermediate print statements, etc.
180 180 self.handle_iopub(msg_id)
181 181
182 182 # after all of that is done, wait for the execute reply
183 183 while self.client.is_alive():
184 184 try:
185 185 self.handle_execute_reply(msg_id, timeout=0.05)
186 186 except Empty:
187 187 pass
188 188 else:
189 189 break
190 190 self._executing = False
191 191
192 192 #-----------------
193 193 # message handlers
194 194 #-----------------
195 195
196 196 def handle_execute_reply(self, msg_id, timeout=None):
197 197 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
198 198 if msg["parent_header"].get("msg_id", None) == msg_id:
199 199
200 200 self.handle_iopub(msg_id)
201 201
202 202 content = msg["content"]
203 203 status = content['status']
204 204
205 205 if status == 'aborted':
206 206 self.write('Aborted\n')
207 207 return
208 208 elif status == 'ok':
209 # print execution payloads as well:
209 # handle payloads
210 210 for item in content["payload"]:
211 text = item.get('text', None)
212 if text:
213 page.page(text)
211 source = item['source']
212 if source == 'page':
213 page.page(item['data']['text/plain'])
214 elif source == 'set_next_input':
215 self.set_next_input(item['text'])
214 216
215 217 elif status == 'error':
216 218 for frame in content["traceback"]:
217 219 print(frame, file=io.stderr)
218 220
219 221 self.execution_count = int(content["execution_count"] + 1)
220 222
221 223
222 224 def handle_iopub(self, msg_id=''):
223 225 """Process messages on the IOPub channel
224 226
225 227 This method consumes and processes messages on the IOPub channel,
226 228 such as stdout, stderr, execute_result and status.
227 229
228 230 It only displays output that is caused by this session.
229 231 """
230 232 while self.client.iopub_channel.msg_ready():
231 233 sub_msg = self.client.iopub_channel.get_msg()
232 234 msg_type = sub_msg['header']['msg_type']
233 235 parent = sub_msg["parent_header"]
234 236
235 237 if parent.get("session", self.session_id) == self.session_id:
236 238 if msg_type == 'status':
237 239 self._execution_state = sub_msg["content"]["execution_state"]
238 240 elif msg_type == 'stream':
239 241 if sub_msg["content"]["name"] == "stdout":
240 242 if self._pending_clearoutput:
241 243 print("\r", file=io.stdout, end="")
242 244 self._pending_clearoutput = False
243 245 print(sub_msg["content"]["data"], file=io.stdout, end="")
244 246 io.stdout.flush()
245 247 elif sub_msg["content"]["name"] == "stderr" :
246 248 if self._pending_clearoutput:
247 249 print("\r", file=io.stderr, end="")
248 250 self._pending_clearoutput = False
249 251 print(sub_msg["content"]["data"], file=io.stderr, end="")
250 252 io.stderr.flush()
251 253
252 254 elif msg_type == 'execute_result':
253 255 if self._pending_clearoutput:
254 256 print("\r", file=io.stdout, end="")
255 257 self._pending_clearoutput = False
256 258 self.execution_count = int(sub_msg["content"]["execution_count"])
257 259 format_dict = sub_msg["content"]["data"]
258 260 self.handle_rich_data(format_dict)
259 261 # taken from DisplayHook.__call__:
260 262 hook = self.displayhook
261 263 hook.start_displayhook()
262 264 hook.write_output_prompt()
263 265 hook.write_format_data(format_dict)
264 266 hook.log_output(format_dict)
265 267 hook.finish_displayhook()
266 268
267 269 elif msg_type == 'display_data':
268 270 data = sub_msg["content"]["data"]
269 271 handled = self.handle_rich_data(data)
270 272 if not handled:
271 273 # if it was an image, we handled it by now
272 274 if 'text/plain' in data:
273 275 print(data['text/plain'])
274 276
275 277 elif msg_type == 'clear_output':
276 278 if sub_msg["content"]["wait"]:
277 279 self._pending_clearoutput = True
278 280 else:
279 281 print("\r", file=io.stdout, end="")
280 282
281 283 _imagemime = {
282 284 'image/png': 'png',
283 285 'image/jpeg': 'jpeg',
284 286 'image/svg+xml': 'svg',
285 287 }
286 288
287 289 def handle_rich_data(self, data):
288 290 for mime in self.mime_preference:
289 291 if mime in data and mime in self._imagemime:
290 292 self.handle_image(data, mime)
291 293 return True
292 294
293 295 def handle_image(self, data, mime):
294 296 handler = getattr(
295 297 self, 'handle_image_{0}'.format(self.image_handler), None)
296 298 if handler:
297 299 handler(data, mime)
298 300
299 301 def handle_image_PIL(self, data, mime):
300 302 if mime not in ('image/png', 'image/jpeg'):
301 303 return
302 304 import PIL.Image
303 305 raw = base64.decodestring(data[mime].encode('ascii'))
304 306 img = PIL.Image.open(BytesIO(raw))
305 307 img.show()
306 308
307 309 def handle_image_stream(self, data, mime):
308 310 raw = base64.decodestring(data[mime].encode('ascii'))
309 311 imageformat = self._imagemime[mime]
310 312 fmt = dict(format=imageformat)
311 313 args = [s.format(**fmt) for s in self.stream_image_handler]
312 314 with open(os.devnull, 'w') as devnull:
313 315 proc = subprocess.Popen(
314 316 args, stdin=subprocess.PIPE,
315 317 stdout=devnull, stderr=devnull)
316 318 proc.communicate(raw)
317 319
318 320 def handle_image_tempfile(self, data, mime):
319 321 raw = base64.decodestring(data[mime].encode('ascii'))
320 322 imageformat = self._imagemime[mime]
321 323 filename = 'tmp.{0}'.format(imageformat)
322 324 with NamedFileInTemporaryDirectory(filename) as f, \
323 325 open(os.devnull, 'w') as devnull:
324 326 f.write(raw)
325 327 f.flush()
326 328 fmt = dict(file=f.name, format=imageformat)
327 329 args = [s.format(**fmt) for s in self.tempfile_image_handler]
328 330 subprocess.call(args, stdout=devnull, stderr=devnull)
329 331
330 332 def handle_image_callable(self, data, mime):
331 333 self.callable_image_handler(data)
332 334
333 335 def handle_input_request(self, msg_id, timeout=0.1):
334 336 """ Method to capture raw_input
335 337 """
336 338 req = self.client.stdin_channel.get_msg(timeout=timeout)
337 339 # in case any iopub came while we were waiting:
338 340 self.handle_iopub(msg_id)
339 341 if msg_id == req["parent_header"].get("msg_id"):
340 342 # wrap SIGINT handler
341 343 real_handler = signal.getsignal(signal.SIGINT)
342 344 def double_int(sig,frame):
343 345 # call real handler (forwards sigint to kernel),
344 346 # then raise local interrupt, stopping local raw_input
345 347 real_handler(sig,frame)
346 348 raise KeyboardInterrupt
347 349 signal.signal(signal.SIGINT, double_int)
348 350 content = req['content']
349 351 read = getpass if content.get('password', False) else input
350 352 try:
351 353 raw_data = read(content["prompt"])
352 354 except EOFError:
353 355 # turn EOFError into EOF character
354 356 raw_data = '\x04'
355 357 except KeyboardInterrupt:
356 358 sys.stdout.write('\n')
357 359 return
358 360 finally:
359 361 # restore SIGINT handler
360 362 signal.signal(signal.SIGINT, real_handler)
361 363
362 364 # only send stdin reply if there *was not* another request
363 365 # or execution finished while we were reading.
364 366 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
365 367 self.client.stdin_channel.input(raw_data)
366 368
367 369 def mainloop(self, display_banner=False):
368 370 while True:
369 371 try:
370 372 self.interact(display_banner=display_banner)
371 373 #self.interact_with_readline()
372 374 # XXX for testing of a readline-decoupled repl loop, call
373 375 # interact_with_readline above
374 376 break
375 377 except KeyboardInterrupt:
376 378 # this should not be necessary, but KeyboardInterrupt
377 379 # handling seems rather unpredictable...
378 380 self.write("\nKeyboardInterrupt in interact()\n")
379 381
380 382 def _banner1_default(self):
381 383 return "IPython Console {version}\n".format(version=release.version)
382 384
383 385 def compute_banner(self):
384 386 super(ZMQTerminalInteractiveShell, self).compute_banner()
385 387 if self.client and not self.kernel_banner:
386 388 msg_id = self.client.kernel_info()
387 389 while True:
388 390 try:
389 391 reply = self.client.get_shell_msg(timeout=1)
390 392 except Empty:
391 393 break
392 394 else:
393 395 if reply['parent_header'].get('msg_id') == msg_id:
394 396 self.kernel_banner = reply['content'].get('banner', '')
395 397 break
396 398 self.banner += self.kernel_banner
397 399
398 400 def wait_for_kernel(self, timeout=None):
399 401 """method to wait for a kernel to be ready"""
400 402 tic = time.time()
401 403 self.client.hb_channel.unpause()
402 404 while True:
403 405 msg_id = self.client.kernel_info()
404 406 reply = None
405 407 while True:
406 408 try:
407 409 reply = self.client.get_shell_msg(timeout=1)
408 410 except Empty:
409 411 break
410 412 else:
411 413 if reply['parent_header'].get('msg_id') == msg_id:
412 414 return True
413 415 if timeout is not None \
414 416 and (time.time() - tic) > timeout \
415 417 and not self.client.hb_channel.is_beating():
416 418 # heart failed
417 419 return False
418 420 return True
419 421
420 422 def interact(self, display_banner=None):
421 423 """Closely emulate the interactive Python console."""
422 424
423 425 # batch run -> do not interact
424 426 if self.exit_now:
425 427 return
426 428
427 429 if display_banner is None:
428 430 display_banner = self.display_banner
429 431
430 432 if isinstance(display_banner, string_types):
431 433 self.show_banner(display_banner)
432 434 elif display_banner:
433 435 self.show_banner()
434 436
435 437 more = False
436 438
437 439 # run a non-empty no-op, so that we don't get a prompt until
438 440 # we know the kernel is ready. This keeps the connection
439 441 # message above the first prompt.
440 442 if not self.wait_for_kernel(self.kernel_timeout):
441 443 error("Kernel did not respond\n")
442 444 return
443 445
444 446 if self.has_readline:
445 447 self.readline_startup_hook(self.pre_readline)
446 448 hlen_b4_cell = self.readline.get_current_history_length()
447 449 else:
448 450 hlen_b4_cell = 0
449 451 # exit_now is set by a call to %Exit or %Quit, through the
450 452 # ask_exit callback.
451 453
452 454 while not self.exit_now:
453 455 if not self.client.is_alive():
454 456 # kernel died, prompt for action or exit
455 457
456 458 action = "restart" if self.manager else "wait for restart"
457 459 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
458 460 if ans:
459 461 if self.manager:
460 462 self.manager.restart_kernel(True)
461 463 self.wait_for_kernel(self.kernel_timeout)
462 464 else:
463 465 self.exit_now = True
464 466 continue
465 467 try:
466 468 # protect prompt block from KeyboardInterrupt
467 469 # when sitting on ctrl-C
468 470 self.hooks.pre_prompt_hook()
469 471 if more:
470 472 try:
471 473 prompt = self.prompt_manager.render('in2')
472 474 except Exception:
473 475 self.showtraceback()
474 476 if self.autoindent:
475 477 self.rl_do_indent = True
476 478
477 479 else:
478 480 try:
479 481 prompt = self.separate_in + self.prompt_manager.render('in')
480 482 except Exception:
481 483 self.showtraceback()
482 484
483 485 line = self.raw_input(prompt)
484 486 if self.exit_now:
485 487 # quick exit on sys.std[in|out] close
486 488 break
487 489 if self.autoindent:
488 490 self.rl_do_indent = False
489 491
490 492 except KeyboardInterrupt:
491 493 #double-guard against keyboardinterrupts during kbdint handling
492 494 try:
493 495 self.write('\nKeyboardInterrupt\n')
494 496 source_raw = self.input_splitter.raw_reset()
495 497 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
496 498 more = False
497 499 except KeyboardInterrupt:
498 500 pass
499 501 except EOFError:
500 502 if self.autoindent:
501 503 self.rl_do_indent = False
502 504 if self.has_readline:
503 505 self.readline_startup_hook(None)
504 506 self.write('\n')
505 507 self.exit()
506 508 except bdb.BdbQuit:
507 509 warn('The Python debugger has exited with a BdbQuit exception.\n'
508 510 'Because of how pdb handles the stack, it is impossible\n'
509 511 'for IPython to properly format this particular exception.\n'
510 512 'IPython will resume normal operation.')
511 513 except:
512 514 # exceptions here are VERY RARE, but they can be triggered
513 515 # asynchronously by signal handlers, for example.
514 516 self.showtraceback()
515 517 else:
516 518 try:
517 519 self.input_splitter.push(line)
518 520 more = self.input_splitter.push_accepts_more()
519 521 except SyntaxError:
520 522 # Run the code directly - run_cell takes care of displaying
521 523 # the exception.
522 524 more = False
523 525 if (self.SyntaxTB.last_syntax_error and
524 526 self.autoedit_syntax):
525 527 self.edit_syntax_error()
526 528 if not more:
527 529 source_raw = self.input_splitter.raw_reset()
528 530 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
529 531 self.run_cell(source_raw)
530 532
531 533
532 534 # Turn off the exit flag, so the mainloop can be restarted if desired
533 535 self.exit_now = False
General Comments 0
You need to be logged in to leave comments. Login now