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