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