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