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