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