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