##// END OF EJS Templates
Make page function more robust.
Thomas Kluyver -
Show More
@@ -1,326 +1,332 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-2009 The IPython Development Team
19 # Copyright (C) 2008-2009 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
28
29 import os
29 import os
30 import re
30 import re
31 import sys
31 import sys
32 import tempfile
32 import tempfile
33
33
34 from IPython.core import ipapi
34 from IPython.core import ipapi
35 from IPython.core.error import TryNext
35 from IPython.core.error import TryNext
36 from IPython.utils.cursesimport import use_curses
36 from IPython.utils.cursesimport import use_curses
37 from IPython.utils.data import chop
37 from IPython.utils.data import chop
38 from IPython.utils import io
38 from IPython.utils import io
39 from IPython.utils.process import system
39 from IPython.utils.process import system
40 from IPython.utils.terminal import get_terminal_size
40 from IPython.utils.terminal import get_terminal_size
41
41
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Classes and functions
44 # Classes and functions
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 esc_re = re.compile(r"(\x1b[^m]+m)")
47 esc_re = re.compile(r"(\x1b[^m]+m)")
48
48
49 def page_dumb(strng, start=0, screen_lines=25):
49 def page_dumb(strng, start=0, screen_lines=25):
50 """Very dumb 'pager' in Python, for when nothing else works.
50 """Very dumb 'pager' in Python, for when nothing else works.
51
51
52 Only moves forward, same interface as page(), except for pager_cmd and
52 Only moves forward, same interface as page(), except for pager_cmd and
53 mode."""
53 mode."""
54
54
55 out_ln = strng.splitlines()[start:]
55 out_ln = strng.splitlines()[start:]
56 screens = chop(out_ln,screen_lines-1)
56 screens = chop(out_ln,screen_lines-1)
57 if len(screens) == 1:
57 if len(screens) == 1:
58 print >>io.stdout, os.linesep.join(screens[0])
58 print >>io.stdout, os.linesep.join(screens[0])
59 else:
59 else:
60 last_escape = ""
60 last_escape = ""
61 for scr in screens[0:-1]:
61 for scr in screens[0:-1]:
62 hunk = os.linesep.join(scr)
62 hunk = os.linesep.join(scr)
63 print >>io.stdout, last_escape + hunk
63 print >>io.stdout, last_escape + hunk
64 if not page_more():
64 if not page_more():
65 return
65 return
66 esc_list = esc_re.findall(hunk)
66 esc_list = esc_re.findall(hunk)
67 if len(esc_list) > 0:
67 if len(esc_list) > 0:
68 last_escape = esc_list[-1]
68 last_escape = esc_list[-1]
69 print >>io.stdout, last_escape + os.linesep.join(screens[-1])
69 print >>io.stdout, last_escape + os.linesep.join(screens[-1])
70
70
71 def _detect_screen_size(use_curses, screen_lines_def):
72 if (TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5':
73 local_use_curses = use_curses
74 else:
75 # curses causes problems on many terminals other than xterm, and
76 # some termios calls lock up on Sun OS5.
77 local_use_curses = False
78 if local_use_curses:
79 import termios
80 import curses
81 # There is a bug in curses, where *sometimes* it fails to properly
82 # initialize, and then after the endwin() call is made, the
83 # terminal is left in an unusable state. Rather than trying to
84 # check everytime for this (by requesting and comparing termios
85 # flags each time), we just save the initial terminal state and
86 # unconditionally reset it every time. It's cheaper than making
87 # the checks.
88 term_flags = termios.tcgetattr(sys.stdout)
89
90 # Curses modifies the stdout buffer size by default, which messes
91 # up Python's normal stdout buffering. This would manifest itself
92 # to IPython users as delayed printing on stdout after having used
93 # the pager.
94 #
95 # We can prevent this by manually setting the NCURSES_NO_SETBUF
96 # environment variable. For more details, see:
97 # http://bugs.python.org/issue10144
98 NCURSES_NO_SETBUF = os.environ.get('NCURSES_NO_SETBUF', None)
99 os.environ['NCURSES_NO_SETBUF'] = ''
100
101 # Proceed with curses initialization
102 scr = curses.initscr()
103 screen_lines_real,screen_cols = scr.getmaxyx()
104 curses.endwin()
105
106 # Restore environment
107 if NCURSES_NO_SETBUF is None:
108 del os.environ['NCURSES_NO_SETBUF']
109 else:
110 os.environ['NCURSES_NO_SETBUF'] = NCURSES_NO_SETBUF
111
112 # Restore terminal state in case endwin() didn't.
113 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
114 # Now we have what we needed: the screen size in rows/columns
115 return screen_lines_real
116 #print '***Screen size:',screen_lines_real,'lines x',\
117 #screen_cols,'columns.' # dbg
118 else:
119 return screen_lines_def
71
120
72 def page(strng, start=0, screen_lines=0, pager_cmd=None):
121 def page(strng, start=0, screen_lines=0, pager_cmd=None):
73 """Print a string, piping through a pager after a certain length.
122 """Print a string, piping through a pager after a certain length.
74
123
75 The screen_lines parameter specifies the number of *usable* lines of your
124 The screen_lines parameter specifies the number of *usable* lines of your
76 terminal screen (total lines minus lines you need to reserve to show other
125 terminal screen (total lines minus lines you need to reserve to show other
77 information).
126 information).
78
127
79 If you set screen_lines to a number <=0, page() will try to auto-determine
128 If you set screen_lines to a number <=0, page() will try to auto-determine
80 your screen size and will only use up to (screen_size+screen_lines) for
129 your screen size and will only use up to (screen_size+screen_lines) for
81 printing, paging after that. That is, if you want auto-detection but need
130 printing, paging after that. That is, if you want auto-detection but need
82 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
131 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
83 auto-detection without any lines reserved simply use screen_lines = 0.
132 auto-detection without any lines reserved simply use screen_lines = 0.
84
133
85 If a string won't fit in the allowed lines, it is sent through the
134 If a string won't fit in the allowed lines, it is sent through the
86 specified pager command. If none given, look for PAGER in the environment,
135 specified pager command. If none given, look for PAGER in the environment,
87 and ultimately default to less.
136 and ultimately default to less.
88
137
89 If no system pager works, the string is sent through a 'dumb pager'
138 If no system pager works, the string is sent through a 'dumb pager'
90 written in python, very simplistic.
139 written in python, very simplistic.
91 """
140 """
92
141
93 # Some routines may auto-compute start offsets incorrectly and pass a
142 # Some routines may auto-compute start offsets incorrectly and pass a
94 # negative value. Offset to 0 for robustness.
143 # negative value. Offset to 0 for robustness.
95 start = max(0, start)
144 start = max(0, start)
96
145
97 # first, try the hook
146 # first, try the hook
98 ip = ipapi.get()
147 ip = ipapi.get()
99 if ip:
148 if ip:
100 try:
149 try:
101 ip.hooks.show_in_pager(strng)
150 ip.hooks.show_in_pager(strng)
102 return
151 return
103 except TryNext:
152 except TryNext:
104 pass
153 pass
105
154
106 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
155 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
107 TERM = os.environ.get('TERM','dumb')
156 TERM = os.environ.get('TERM','dumb')
108 if TERM in ['dumb','emacs'] and os.name != 'nt':
157 if TERM in ['dumb','emacs'] and os.name != 'nt':
109 print strng
158 print strng
110 return
159 return
111 # 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
112 str_lines = strng.splitlines()[start:]
161 str_lines = strng.splitlines()[start:]
113 str_toprint = os.linesep.join(str_lines)
162 str_toprint = os.linesep.join(str_lines)
114 num_newlines = len(str_lines)
163 num_newlines = len(str_lines)
115 len_str = len(str_toprint)
164 len_str = len(str_toprint)
116
165
117 # Dumb heuristics to guesstimate number of on-screen lines the string
166 # Dumb heuristics to guesstimate number of on-screen lines the string
118 # takes. Very basic, but good enough for docstrings in reasonable
167 # takes. Very basic, but good enough for docstrings in reasonable
119 # 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.
120 numlines = max(num_newlines,int(len_str/80)+1)
169 numlines = max(num_newlines,int(len_str/80)+1)
121
170
122 screen_lines_def = get_terminal_size()[1]
171 screen_lines_def = get_terminal_size()[1]
123
172
124 # auto-determine screen size
173 # auto-determine screen size
125 if screen_lines <= 0:
174 if screen_lines <= 0:
126 if (TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5':
175 try:
127 local_use_curses = use_curses
176 screen_lines += _detect_screen_size(use_curses, screen_lines_def)
128 else:
177 except Exception:
129 # curses causes problems on many terminals other than xterm, and
178 print >>io.stdout, str_toprint
130 # some termios calls lock up on Sun OS5.
179 return
131 local_use_curses = False
132 if local_use_curses:
133 import termios
134 import curses
135 # There is a bug in curses, where *sometimes* it fails to properly
136 # initialize, and then after the endwin() call is made, the
137 # terminal is left in an unusable state. Rather than trying to
138 # check everytime for this (by requesting and comparing termios
139 # flags each time), we just save the initial terminal state and
140 # unconditionally reset it every time. It's cheaper than making
141 # the checks.
142 term_flags = termios.tcgetattr(sys.stdout)
143
144 # Curses modifies the stdout buffer size by default, which messes
145 # up Python's normal stdout buffering. This would manifest itself
146 # to IPython users as delayed printing on stdout after having used
147 # the pager.
148 #
149 # We can prevent this by manually setting the NCURSES_NO_SETBUF
150 # environment variable. For more details, see:
151 # http://bugs.python.org/issue10144
152 NCURSES_NO_SETBUF = os.environ.get('NCURSES_NO_SETBUF', None)
153 os.environ['NCURSES_NO_SETBUF'] = ''
154
155 # Proceed with curses initialization
156 scr = curses.initscr()
157 screen_lines_real,screen_cols = scr.getmaxyx()
158 curses.endwin()
159
160 # Restore environment
161 if NCURSES_NO_SETBUF is None:
162 del os.environ['NCURSES_NO_SETBUF']
163 else:
164 os.environ['NCURSES_NO_SETBUF'] = NCURSES_NO_SETBUF
165
166 # Restore terminal state in case endwin() didn't.
167 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
168 # Now we have what we needed: the screen size in rows/columns
169 screen_lines += screen_lines_real
170 #print '***Screen size:',screen_lines_real,'lines x',\
171 #screen_cols,'columns.' # dbg
172 else:
173 screen_lines += screen_lines_def
174
180
175 #print 'numlines',numlines,'screenlines',screen_lines # dbg
181 #print 'numlines',numlines,'screenlines',screen_lines # dbg
176 if numlines <= screen_lines :
182 if numlines <= screen_lines :
177 #print '*** normal print' # dbg
183 #print '*** normal print' # dbg
178 print >>io.stdout, str_toprint
184 print >>io.stdout, str_toprint
179 else:
185 else:
180 # 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.
181 # 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
182 # value of a failed system command. If any intermediate attempt
188 # value of a failed system command. If any intermediate attempt
183 # 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.
184 pager_cmd = get_pager_cmd(pager_cmd)
190 pager_cmd = get_pager_cmd(pager_cmd)
185 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
191 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
186 if os.name == 'nt':
192 if os.name == 'nt':
187 if pager_cmd.startswith('type'):
193 if pager_cmd.startswith('type'):
188 # The default WinXP 'type' command is failing on complex strings.
194 # The default WinXP 'type' command is failing on complex strings.
189 retval = 1
195 retval = 1
190 else:
196 else:
191 tmpname = tempfile.mktemp('.txt')
197 tmpname = tempfile.mktemp('.txt')
192 tmpfile = file(tmpname,'wt')
198 tmpfile = file(tmpname,'wt')
193 tmpfile.write(strng)
199 tmpfile.write(strng)
194 tmpfile.close()
200 tmpfile.close()
195 cmd = "%s < %s" % (pager_cmd,tmpname)
201 cmd = "%s < %s" % (pager_cmd,tmpname)
196 if os.system(cmd):
202 if os.system(cmd):
197 retval = 1
203 retval = 1
198 else:
204 else:
199 retval = None
205 retval = None
200 os.remove(tmpname)
206 os.remove(tmpname)
201 else:
207 else:
202 try:
208 try:
203 retval = None
209 retval = None
204 # if I use popen4, things hang. No idea why.
210 # if I use popen4, things hang. No idea why.
205 #pager,shell_out = os.popen4(pager_cmd)
211 #pager,shell_out = os.popen4(pager_cmd)
206 pager = os.popen(pager_cmd,'w')
212 pager = os.popen(pager_cmd,'w')
207 pager.write(strng)
213 pager.write(strng)
208 pager.close()
214 pager.close()
209 retval = pager.close() # success returns None
215 retval = pager.close() # success returns None
210 except IOError,msg: # broken pipe when user quits
216 except IOError,msg: # broken pipe when user quits
211 if msg.args == (32,'Broken pipe'):
217 if msg.args == (32,'Broken pipe'):
212 retval = None
218 retval = None
213 else:
219 else:
214 retval = 1
220 retval = 1
215 except OSError:
221 except OSError:
216 # Other strange problems, sometimes seen in Win2k/cygwin
222 # Other strange problems, sometimes seen in Win2k/cygwin
217 retval = 1
223 retval = 1
218 if retval is not None:
224 if retval is not None:
219 page_dumb(strng,screen_lines=screen_lines)
225 page_dumb(strng,screen_lines=screen_lines)
220
226
221
227
222 def page_file(fname, start=0, pager_cmd=None):
228 def page_file(fname, start=0, pager_cmd=None):
223 """Page a file, using an optional pager command and starting line.
229 """Page a file, using an optional pager command and starting line.
224 """
230 """
225
231
226 pager_cmd = get_pager_cmd(pager_cmd)
232 pager_cmd = get_pager_cmd(pager_cmd)
227 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
233 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
228
234
229 try:
235 try:
230 if os.environ['TERM'] in ['emacs','dumb']:
236 if os.environ['TERM'] in ['emacs','dumb']:
231 raise EnvironmentError
237 raise EnvironmentError
232 system(pager_cmd + ' ' + fname)
238 system(pager_cmd + ' ' + fname)
233 except:
239 except:
234 try:
240 try:
235 if start > 0:
241 if start > 0:
236 start -= 1
242 start -= 1
237 page(open(fname).read(),start)
243 page(open(fname).read(),start)
238 except:
244 except:
239 print 'Unable to show file',`fname`
245 print 'Unable to show file',`fname`
240
246
241
247
242 def get_pager_cmd(pager_cmd=None):
248 def get_pager_cmd(pager_cmd=None):
243 """Return a pager command.
249 """Return a pager command.
244
250
245 Makes some attempts at finding an OS-correct one.
251 Makes some attempts at finding an OS-correct one.
246 """
252 """
247 if os.name == 'posix':
253 if os.name == 'posix':
248 default_pager_cmd = 'less -r' # -r for color control sequences
254 default_pager_cmd = 'less -r' # -r for color control sequences
249 elif os.name in ['nt','dos']:
255 elif os.name in ['nt','dos']:
250 default_pager_cmd = 'type'
256 default_pager_cmd = 'type'
251
257
252 if pager_cmd is None:
258 if pager_cmd is None:
253 try:
259 try:
254 pager_cmd = os.environ['PAGER']
260 pager_cmd = os.environ['PAGER']
255 except:
261 except:
256 pager_cmd = default_pager_cmd
262 pager_cmd = default_pager_cmd
257 return pager_cmd
263 return pager_cmd
258
264
259
265
260 def get_pager_start(pager, start):
266 def get_pager_start(pager, start):
261 """Return the string for paging files with an offset.
267 """Return the string for paging files with an offset.
262
268
263 This is the '+N' argument which less and more (under Unix) accept.
269 This is the '+N' argument which less and more (under Unix) accept.
264 """
270 """
265
271
266 if pager in ['less','more']:
272 if pager in ['less','more']:
267 if start:
273 if start:
268 start_string = '+' + str(start)
274 start_string = '+' + str(start)
269 else:
275 else:
270 start_string = ''
276 start_string = ''
271 else:
277 else:
272 start_string = ''
278 start_string = ''
273 return start_string
279 return start_string
274
280
275
281
276 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
282 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
277 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
283 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
278 import msvcrt
284 import msvcrt
279 def page_more():
285 def page_more():
280 """ Smart pausing between pages
286 """ Smart pausing between pages
281
287
282 @return: True if need print more lines, False if quit
288 @return: True if need print more lines, False if quit
283 """
289 """
284 io.stdout.write('---Return to continue, q to quit--- ')
290 io.stdout.write('---Return to continue, q to quit--- ')
285 ans = msvcrt.getch()
291 ans = msvcrt.getch()
286 if ans in ("q", "Q"):
292 if ans in ("q", "Q"):
287 result = False
293 result = False
288 else:
294 else:
289 result = True
295 result = True
290 io.stdout.write("\b"*37 + " "*37 + "\b"*37)
296 io.stdout.write("\b"*37 + " "*37 + "\b"*37)
291 return result
297 return result
292 else:
298 else:
293 def page_more():
299 def page_more():
294 ans = raw_input('---Return to continue, q to quit--- ')
300 ans = raw_input('---Return to continue, q to quit--- ')
295 if ans.lower().startswith('q'):
301 if ans.lower().startswith('q'):
296 return False
302 return False
297 else:
303 else:
298 return True
304 return True
299
305
300
306
301 def snip_print(str,width = 75,print_full = 0,header = ''):
307 def snip_print(str,width = 75,print_full = 0,header = ''):
302 """Print a string snipping the midsection to fit in width.
308 """Print a string snipping the midsection to fit in width.
303
309
304 print_full: mode control:
310 print_full: mode control:
305 - 0: only snip long strings
311 - 0: only snip long strings
306 - 1: send to page() directly.
312 - 1: send to page() directly.
307 - 2: snip long strings and ask for full length viewing with page()
313 - 2: snip long strings and ask for full length viewing with page()
308 Return 1 if snipping was necessary, 0 otherwise."""
314 Return 1 if snipping was necessary, 0 otherwise."""
309
315
310 if print_full == 1:
316 if print_full == 1:
311 page(header+str)
317 page(header+str)
312 return 0
318 return 0
313
319
314 print header,
320 print header,
315 if len(str) < width:
321 if len(str) < width:
316 print str
322 print str
317 snip = 0
323 snip = 0
318 else:
324 else:
319 whalf = int((width -5)/2)
325 whalf = int((width -5)/2)
320 print str[:whalf] + ' <...> ' + str[-whalf:]
326 print str[:whalf] + ' <...> ' + str[-whalf:]
321 snip = 1
327 snip = 1
322 if snip and print_full == 2:
328 if snip and print_full == 2:
323 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
329 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
324 page(str)
330 page(str)
325 return snip
331 return snip
326
332
General Comments 0
You need to be logged in to leave comments. Login now