##// END OF EJS Templates
removed snip_print fn because unused...
Naveen Honest Raj K -
Show More
@@ -1,372 +1,343 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 Notes
5 Notes
6 -----
6 -----
7
7
8 For now this uses IPython hooks, so it can't be in IPython.utils. If we can get
8 For now this uses IPython hooks, so it can't be in IPython.utils. If we can get
9 rid of that dependency, we could move it there.
9 rid of that dependency, we could move it there.
10 -----
10 -----
11 """
11 """
12
12
13 # Copyright (c) IPython Development Team.
13 # Copyright (c) IPython Development Team.
14 # Distributed under the terms of the Modified BSD License.
14 # Distributed under the terms of the Modified BSD License.
15
15
16
16
17 import os
17 import os
18 import io
18 import io
19 import re
19 import re
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import subprocess
22 import subprocess
23
23
24 from io import UnsupportedOperation
24 from io import UnsupportedOperation
25
25
26 from IPython import get_ipython
26 from IPython import get_ipython
27 from IPython.core.display import display
27 from IPython.core.display import display
28 from IPython.core.error import TryNext
28 from IPython.core.error import TryNext
29 from IPython.utils.data import chop
29 from IPython.utils.data import chop
30 from IPython.utils.process import system
30 from IPython.utils.process import system
31 from IPython.utils.terminal import get_terminal_size
31 from IPython.utils.terminal import get_terminal_size
32 from IPython.utils import py3compat
32 from IPython.utils import py3compat
33
33
34
34
35 def display_page(strng, start=0, screen_lines=25):
35 def display_page(strng, start=0, screen_lines=25):
36 """Just display, no paging. screen_lines is ignored."""
36 """Just display, no paging. screen_lines is ignored."""
37 if isinstance(strng, dict):
37 if isinstance(strng, dict):
38 data = strng
38 data = strng
39 else:
39 else:
40 if start:
40 if start:
41 strng = u'\n'.join(strng.splitlines()[start:])
41 strng = u'\n'.join(strng.splitlines()[start:])
42 data = { 'text/plain': strng }
42 data = { 'text/plain': strng }
43 display(data, raw=True)
43 display(data, raw=True)
44
44
45
45
46 def as_hook(page_func):
46 def as_hook(page_func):
47 """Wrap a pager func to strip the `self` arg
47 """Wrap a pager func to strip the `self` arg
48
48
49 so it can be called as a hook.
49 so it can be called as a hook.
50 """
50 """
51 return lambda self, *args, **kwargs: page_func(*args, **kwargs)
51 return lambda self, *args, **kwargs: page_func(*args, **kwargs)
52
52
53
53
54 esc_re = re.compile(r"(\x1b[^m]+m)")
54 esc_re = re.compile(r"(\x1b[^m]+m)")
55
55
56 def page_dumb(strng, start=0, screen_lines=25):
56 def page_dumb(strng, start=0, screen_lines=25):
57 """Very dumb 'pager' in Python, for when nothing else works.
57 """Very dumb 'pager' in Python, for when nothing else works.
58
58
59 Only moves forward, same interface as page(), except for pager_cmd and
59 Only moves forward, same interface as page(), except for pager_cmd and
60 mode.
60 mode.
61 """
61 """
62 if isinstance(strng, dict):
62 if isinstance(strng, dict):
63 strng = strng.get('text/plain', '')
63 strng = strng.get('text/plain', '')
64 out_ln = strng.splitlines()[start:]
64 out_ln = strng.splitlines()[start:]
65 screens = chop(out_ln,screen_lines-1)
65 screens = chop(out_ln,screen_lines-1)
66 if len(screens) == 1:
66 if len(screens) == 1:
67 print(os.linesep.join(screens[0]))
67 print(os.linesep.join(screens[0]))
68 else:
68 else:
69 last_escape = ""
69 last_escape = ""
70 for scr in screens[0:-1]:
70 for scr in screens[0:-1]:
71 hunk = os.linesep.join(scr)
71 hunk = os.linesep.join(scr)
72 print(last_escape + hunk)
72 print(last_escape + hunk)
73 if not page_more():
73 if not page_more():
74 return
74 return
75 esc_list = esc_re.findall(hunk)
75 esc_list = esc_re.findall(hunk)
76 if len(esc_list) > 0:
76 if len(esc_list) > 0:
77 last_escape = esc_list[-1]
77 last_escape = esc_list[-1]
78 print(last_escape + os.linesep.join(screens[-1]))
78 print(last_escape + os.linesep.join(screens[-1]))
79
79
80 def _detect_screen_size(screen_lines_def):
80 def _detect_screen_size(screen_lines_def):
81 """Attempt to work out the number of lines on the screen.
81 """Attempt to work out the number of lines on the screen.
82
82
83 This is called by page(). It can raise an error (e.g. when run in the
83 This is called by page(). It can raise an error (e.g. when run in the
84 test suite), so it's separated out so it can easily be called in a try block.
84 test suite), so it's separated out so it can easily be called in a try block.
85 """
85 """
86 TERM = os.environ.get('TERM',None)
86 TERM = os.environ.get('TERM',None)
87 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
87 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
88 # curses causes problems on many terminals other than xterm, and
88 # curses causes problems on many terminals other than xterm, and
89 # some termios calls lock up on Sun OS5.
89 # some termios calls lock up on Sun OS5.
90 return screen_lines_def
90 return screen_lines_def
91
91
92 try:
92 try:
93 import termios
93 import termios
94 import curses
94 import curses
95 except ImportError:
95 except ImportError:
96 return screen_lines_def
96 return screen_lines_def
97
97
98 # There is a bug in curses, where *sometimes* it fails to properly
98 # There is a bug in curses, where *sometimes* it fails to properly
99 # initialize, and then after the endwin() call is made, the
99 # initialize, and then after the endwin() call is made, the
100 # terminal is left in an unusable state. Rather than trying to
100 # terminal is left in an unusable state. Rather than trying to
101 # check every time for this (by requesting and comparing termios
101 # check every time for this (by requesting and comparing termios
102 # flags each time), we just save the initial terminal state and
102 # flags each time), we just save the initial terminal state and
103 # unconditionally reset it every time. It's cheaper than making
103 # unconditionally reset it every time. It's cheaper than making
104 # the checks.
104 # the checks.
105 try:
105 try:
106 term_flags = termios.tcgetattr(sys.stdout)
106 term_flags = termios.tcgetattr(sys.stdout)
107 except termios.error as err:
107 except termios.error as err:
108 # can fail on Linux 2.6, pager_page will catch the TypeError
108 # can fail on Linux 2.6, pager_page will catch the TypeError
109 raise TypeError('termios error: {0}'.format(err))
109 raise TypeError('termios error: {0}'.format(err))
110
110
111 try:
111 try:
112 scr = curses.initscr()
112 scr = curses.initscr()
113 except AttributeError:
113 except AttributeError:
114 # Curses on Solaris may not be complete, so we can't use it there
114 # Curses on Solaris may not be complete, so we can't use it there
115 return screen_lines_def
115 return screen_lines_def
116
116
117 screen_lines_real,screen_cols = scr.getmaxyx()
117 screen_lines_real,screen_cols = scr.getmaxyx()
118 curses.endwin()
118 curses.endwin()
119
119
120 # Restore terminal state in case endwin() didn't.
120 # Restore terminal state in case endwin() didn't.
121 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
121 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
122 # Now we have what we needed: the screen size in rows/columns
122 # Now we have what we needed: the screen size in rows/columns
123 return screen_lines_real
123 return screen_lines_real
124 #print '***Screen size:',screen_lines_real,'lines x',\
124 #print '***Screen size:',screen_lines_real,'lines x',\
125 #screen_cols,'columns.' # dbg
125 #screen_cols,'columns.' # dbg
126
126
127 def pager_page(strng, start=0, screen_lines=0, pager_cmd=None):
127 def pager_page(strng, start=0, screen_lines=0, pager_cmd=None):
128 """Display a string, piping through a pager after a certain length.
128 """Display a string, piping through a pager after a certain length.
129
129
130 strng can be a mime-bundle dict, supplying multiple representations,
130 strng can be a mime-bundle dict, supplying multiple representations,
131 keyed by mime-type.
131 keyed by mime-type.
132
132
133 The screen_lines parameter specifies the number of *usable* lines of your
133 The screen_lines parameter specifies the number of *usable* lines of your
134 terminal screen (total lines minus lines you need to reserve to show other
134 terminal screen (total lines minus lines you need to reserve to show other
135 information).
135 information).
136
136
137 If you set screen_lines to a number <=0, page() will try to auto-determine
137 If you set screen_lines to a number <=0, page() will try to auto-determine
138 your screen size and will only use up to (screen_size+screen_lines) for
138 your screen size and will only use up to (screen_size+screen_lines) for
139 printing, paging after that. That is, if you want auto-detection but need
139 printing, paging after that. That is, if you want auto-detection but need
140 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
140 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
141 auto-detection without any lines reserved simply use screen_lines = 0.
141 auto-detection without any lines reserved simply use screen_lines = 0.
142
142
143 If a string won't fit in the allowed lines, it is sent through the
143 If a string won't fit in the allowed lines, it is sent through the
144 specified pager command. If none given, look for PAGER in the environment,
144 specified pager command. If none given, look for PAGER in the environment,
145 and ultimately default to less.
145 and ultimately default to less.
146
146
147 If no system pager works, the string is sent through a 'dumb pager'
147 If no system pager works, the string is sent through a 'dumb pager'
148 written in python, very simplistic.
148 written in python, very simplistic.
149 """
149 """
150
150
151 # for compatibility with mime-bundle form:
151 # for compatibility with mime-bundle form:
152 if isinstance(strng, dict):
152 if isinstance(strng, dict):
153 strng = strng['text/plain']
153 strng = strng['text/plain']
154
154
155 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
155 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
156 TERM = os.environ.get('TERM','dumb')
156 TERM = os.environ.get('TERM','dumb')
157 if TERM in ['dumb','emacs'] and os.name != 'nt':
157 if TERM in ['dumb','emacs'] and os.name != 'nt':
158 print(strng)
158 print(strng)
159 return
159 return
160 # chop off the topmost part of the string we don't want to see
160 # chop off the topmost part of the string we don't want to see
161 str_lines = strng.splitlines()[start:]
161 str_lines = strng.splitlines()[start:]
162 str_toprint = os.linesep.join(str_lines)
162 str_toprint = os.linesep.join(str_lines)
163 num_newlines = len(str_lines)
163 num_newlines = len(str_lines)
164 len_str = len(str_toprint)
164 len_str = len(str_toprint)
165
165
166 # Dumb heuristics to guesstimate number of on-screen lines the string
166 # Dumb heuristics to guesstimate number of on-screen lines the string
167 # takes. Very basic, but good enough for docstrings in reasonable
167 # takes. Very basic, but good enough for docstrings in reasonable
168 # terminals. If someone later feels like refining it, it's not hard.
168 # terminals. If someone later feels like refining it, it's not hard.
169 numlines = max(num_newlines,int(len_str/80)+1)
169 numlines = max(num_newlines,int(len_str/80)+1)
170
170
171 screen_lines_def = get_terminal_size()[1]
171 screen_lines_def = get_terminal_size()[1]
172
172
173 # auto-determine screen size
173 # auto-determine screen size
174 if screen_lines <= 0:
174 if screen_lines <= 0:
175 try:
175 try:
176 screen_lines += _detect_screen_size(screen_lines_def)
176 screen_lines += _detect_screen_size(screen_lines_def)
177 except (TypeError, UnsupportedOperation):
177 except (TypeError, UnsupportedOperation):
178 print(str_toprint)
178 print(str_toprint)
179 return
179 return
180
180
181 #print 'numlines',numlines,'screenlines',screen_lines # dbg
181 #print 'numlines',numlines,'screenlines',screen_lines # dbg
182 if numlines <= screen_lines :
182 if numlines <= screen_lines :
183 #print '*** normal print' # dbg
183 #print '*** normal print' # dbg
184 print(str_toprint)
184 print(str_toprint)
185 else:
185 else:
186 # Try to open pager and default to internal one if that fails.
186 # Try to open pager and default to internal one if that fails.
187 # All failure modes are tagged as 'retval=1', to match the return
187 # All failure modes are tagged as 'retval=1', to match the return
188 # value of a failed system command. If any intermediate attempt
188 # value of a failed system command. If any intermediate attempt
189 # sets retval to 1, at the end we resort to our own page_dumb() pager.
189 # sets retval to 1, at the end we resort to our own page_dumb() pager.
190 pager_cmd = get_pager_cmd(pager_cmd)
190 pager_cmd = get_pager_cmd(pager_cmd)
191 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
191 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
192 if os.name == 'nt':
192 if os.name == 'nt':
193 if pager_cmd.startswith('type'):
193 if pager_cmd.startswith('type'):
194 # The default WinXP 'type' command is failing on complex strings.
194 # The default WinXP 'type' command is failing on complex strings.
195 retval = 1
195 retval = 1
196 else:
196 else:
197 fd, tmpname = tempfile.mkstemp('.txt')
197 fd, tmpname = tempfile.mkstemp('.txt')
198 try:
198 try:
199 os.close(fd)
199 os.close(fd)
200 with open(tmpname, 'wt') as tmpfile:
200 with open(tmpname, 'wt') as tmpfile:
201 tmpfile.write(strng)
201 tmpfile.write(strng)
202 cmd = "%s < %s" % (pager_cmd, tmpname)
202 cmd = "%s < %s" % (pager_cmd, tmpname)
203 # tmpfile needs to be closed for windows
203 # tmpfile needs to be closed for windows
204 if os.system(cmd):
204 if os.system(cmd):
205 retval = 1
205 retval = 1
206 else:
206 else:
207 retval = None
207 retval = None
208 finally:
208 finally:
209 os.remove(tmpname)
209 os.remove(tmpname)
210 else:
210 else:
211 try:
211 try:
212 retval = None
212 retval = None
213 # Emulate os.popen, but redirect stderr
213 # Emulate os.popen, but redirect stderr
214 proc = subprocess.Popen(pager_cmd,
214 proc = subprocess.Popen(pager_cmd,
215 shell=True,
215 shell=True,
216 stdin=subprocess.PIPE,
216 stdin=subprocess.PIPE,
217 stderr=subprocess.DEVNULL
217 stderr=subprocess.DEVNULL
218 )
218 )
219 pager = os._wrap_close(io.TextIOWrapper(proc.stdin), proc)
219 pager = os._wrap_close(io.TextIOWrapper(proc.stdin), proc)
220 try:
220 try:
221 pager_encoding = pager.encoding or sys.stdout.encoding
221 pager_encoding = pager.encoding or sys.stdout.encoding
222 pager.write(strng)
222 pager.write(strng)
223 finally:
223 finally:
224 retval = pager.close()
224 retval = pager.close()
225 except IOError as msg: # broken pipe when user quits
225 except IOError as msg: # broken pipe when user quits
226 if msg.args == (32, 'Broken pipe'):
226 if msg.args == (32, 'Broken pipe'):
227 retval = None
227 retval = None
228 else:
228 else:
229 retval = 1
229 retval = 1
230 except OSError:
230 except OSError:
231 # Other strange problems, sometimes seen in Win2k/cygwin
231 # Other strange problems, sometimes seen in Win2k/cygwin
232 retval = 1
232 retval = 1
233 if retval is not None:
233 if retval is not None:
234 page_dumb(strng,screen_lines=screen_lines)
234 page_dumb(strng,screen_lines=screen_lines)
235
235
236
236
237 def page(data, start=0, screen_lines=0, pager_cmd=None):
237 def page(data, start=0, screen_lines=0, pager_cmd=None):
238 """Display content in a pager, piping through a pager after a certain length.
238 """Display content in a pager, piping through a pager after a certain length.
239
239
240 data can be a mime-bundle dict, supplying multiple representations,
240 data can be a mime-bundle dict, supplying multiple representations,
241 keyed by mime-type, or text.
241 keyed by mime-type, or text.
242
242
243 Pager is dispatched via the `show_in_pager` IPython hook.
243 Pager is dispatched via the `show_in_pager` IPython hook.
244 If no hook is registered, `pager_page` will be used.
244 If no hook is registered, `pager_page` will be used.
245 """
245 """
246 # Some routines may auto-compute start offsets incorrectly and pass a
246 # Some routines may auto-compute start offsets incorrectly and pass a
247 # negative value. Offset to 0 for robustness.
247 # negative value. Offset to 0 for robustness.
248 start = max(0, start)
248 start = max(0, start)
249
249
250 # first, try the hook
250 # first, try the hook
251 ip = get_ipython()
251 ip = get_ipython()
252 if ip:
252 if ip:
253 try:
253 try:
254 ip.hooks.show_in_pager(data, start=start, screen_lines=screen_lines)
254 ip.hooks.show_in_pager(data, start=start, screen_lines=screen_lines)
255 return
255 return
256 except TryNext:
256 except TryNext:
257 pass
257 pass
258
258
259 # fallback on default pager
259 # fallback on default pager
260 return pager_page(data, start, screen_lines, pager_cmd)
260 return pager_page(data, start, screen_lines, pager_cmd)
261
261
262
262
263 def page_file(fname, start=0, pager_cmd=None):
263 def page_file(fname, start=0, pager_cmd=None):
264 """Page a file, using an optional pager command and starting line.
264 """Page a file, using an optional pager command and starting line.
265 """
265 """
266
266
267 pager_cmd = get_pager_cmd(pager_cmd)
267 pager_cmd = get_pager_cmd(pager_cmd)
268 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
268 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
269
269
270 try:
270 try:
271 if os.environ['TERM'] in ['emacs','dumb']:
271 if os.environ['TERM'] in ['emacs','dumb']:
272 raise EnvironmentError
272 raise EnvironmentError
273 system(pager_cmd + ' ' + fname)
273 system(pager_cmd + ' ' + fname)
274 except:
274 except:
275 try:
275 try:
276 if start > 0:
276 if start > 0:
277 start -= 1
277 start -= 1
278 page(open(fname).read(),start)
278 page(open(fname).read(),start)
279 except:
279 except:
280 print('Unable to show file',repr(fname))
280 print('Unable to show file',repr(fname))
281
281
282
282
283 def get_pager_cmd(pager_cmd=None):
283 def get_pager_cmd(pager_cmd=None):
284 """Return a pager command.
284 """Return a pager command.
285
285
286 Makes some attempts at finding an OS-correct one.
286 Makes some attempts at finding an OS-correct one.
287 """
287 """
288 if os.name == 'posix':
288 if os.name == 'posix':
289 default_pager_cmd = 'less -R' # -R for color control sequences
289 default_pager_cmd = 'less -R' # -R for color control sequences
290 elif os.name in ['nt','dos']:
290 elif os.name in ['nt','dos']:
291 default_pager_cmd = 'type'
291 default_pager_cmd = 'type'
292
292
293 if pager_cmd is None:
293 if pager_cmd is None:
294 try:
294 try:
295 pager_cmd = os.environ['PAGER']
295 pager_cmd = os.environ['PAGER']
296 except:
296 except:
297 pager_cmd = default_pager_cmd
297 pager_cmd = default_pager_cmd
298
298
299 if pager_cmd == 'less' and '-r' not in os.environ.get('LESS', '').lower():
299 if pager_cmd == 'less' and '-r' not in os.environ.get('LESS', '').lower():
300 pager_cmd += ' -R'
300 pager_cmd += ' -R'
301
301
302 return pager_cmd
302 return pager_cmd
303
303
304
304
305 def get_pager_start(pager, start):
305 def get_pager_start(pager, start):
306 """Return the string for paging files with an offset.
306 """Return the string for paging files with an offset.
307
307
308 This is the '+N' argument which less and more (under Unix) accept.
308 This is the '+N' argument which less and more (under Unix) accept.
309 """
309 """
310
310
311 if pager in ['less','more']:
311 if pager in ['less','more']:
312 if start:
312 if start:
313 start_string = '+' + str(start)
313 start_string = '+' + str(start)
314 else:
314 else:
315 start_string = ''
315 start_string = ''
316 else:
316 else:
317 start_string = ''
317 start_string = ''
318 return start_string
318 return start_string
319
319
320
320
321 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
321 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
322 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
322 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
323 import msvcrt
323 import msvcrt
324 def page_more():
324 def page_more():
325 """ Smart pausing between pages
325 """ Smart pausing between pages
326
326
327 @return: True if need print more lines, False if quit
327 @return: True if need print more lines, False if quit
328 """
328 """
329 sys.stdout.write('---Return to continue, q to quit--- ')
329 sys.stdout.write('---Return to continue, q to quit--- ')
330 ans = msvcrt.getwch()
330 ans = msvcrt.getwch()
331 if ans in ("q", "Q"):
331 if ans in ("q", "Q"):
332 result = False
332 result = False
333 else:
333 else:
334 result = True
334 result = True
335 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
335 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
336 return result
336 return result
337 else:
337 else:
338 def page_more():
338 def page_more():
339 ans = py3compat.input('---Return to continue, q to quit--- ')
339 ans = py3compat.input('---Return to continue, q to quit--- ')
340 if ans.lower().startswith('q'):
340 if ans.lower().startswith('q'):
341 return False
341 return False
342 else:
342 else:
343 return True
343 return True
344
345
346 def snip_print(str,width = 75,print_full = 0,header = ''):
347 """Print a string snipping the midsection to fit in width.
348
349 print_full: mode control:
350
351 - 0: only snip long strings
352 - 1: send to page() directly.
353 - 2: snip long strings and ask for full length viewing with page()
354
355 Return 1 if snipping was necessary, 0 otherwise."""
356
357 if print_full == 1:
358 page(header+str)
359 return 0
360
361 print(header, end=' ')
362 if len(str) < width:
363 print(str)
364 snip = 0
365 else:
366 whalf = int((width -5)/2)
367 print(str[:whalf] + ' <...> ' + str[-whalf:])
368 snip = 1
369 if snip and print_full == 2:
370 if py3compat.input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
371 page(str)
372 return snip
General Comments 0
You need to be logged in to leave comments. Login now