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