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