##// END OF EJS Templates
Fix ipipe/ibrowse bug on OSX....
Fernando Perez -
Show More
@@ -1,1767 +1,1767 b''
1 # -*- coding: iso-8859-1 -*-
1 # -*- coding: iso-8859-1 -*-
2
2
3 import curses, fcntl, signal, struct, tty, textwrap, inspect
3 import curses, fcntl, signal, struct, tty, textwrap, inspect
4
4
5 from IPython.core import ipapi
5 from IPython.core import ipapi
6
6
7 import astyle, ipipe
7 import astyle, ipipe
8
8
9
9
10 # Python 2.3 compatibility
10 # Python 2.3 compatibility
11 try:
11 try:
12 set
12 set
13 except NameError:
13 except NameError:
14 import sets
14 import sets
15 set = sets.Set
15 set = sets.Set
16
16
17 # Python 2.3 compatibility
17 # Python 2.3 compatibility
18 try:
18 try:
19 sorted
19 sorted
20 except NameError:
20 except NameError:
21 from ipipe import sorted
21 from ipipe import sorted
22
22
23
23
24 class UnassignedKeyError(Exception):
24 class UnassignedKeyError(Exception):
25 """
25 """
26 Exception that is used for reporting unassigned keys.
26 Exception that is used for reporting unassigned keys.
27 """
27 """
28
28
29
29
30 class UnknownCommandError(Exception):
30 class UnknownCommandError(Exception):
31 """
31 """
32 Exception that is used for reporting unknown commands (this should never
32 Exception that is used for reporting unknown commands (this should never
33 happen).
33 happen).
34 """
34 """
35
35
36
36
37 class CommandError(Exception):
37 class CommandError(Exception):
38 """
38 """
39 Exception that is used for reporting that a command can't be executed.
39 Exception that is used for reporting that a command can't be executed.
40 """
40 """
41
41
42
42
43 class Keymap(dict):
43 class Keymap(dict):
44 """
44 """
45 Stores mapping of keys to commands.
45 Stores mapping of keys to commands.
46 """
46 """
47 def __init__(self):
47 def __init__(self):
48 self._keymap = {}
48 self._keymap = {}
49
49
50 def __setitem__(self, key, command):
50 def __setitem__(self, key, command):
51 if isinstance(key, str):
51 if isinstance(key, str):
52 for c in key:
52 for c in key:
53 dict.__setitem__(self, ord(c), command)
53 dict.__setitem__(self, ord(c), command)
54 else:
54 else:
55 dict.__setitem__(self, key, command)
55 dict.__setitem__(self, key, command)
56
56
57 def __getitem__(self, key):
57 def __getitem__(self, key):
58 if isinstance(key, str):
58 if isinstance(key, str):
59 key = ord(key)
59 key = ord(key)
60 return dict.__getitem__(self, key)
60 return dict.__getitem__(self, key)
61
61
62 def __detitem__(self, key):
62 def __detitem__(self, key):
63 if isinstance(key, str):
63 if isinstance(key, str):
64 key = ord(key)
64 key = ord(key)
65 dict.__detitem__(self, key)
65 dict.__detitem__(self, key)
66
66
67 def register(self, command, *keys):
67 def register(self, command, *keys):
68 for key in keys:
68 for key in keys:
69 self[key] = command
69 self[key] = command
70
70
71 def get(self, key, default=None):
71 def get(self, key, default=None):
72 if isinstance(key, str):
72 if isinstance(key, str):
73 key = ord(key)
73 key = ord(key)
74 return dict.get(self, key, default)
74 return dict.get(self, key, default)
75
75
76 def findkey(self, command, default=ipipe.noitem):
76 def findkey(self, command, default=ipipe.noitem):
77 for (key, commandcandidate) in self.iteritems():
77 for (key, commandcandidate) in self.iteritems():
78 if commandcandidate == command:
78 if commandcandidate == command:
79 return key
79 return key
80 if default is ipipe.noitem:
80 if default is ipipe.noitem:
81 raise KeyError(command)
81 raise KeyError(command)
82 return default
82 return default
83
83
84
84
85 class _BrowserCachedItem(object):
85 class _BrowserCachedItem(object):
86 # This is used internally by ``ibrowse`` to store a item together with its
86 # This is used internally by ``ibrowse`` to store a item together with its
87 # marked status.
87 # marked status.
88 __slots__ = ("item", "marked")
88 __slots__ = ("item", "marked")
89
89
90 def __init__(self, item):
90 def __init__(self, item):
91 self.item = item
91 self.item = item
92 self.marked = False
92 self.marked = False
93
93
94
94
95 class _BrowserHelp(object):
95 class _BrowserHelp(object):
96 style_header = astyle.Style.fromstr("yellow:black:bold")
96 style_header = astyle.Style.fromstr("yellow:black:bold")
97 # This is used internally by ``ibrowse`` for displaying the help screen.
97 # This is used internally by ``ibrowse`` for displaying the help screen.
98 def __init__(self, browser):
98 def __init__(self, browser):
99 self.browser = browser
99 self.browser = browser
100
100
101 def __xrepr__(self, mode):
101 def __xrepr__(self, mode):
102 yield (-1, True)
102 yield (-1, True)
103 if mode == "header" or mode == "footer":
103 if mode == "header" or mode == "footer":
104 yield (astyle.style_default, "ibrowse help screen")
104 yield (astyle.style_default, "ibrowse help screen")
105 else:
105 else:
106 yield (astyle.style_default, repr(self))
106 yield (astyle.style_default, repr(self))
107
107
108 def __iter__(self):
108 def __iter__(self):
109 # Get reverse key mapping
109 # Get reverse key mapping
110 allkeys = {}
110 allkeys = {}
111 for (key, cmd) in self.browser.keymap.iteritems():
111 for (key, cmd) in self.browser.keymap.iteritems():
112 allkeys.setdefault(cmd, []).append(key)
112 allkeys.setdefault(cmd, []).append(key)
113
113
114 fields = ("key", "description")
114 fields = ("key", "description")
115
115
116 commands = []
116 commands = []
117 for name in dir(self.browser):
117 for name in dir(self.browser):
118 if name.startswith("cmd_"):
118 if name.startswith("cmd_"):
119 command = getattr(self.browser, name)
119 command = getattr(self.browser, name)
120 commands.append((inspect.getsourcelines(command)[-1], name[4:], command))
120 commands.append((inspect.getsourcelines(command)[-1], name[4:], command))
121 commands.sort()
121 commands.sort()
122 commands = [(c[1], c[2]) for c in commands]
122 commands = [(c[1], c[2]) for c in commands]
123 for (i, (name, command)) in enumerate(commands):
123 for (i, (name, command)) in enumerate(commands):
124 if i:
124 if i:
125 yield ipipe.Fields(fields, key="", description="")
125 yield ipipe.Fields(fields, key="", description="")
126
126
127 description = command.__doc__
127 description = command.__doc__
128 if description is None:
128 if description is None:
129 lines = []
129 lines = []
130 else:
130 else:
131 lines = [l.strip() for l in description.splitlines() if l.strip()]
131 lines = [l.strip() for l in description.splitlines() if l.strip()]
132 description = "\n".join(lines)
132 description = "\n".join(lines)
133 lines = textwrap.wrap(description, 60)
133 lines = textwrap.wrap(description, 60)
134 keys = allkeys.get(name, [])
134 keys = allkeys.get(name, [])
135
135
136 yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name)))
136 yield ipipe.Fields(fields, key="", description=astyle.Text((self.style_header, name)))
137 for i in xrange(max(len(keys), len(lines))):
137 for i in xrange(max(len(keys), len(lines))):
138 try:
138 try:
139 key = self.browser.keylabel(keys[i])
139 key = self.browser.keylabel(keys[i])
140 except IndexError:
140 except IndexError:
141 key = ""
141 key = ""
142 try:
142 try:
143 line = lines[i]
143 line = lines[i]
144 except IndexError:
144 except IndexError:
145 line = ""
145 line = ""
146 yield ipipe.Fields(fields, key=key, description=line)
146 yield ipipe.Fields(fields, key=key, description=line)
147
147
148
148
149 class _BrowserLevel(object):
149 class _BrowserLevel(object):
150 # This is used internally to store the state (iterator, fetch items,
150 # This is used internally to store the state (iterator, fetch items,
151 # position of cursor and screen, etc.) of one browser level
151 # position of cursor and screen, etc.) of one browser level
152 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
152 # An ``ibrowse`` object keeps multiple ``_BrowserLevel`` objects in
153 # a stack.
153 # a stack.
154 def __init__(self, browser, input, mainsizey, *attrs):
154 def __init__(self, browser, input, mainsizey, *attrs):
155 self.browser = browser
155 self.browser = browser
156 self.input = input
156 self.input = input
157 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
157 self.header = [x for x in ipipe.xrepr(input, "header") if not isinstance(x[0], int)]
158 # iterator for the input
158 # iterator for the input
159 self.iterator = ipipe.xiter(input)
159 self.iterator = ipipe.xiter(input)
160
160
161 # is the iterator exhausted?
161 # is the iterator exhausted?
162 self.exhausted = False
162 self.exhausted = False
163
163
164 # attributes to be display (autodetected if empty)
164 # attributes to be display (autodetected if empty)
165 self.attrs = attrs
165 self.attrs = attrs
166
166
167 # fetched items (+ marked flag)
167 # fetched items (+ marked flag)
168 self.items = ipipe.deque()
168 self.items = ipipe.deque()
169
169
170 # Number of marked objects
170 # Number of marked objects
171 self.marked = 0
171 self.marked = 0
172
172
173 # Vertical cursor position
173 # Vertical cursor position
174 self.cury = 0
174 self.cury = 0
175
175
176 # Horizontal cursor position
176 # Horizontal cursor position
177 self.curx = 0
177 self.curx = 0
178
178
179 # Index of first data column
179 # Index of first data column
180 self.datastartx = 0
180 self.datastartx = 0
181
181
182 # Index of first data line
182 # Index of first data line
183 self.datastarty = 0
183 self.datastarty = 0
184
184
185 # height of the data display area
185 # height of the data display area
186 self.mainsizey = mainsizey
186 self.mainsizey = mainsizey
187
187
188 # width of the data display area (changes when scrolling)
188 # width of the data display area (changes when scrolling)
189 self.mainsizex = 0
189 self.mainsizex = 0
190
190
191 # Size of row number (changes when scrolling)
191 # Size of row number (changes when scrolling)
192 self.numbersizex = 0
192 self.numbersizex = 0
193
193
194 # Attributes to display (in this order)
194 # Attributes to display (in this order)
195 self.displayattrs = []
195 self.displayattrs = []
196
196
197 # index and attribute under the cursor
197 # index and attribute under the cursor
198 self.displayattr = (None, ipipe.noitem)
198 self.displayattr = (None, ipipe.noitem)
199
199
200 # Maps attributes to column widths
200 # Maps attributes to column widths
201 self.colwidths = {}
201 self.colwidths = {}
202
202
203 # Set of hidden attributes
203 # Set of hidden attributes
204 self.hiddenattrs = set()
204 self.hiddenattrs = set()
205
205
206 # This takes care of all the caches etc.
206 # This takes care of all the caches etc.
207 self.moveto(0, 0, refresh=True)
207 self.moveto(0, 0, refresh=True)
208
208
209 def fetch(self, count):
209 def fetch(self, count):
210 # Try to fill ``self.items`` with at least ``count`` objects.
210 # Try to fill ``self.items`` with at least ``count`` objects.
211 have = len(self.items)
211 have = len(self.items)
212 while not self.exhausted and have < count:
212 while not self.exhausted and have < count:
213 try:
213 try:
214 item = self.iterator.next()
214 item = self.iterator.next()
215 except StopIteration:
215 except StopIteration:
216 self.exhausted = True
216 self.exhausted = True
217 break
217 break
218 except (KeyboardInterrupt, SystemExit):
218 except (KeyboardInterrupt, SystemExit):
219 raise
219 raise
220 except Exception, exc:
220 except Exception, exc:
221 have += 1
221 have += 1
222 self.items.append(_BrowserCachedItem(exc))
222 self.items.append(_BrowserCachedItem(exc))
223 self.exhausted = True
223 self.exhausted = True
224 break
224 break
225 else:
225 else:
226 have += 1
226 have += 1
227 self.items.append(_BrowserCachedItem(item))
227 self.items.append(_BrowserCachedItem(item))
228
228
229 def calcdisplayattrs(self):
229 def calcdisplayattrs(self):
230 # Calculate which attributes are available from the objects that are
230 # Calculate which attributes are available from the objects that are
231 # currently visible on screen (and store it in ``self.displayattrs``)
231 # currently visible on screen (and store it in ``self.displayattrs``)
232
232
233 attrs = set()
233 attrs = set()
234 self.displayattrs = []
234 self.displayattrs = []
235 if self.attrs:
235 if self.attrs:
236 # If the browser object specifies a fixed list of attributes,
236 # If the browser object specifies a fixed list of attributes,
237 # simply use it (removing hidden attributes).
237 # simply use it (removing hidden attributes).
238 for attr in self.attrs:
238 for attr in self.attrs:
239 attr = ipipe.upgradexattr(attr)
239 attr = ipipe.upgradexattr(attr)
240 if attr not in attrs and attr not in self.hiddenattrs:
240 if attr not in attrs and attr not in self.hiddenattrs:
241 self.displayattrs.append(attr)
241 self.displayattrs.append(attr)
242 attrs.add(attr)
242 attrs.add(attr)
243 else:
243 else:
244 endy = min(self.datastarty+self.mainsizey, len(self.items))
244 endy = min(self.datastarty+self.mainsizey, len(self.items))
245 for i in xrange(self.datastarty, endy):
245 for i in xrange(self.datastarty, endy):
246 for attr in ipipe.xattrs(self.items[i].item, "default"):
246 for attr in ipipe.xattrs(self.items[i].item, "default"):
247 if attr not in attrs and attr not in self.hiddenattrs:
247 if attr not in attrs and attr not in self.hiddenattrs:
248 self.displayattrs.append(attr)
248 self.displayattrs.append(attr)
249 attrs.add(attr)
249 attrs.add(attr)
250
250
251 def getrow(self, i):
251 def getrow(self, i):
252 # Return a dictionary with the attributes for the object
252 # Return a dictionary with the attributes for the object
253 # ``self.items[i]``. Attribute names are taken from
253 # ``self.items[i]``. Attribute names are taken from
254 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
254 # ``self.displayattrs`` so ``calcdisplayattrs()`` must have been
255 # called before.
255 # called before.
256 row = {}
256 row = {}
257 item = self.items[i].item
257 item = self.items[i].item
258 for attr in self.displayattrs:
258 for attr in self.displayattrs:
259 try:
259 try:
260 value = attr.value(item)
260 value = attr.value(item)
261 except (KeyboardInterrupt, SystemExit):
261 except (KeyboardInterrupt, SystemExit):
262 raise
262 raise
263 except Exception, exc:
263 except Exception, exc:
264 value = exc
264 value = exc
265 # only store attribute if it exists (or we got an exception)
265 # only store attribute if it exists (or we got an exception)
266 if value is not ipipe.noitem:
266 if value is not ipipe.noitem:
267 # remember alignment, length and colored text
267 # remember alignment, length and colored text
268 row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength)
268 row[attr] = ipipe.xformat(value, "cell", self.browser.maxattrlength)
269 return row
269 return row
270
270
271 def calcwidths(self):
271 def calcwidths(self):
272 # Recalculate the displayed fields and their widths.
272 # Recalculate the displayed fields and their widths.
273 # ``calcdisplayattrs()'' must have been called and the cache
273 # ``calcdisplayattrs()'' must have been called and the cache
274 # for attributes of the objects on screen (``self.displayrows``)
274 # for attributes of the objects on screen (``self.displayrows``)
275 # must have been filled. This sets ``self.colwidths`` which maps
275 # must have been filled. This sets ``self.colwidths`` which maps
276 # attribute descriptors to widths.
276 # attribute descriptors to widths.
277 self.colwidths = {}
277 self.colwidths = {}
278 for row in self.displayrows:
278 for row in self.displayrows:
279 for attr in self.displayattrs:
279 for attr in self.displayattrs:
280 try:
280 try:
281 length = row[attr][1]
281 length = row[attr][1]
282 except KeyError:
282 except KeyError:
283 length = 0
283 length = 0
284 # always add attribute to colwidths, even if it doesn't exist
284 # always add attribute to colwidths, even if it doesn't exist
285 if attr not in self.colwidths:
285 if attr not in self.colwidths:
286 self.colwidths[attr] = len(attr.name())
286 self.colwidths[attr] = len(attr.name())
287 newwidth = max(self.colwidths[attr], length)
287 newwidth = max(self.colwidths[attr], length)
288 self.colwidths[attr] = newwidth
288 self.colwidths[attr] = newwidth
289
289
290 # How many characters do we need to paint the largest item number?
290 # How many characters do we need to paint the largest item number?
291 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
291 self.numbersizex = len(str(self.datastarty+self.mainsizey-1))
292 # How must space have we got to display data?
292 # How must space have we got to display data?
293 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
293 self.mainsizex = self.browser.scrsizex-self.numbersizex-3
294 # width of all columns
294 # width of all columns
295 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
295 self.datasizex = sum(self.colwidths.itervalues()) + len(self.colwidths)
296
296
297 def calcdisplayattr(self):
297 def calcdisplayattr(self):
298 # Find out which attribute the cursor is on and store this
298 # Find out which attribute the cursor is on and store this
299 # information in ``self.displayattr``.
299 # information in ``self.displayattr``.
300 pos = 0
300 pos = 0
301 for (i, attr) in enumerate(self.displayattrs):
301 for (i, attr) in enumerate(self.displayattrs):
302 if pos+self.colwidths[attr] >= self.curx:
302 if pos+self.colwidths[attr] >= self.curx:
303 self.displayattr = (i, attr)
303 self.displayattr = (i, attr)
304 break
304 break
305 pos += self.colwidths[attr]+1
305 pos += self.colwidths[attr]+1
306 else:
306 else:
307 self.displayattr = (None, ipipe.noitem)
307 self.displayattr = (None, ipipe.noitem)
308
308
309 def moveto(self, x, y, refresh=False):
309 def moveto(self, x, y, refresh=False):
310 # Move the cursor to the position ``(x,y)`` (in data coordinates,
310 # Move the cursor to the position ``(x,y)`` (in data coordinates,
311 # not in screen coordinates). If ``refresh`` is true, all cached
311 # not in screen coordinates). If ``refresh`` is true, all cached
312 # values will be recalculated (e.g. because the list has been
312 # values will be recalculated (e.g. because the list has been
313 # resorted, so screen positions etc. are no longer valid).
313 # resorted, so screen positions etc. are no longer valid).
314 olddatastarty = self.datastarty
314 olddatastarty = self.datastarty
315 oldx = self.curx
315 oldx = self.curx
316 oldy = self.cury
316 oldy = self.cury
317 x = int(x+0.5)
317 x = int(x+0.5)
318 y = int(y+0.5)
318 y = int(y+0.5)
319 newx = x # remember where we wanted to move
319 newx = x # remember where we wanted to move
320 newy = y # remember where we wanted to move
320 newy = y # remember where we wanted to move
321
321
322 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
322 scrollbordery = min(self.browser.scrollbordery, self.mainsizey//2)
323 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
323 scrollborderx = min(self.browser.scrollborderx, self.mainsizex//2)
324
324
325 # Make sure that the cursor didn't leave the main area vertically
325 # Make sure that the cursor didn't leave the main area vertically
326 if y < 0:
326 if y < 0:
327 y = 0
327 y = 0
328 # try to get enough items to fill the screen
328 # try to get enough items to fill the screen
329 self.fetch(max(y+scrollbordery+1, self.mainsizey))
329 self.fetch(max(y+scrollbordery+1, self.mainsizey))
330 if y >= len(self.items):
330 if y >= len(self.items):
331 y = max(0, len(self.items)-1)
331 y = max(0, len(self.items)-1)
332
332
333 # Make sure that the cursor stays on screen vertically
333 # Make sure that the cursor stays on screen vertically
334 if y < self.datastarty+scrollbordery:
334 if y < self.datastarty+scrollbordery:
335 self.datastarty = max(0, y-scrollbordery)
335 self.datastarty = max(0, y-scrollbordery)
336 elif y >= self.datastarty+self.mainsizey-scrollbordery:
336 elif y >= self.datastarty+self.mainsizey-scrollbordery:
337 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
337 self.datastarty = max(0, min(y-self.mainsizey+scrollbordery+1,
338 len(self.items)-self.mainsizey))
338 len(self.items)-self.mainsizey))
339
339
340 if refresh: # Do we need to refresh the complete display?
340 if refresh: # Do we need to refresh the complete display?
341 self.calcdisplayattrs()
341 self.calcdisplayattrs()
342 endy = min(self.datastarty+self.mainsizey, len(self.items))
342 endy = min(self.datastarty+self.mainsizey, len(self.items))
343 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
343 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
344 self.calcwidths()
344 self.calcwidths()
345 # Did we scroll vertically => update displayrows
345 # Did we scroll vertically => update displayrows
346 # and various other attributes
346 # and various other attributes
347 elif self.datastarty != olddatastarty:
347 elif self.datastarty != olddatastarty:
348 # Recalculate which attributes we have to display
348 # Recalculate which attributes we have to display
349 olddisplayattrs = self.displayattrs
349 olddisplayattrs = self.displayattrs
350 self.calcdisplayattrs()
350 self.calcdisplayattrs()
351 # If there are new attributes, recreate the cache
351 # If there are new attributes, recreate the cache
352 if self.displayattrs != olddisplayattrs:
352 if self.displayattrs != olddisplayattrs:
353 endy = min(self.datastarty+self.mainsizey, len(self.items))
353 endy = min(self.datastarty+self.mainsizey, len(self.items))
354 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
354 self.displayrows = map(self.getrow, xrange(self.datastarty, endy))
355 elif self.datastarty<olddatastarty: # we did scroll up
355 elif self.datastarty<olddatastarty: # we did scroll up
356 # drop rows from the end
356 # drop rows from the end
357 del self.displayrows[self.datastarty-olddatastarty:]
357 del self.displayrows[self.datastarty-olddatastarty:]
358 # fetch new items
358 # fetch new items
359 for i in xrange(min(olddatastarty, self.datastarty+self.mainsizey)-1,
359 for i in xrange(min(olddatastarty, self.datastarty+self.mainsizey)-1,
360 self.datastarty-1, -1):
360 self.datastarty-1, -1):
361 try:
361 try:
362 row = self.getrow(i)
362 row = self.getrow(i)
363 except IndexError:
363 except IndexError:
364 # we didn't have enough objects to fill the screen
364 # we didn't have enough objects to fill the screen
365 break
365 break
366 self.displayrows.insert(0, row)
366 self.displayrows.insert(0, row)
367 else: # we did scroll down
367 else: # we did scroll down
368 # drop rows from the start
368 # drop rows from the start
369 del self.displayrows[:self.datastarty-olddatastarty]
369 del self.displayrows[:self.datastarty-olddatastarty]
370 # fetch new items
370 # fetch new items
371 for i in xrange(max(olddatastarty+self.mainsizey, self.datastarty),
371 for i in xrange(max(olddatastarty+self.mainsizey, self.datastarty),
372 self.datastarty+self.mainsizey):
372 self.datastarty+self.mainsizey):
373 try:
373 try:
374 row = self.getrow(i)
374 row = self.getrow(i)
375 except IndexError:
375 except IndexError:
376 # we didn't have enough objects to fill the screen
376 # we didn't have enough objects to fill the screen
377 break
377 break
378 self.displayrows.append(row)
378 self.displayrows.append(row)
379 self.calcwidths()
379 self.calcwidths()
380
380
381 # Make sure that the cursor didn't leave the data area horizontally
381 # Make sure that the cursor didn't leave the data area horizontally
382 if x < 0:
382 if x < 0:
383 x = 0
383 x = 0
384 elif x >= self.datasizex:
384 elif x >= self.datasizex:
385 x = max(0, self.datasizex-1)
385 x = max(0, self.datasizex-1)
386
386
387 # Make sure that the cursor stays on screen horizontally
387 # Make sure that the cursor stays on screen horizontally
388 if x < self.datastartx+scrollborderx:
388 if x < self.datastartx+scrollborderx:
389 self.datastartx = max(0, x-scrollborderx)
389 self.datastartx = max(0, x-scrollborderx)
390 elif x >= self.datastartx+self.mainsizex-scrollborderx:
390 elif x >= self.datastartx+self.mainsizex-scrollborderx:
391 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
391 self.datastartx = max(0, min(x-self.mainsizex+scrollborderx+1,
392 self.datasizex-self.mainsizex))
392 self.datasizex-self.mainsizex))
393
393
394 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
394 if x == oldx and y == oldy and (x != newx or y != newy): # couldn't move
395 self.browser.beep()
395 self.browser.beep()
396 else:
396 else:
397 self.curx = x
397 self.curx = x
398 self.cury = y
398 self.cury = y
399 self.calcdisplayattr()
399 self.calcdisplayattr()
400
400
401 def sort(self, key, reverse=False):
401 def sort(self, key, reverse=False):
402 """
402 """
403 Sort the currently list of items using the key function ``key``. If
403 Sort the currently list of items using the key function ``key``. If
404 ``reverse`` is true the sort order is reversed.
404 ``reverse`` is true the sort order is reversed.
405 """
405 """
406 curitem = self.items[self.cury] # Remember where the cursor is now
406 curitem = self.items[self.cury] # Remember where the cursor is now
407
407
408 # Sort items
408 # Sort items
409 def realkey(item):
409 def realkey(item):
410 return key(item.item)
410 return key(item.item)
411 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
411 self.items = ipipe.deque(sorted(self.items, key=realkey, reverse=reverse))
412
412
413 # Find out where the object under the cursor went
413 # Find out where the object under the cursor went
414 cury = self.cury
414 cury = self.cury
415 for (i, item) in enumerate(self.items):
415 for (i, item) in enumerate(self.items):
416 if item is curitem:
416 if item is curitem:
417 cury = i
417 cury = i
418 break
418 break
419
419
420 self.moveto(self.curx, cury, refresh=True)
420 self.moveto(self.curx, cury, refresh=True)
421
421
422 def refresh(self):
422 def refresh(self):
423 """
423 """
424 Restart iterating the input.
424 Restart iterating the input.
425 """
425 """
426 self.iterator = ipipe.xiter(self.input)
426 self.iterator = ipipe.xiter(self.input)
427 self.items.clear()
427 self.items.clear()
428 self.exhausted = False
428 self.exhausted = False
429 self.datastartx = self.datastarty = 0
429 self.datastartx = self.datastarty = 0
430 self.moveto(0, 0, refresh=True)
430 self.moveto(0, 0, refresh=True)
431
431
432 def refreshfind(self):
432 def refreshfind(self):
433 """
433 """
434 Restart iterating the input and go back to the same object as before
434 Restart iterating the input and go back to the same object as before
435 (if it can be found in the new iterator).
435 (if it can be found in the new iterator).
436 """
436 """
437 try:
437 try:
438 oldobject = self.items[self.cury].item
438 oldobject = self.items[self.cury].item
439 except IndexError:
439 except IndexError:
440 oldobject = ipipe.noitem
440 oldobject = ipipe.noitem
441 self.iterator = ipipe.xiter(self.input)
441 self.iterator = ipipe.xiter(self.input)
442 self.items.clear()
442 self.items.clear()
443 self.exhausted = False
443 self.exhausted = False
444 while True:
444 while True:
445 self.fetch(len(self.items)+1)
445 self.fetch(len(self.items)+1)
446 if self.exhausted:
446 if self.exhausted:
447 curses.beep()
447 curses.beep()
448 self.datastartx = self.datastarty = 0
448 self.datastartx = self.datastarty = 0
449 self.moveto(self.curx, 0, refresh=True)
449 self.moveto(self.curx, 0, refresh=True)
450 break
450 break
451 if self.items[-1].item == oldobject:
451 if self.items[-1].item == oldobject:
452 self.datastartx = self.datastarty = 0
452 self.datastartx = self.datastarty = 0
453 self.moveto(self.curx, len(self.items)-1, refresh=True)
453 self.moveto(self.curx, len(self.items)-1, refresh=True)
454 break
454 break
455
455
456
456
457 class _CommandInput(object):
457 class _CommandInput(object):
458 keymap = Keymap()
458 keymap = Keymap()
459 keymap.register("left", curses.KEY_LEFT)
459 keymap.register("left", curses.KEY_LEFT)
460 keymap.register("right", curses.KEY_RIGHT)
460 keymap.register("right", curses.KEY_RIGHT)
461 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
461 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
462 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
462 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
463 # FIXME: What's happening here?
463 # FIXME: What's happening here?
464 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
464 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
465 keymap.register("delete", curses.KEY_DC)
465 keymap.register("delete", curses.KEY_DC)
466 keymap.register("delend", 0x0b) # Ctrl-K
466 keymap.register("delend", 0x0b) # Ctrl-K
467 keymap.register("execute", "\r\n")
467 keymap.register("execute", "\r\n")
468 keymap.register("up", curses.KEY_UP)
468 keymap.register("up", curses.KEY_UP)
469 keymap.register("down", curses.KEY_DOWN)
469 keymap.register("down", curses.KEY_DOWN)
470 keymap.register("incsearchup", curses.KEY_PPAGE)
470 keymap.register("incsearchup", curses.KEY_PPAGE)
471 keymap.register("incsearchdown", curses.KEY_NPAGE)
471 keymap.register("incsearchdown", curses.KEY_NPAGE)
472 keymap.register("exit", "\x18"), # Ctrl-X
472 keymap.register("exit", "\x18"), # Ctrl-X
473
473
474 def __init__(self, prompt):
474 def __init__(self, prompt):
475 self.prompt = prompt
475 self.prompt = prompt
476 self.history = []
476 self.history = []
477 self.maxhistory = 100
477 self.maxhistory = 100
478 self.input = ""
478 self.input = ""
479 self.curx = 0
479 self.curx = 0
480 self.cury = -1 # blank line
480 self.cury = -1 # blank line
481
481
482 def start(self):
482 def start(self):
483 self.input = ""
483 self.input = ""
484 self.curx = 0
484 self.curx = 0
485 self.cury = -1 # blank line
485 self.cury = -1 # blank line
486
486
487 def handlekey(self, browser, key):
487 def handlekey(self, browser, key):
488 cmdname = self.keymap.get(key, None)
488 cmdname = self.keymap.get(key, None)
489 if cmdname is not None:
489 if cmdname is not None:
490 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
490 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
491 if cmdfunc is not None:
491 if cmdfunc is not None:
492 return cmdfunc(browser)
492 return cmdfunc(browser)
493 curses.beep()
493 curses.beep()
494 elif key != -1:
494 elif key != -1:
495 try:
495 try:
496 char = chr(key)
496 char = chr(key)
497 except ValueError:
497 except ValueError:
498 curses.beep()
498 curses.beep()
499 else:
499 else:
500 return self.handlechar(browser, char)
500 return self.handlechar(browser, char)
501
501
502 def handlechar(self, browser, char):
502 def handlechar(self, browser, char):
503 self.input = self.input[:self.curx] + char + self.input[self.curx:]
503 self.input = self.input[:self.curx] + char + self.input[self.curx:]
504 self.curx += 1
504 self.curx += 1
505 return True
505 return True
506
506
507 def dohistory(self):
507 def dohistory(self):
508 self.history.insert(0, self.input)
508 self.history.insert(0, self.input)
509 del self.history[:-self.maxhistory]
509 del self.history[:-self.maxhistory]
510
510
511 def cmd_backspace(self, browser):
511 def cmd_backspace(self, browser):
512 if self.curx:
512 if self.curx:
513 self.input = self.input[:self.curx-1] + self.input[self.curx:]
513 self.input = self.input[:self.curx-1] + self.input[self.curx:]
514 self.curx -= 1
514 self.curx -= 1
515 return True
515 return True
516 else:
516 else:
517 curses.beep()
517 curses.beep()
518
518
519 def cmd_delete(self, browser):
519 def cmd_delete(self, browser):
520 if self.curx<len(self.input):
520 if self.curx<len(self.input):
521 self.input = self.input[:self.curx] + self.input[self.curx+1:]
521 self.input = self.input[:self.curx] + self.input[self.curx+1:]
522 return True
522 return True
523 else:
523 else:
524 curses.beep()
524 curses.beep()
525
525
526 def cmd_delend(self, browser):
526 def cmd_delend(self, browser):
527 if self.curx<len(self.input):
527 if self.curx<len(self.input):
528 self.input = self.input[:self.curx]
528 self.input = self.input[:self.curx]
529 return True
529 return True
530
530
531 def cmd_left(self, browser):
531 def cmd_left(self, browser):
532 if self.curx:
532 if self.curx:
533 self.curx -= 1
533 self.curx -= 1
534 return True
534 return True
535 else:
535 else:
536 curses.beep()
536 curses.beep()
537
537
538 def cmd_right(self, browser):
538 def cmd_right(self, browser):
539 if self.curx < len(self.input):
539 if self.curx < len(self.input):
540 self.curx += 1
540 self.curx += 1
541 return True
541 return True
542 else:
542 else:
543 curses.beep()
543 curses.beep()
544
544
545 def cmd_home(self, browser):
545 def cmd_home(self, browser):
546 if self.curx:
546 if self.curx:
547 self.curx = 0
547 self.curx = 0
548 return True
548 return True
549 else:
549 else:
550 curses.beep()
550 curses.beep()
551
551
552 def cmd_end(self, browser):
552 def cmd_end(self, browser):
553 if self.curx < len(self.input):
553 if self.curx < len(self.input):
554 self.curx = len(self.input)
554 self.curx = len(self.input)
555 return True
555 return True
556 else:
556 else:
557 curses.beep()
557 curses.beep()
558
558
559 def cmd_up(self, browser):
559 def cmd_up(self, browser):
560 if self.cury < len(self.history)-1:
560 if self.cury < len(self.history)-1:
561 self.cury += 1
561 self.cury += 1
562 self.input = self.history[self.cury]
562 self.input = self.history[self.cury]
563 self.curx = len(self.input)
563 self.curx = len(self.input)
564 return True
564 return True
565 else:
565 else:
566 curses.beep()
566 curses.beep()
567
567
568 def cmd_down(self, browser):
568 def cmd_down(self, browser):
569 if self.cury >= 0:
569 if self.cury >= 0:
570 self.cury -= 1
570 self.cury -= 1
571 if self.cury>=0:
571 if self.cury>=0:
572 self.input = self.history[self.cury]
572 self.input = self.history[self.cury]
573 else:
573 else:
574 self.input = ""
574 self.input = ""
575 self.curx = len(self.input)
575 self.curx = len(self.input)
576 return True
576 return True
577 else:
577 else:
578 curses.beep()
578 curses.beep()
579
579
580 def cmd_incsearchup(self, browser):
580 def cmd_incsearchup(self, browser):
581 prefix = self.input[:self.curx]
581 prefix = self.input[:self.curx]
582 cury = self.cury
582 cury = self.cury
583 while True:
583 while True:
584 cury += 1
584 cury += 1
585 if cury >= len(self.history):
585 if cury >= len(self.history):
586 break
586 break
587 if self.history[cury].startswith(prefix):
587 if self.history[cury].startswith(prefix):
588 self.input = self.history[cury]
588 self.input = self.history[cury]
589 self.cury = cury
589 self.cury = cury
590 return True
590 return True
591 curses.beep()
591 curses.beep()
592
592
593 def cmd_incsearchdown(self, browser):
593 def cmd_incsearchdown(self, browser):
594 prefix = self.input[:self.curx]
594 prefix = self.input[:self.curx]
595 cury = self.cury
595 cury = self.cury
596 while True:
596 while True:
597 cury -= 1
597 cury -= 1
598 if cury <= 0:
598 if cury <= 0:
599 break
599 break
600 if self.history[cury].startswith(prefix):
600 if self.history[cury].startswith(prefix):
601 self.input = self.history[self.cury]
601 self.input = self.history[self.cury]
602 self.cury = cury
602 self.cury = cury
603 return True
603 return True
604 curses.beep()
604 curses.beep()
605
605
606 def cmd_exit(self, browser):
606 def cmd_exit(self, browser):
607 browser.mode = "default"
607 browser.mode = "default"
608 return True
608 return True
609
609
610 def cmd_execute(self, browser):
610 def cmd_execute(self, browser):
611 raise NotImplementedError
611 raise NotImplementedError
612
612
613
613
614 class _CommandGoto(_CommandInput):
614 class _CommandGoto(_CommandInput):
615 def __init__(self):
615 def __init__(self):
616 _CommandInput.__init__(self, "goto object #")
616 _CommandInput.__init__(self, "goto object #")
617
617
618 def handlechar(self, browser, char):
618 def handlechar(self, browser, char):
619 # Only accept digits
619 # Only accept digits
620 if not "0" <= char <= "9":
620 if not "0" <= char <= "9":
621 curses.beep()
621 curses.beep()
622 else:
622 else:
623 return _CommandInput.handlechar(self, browser, char)
623 return _CommandInput.handlechar(self, browser, char)
624
624
625 def cmd_execute(self, browser):
625 def cmd_execute(self, browser):
626 level = browser.levels[-1]
626 level = browser.levels[-1]
627 if self.input:
627 if self.input:
628 self.dohistory()
628 self.dohistory()
629 level.moveto(level.curx, int(self.input))
629 level.moveto(level.curx, int(self.input))
630 browser.mode = "default"
630 browser.mode = "default"
631 return True
631 return True
632
632
633
633
634 class _CommandFind(_CommandInput):
634 class _CommandFind(_CommandInput):
635 def __init__(self):
635 def __init__(self):
636 _CommandInput.__init__(self, "find expression")
636 _CommandInput.__init__(self, "find expression")
637
637
638 def cmd_execute(self, browser):
638 def cmd_execute(self, browser):
639 level = browser.levels[-1]
639 level = browser.levels[-1]
640 if self.input:
640 if self.input:
641 self.dohistory()
641 self.dohistory()
642 while True:
642 while True:
643 cury = level.cury
643 cury = level.cury
644 level.moveto(level.curx, cury+1)
644 level.moveto(level.curx, cury+1)
645 if cury == level.cury:
645 if cury == level.cury:
646 curses.beep()
646 curses.beep()
647 break # hit end
647 break # hit end
648 item = level.items[level.cury].item
648 item = level.items[level.cury].item
649 try:
649 try:
650 globals = ipipe.getglobals(None)
650 globals = ipipe.getglobals(None)
651 if eval(self.input, globals, ipipe.AttrNamespace(item)):
651 if eval(self.input, globals, ipipe.AttrNamespace(item)):
652 break # found something
652 break # found something
653 except (KeyboardInterrupt, SystemExit):
653 except (KeyboardInterrupt, SystemExit):
654 raise
654 raise
655 except Exception, exc:
655 except Exception, exc:
656 browser.report(exc)
656 browser.report(exc)
657 curses.beep()
657 curses.beep()
658 break # break on error
658 break # break on error
659 browser.mode = "default"
659 browser.mode = "default"
660 return True
660 return True
661
661
662
662
663 class _CommandFindBackwards(_CommandInput):
663 class _CommandFindBackwards(_CommandInput):
664 def __init__(self):
664 def __init__(self):
665 _CommandInput.__init__(self, "find backwards expression")
665 _CommandInput.__init__(self, "find backwards expression")
666
666
667 def cmd_execute(self, browser):
667 def cmd_execute(self, browser):
668 level = browser.levels[-1]
668 level = browser.levels[-1]
669 if self.input:
669 if self.input:
670 self.dohistory()
670 self.dohistory()
671 while level.cury:
671 while level.cury:
672 level.moveto(level.curx, level.cury-1)
672 level.moveto(level.curx, level.cury-1)
673 item = level.items[level.cury].item
673 item = level.items[level.cury].item
674 try:
674 try:
675 globals = ipipe.getglobals(None)
675 globals = ipipe.getglobals(None)
676 if eval(self.input, globals, ipipe.AttrNamespace(item)):
676 if eval(self.input, globals, ipipe.AttrNamespace(item)):
677 break # found something
677 break # found something
678 except (KeyboardInterrupt, SystemExit):
678 except (KeyboardInterrupt, SystemExit):
679 raise
679 raise
680 except Exception, exc:
680 except Exception, exc:
681 browser.report(exc)
681 browser.report(exc)
682 curses.beep()
682 curses.beep()
683 break # break on error
683 break # break on error
684 else:
684 else:
685 curses.beep()
685 curses.beep()
686 browser.mode = "default"
686 browser.mode = "default"
687 return True
687 return True
688
688
689
689
690 class ibrowse(ipipe.Display):
690 class ibrowse(ipipe.Display):
691 # Show this many lines from the previous screen when paging horizontally
691 # Show this many lines from the previous screen when paging horizontally
692 pageoverlapx = 1
692 pageoverlapx = 1
693
693
694 # Show this many lines from the previous screen when paging vertically
694 # Show this many lines from the previous screen when paging vertically
695 pageoverlapy = 1
695 pageoverlapy = 1
696
696
697 # Start scrolling when the cursor is less than this number of columns
697 # Start scrolling when the cursor is less than this number of columns
698 # away from the left or right screen edge
698 # away from the left or right screen edge
699 scrollborderx = 10
699 scrollborderx = 10
700
700
701 # Start scrolling when the cursor is less than this number of lines
701 # Start scrolling when the cursor is less than this number of lines
702 # away from the top or bottom screen edge
702 # away from the top or bottom screen edge
703 scrollbordery = 5
703 scrollbordery = 5
704
704
705 # Accelerate by this factor when scrolling horizontally
705 # Accelerate by this factor when scrolling horizontally
706 acceleratex = 1.05
706 acceleratex = 1.05
707
707
708 # Accelerate by this factor when scrolling vertically
708 # Accelerate by this factor when scrolling vertically
709 acceleratey = 1.05
709 acceleratey = 1.05
710
710
711 # The maximum horizontal scroll speed
711 # The maximum horizontal scroll speed
712 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
712 # (as a factor of the screen width (i.e. 0.5 == half a screen width)
713 maxspeedx = 0.5
713 maxspeedx = 0.5
714
714
715 # The maximum vertical scroll speed
715 # The maximum vertical scroll speed
716 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
716 # (as a factor of the screen height (i.e. 0.5 == half a screen height)
717 maxspeedy = 0.5
717 maxspeedy = 0.5
718
718
719 # The maximum number of header lines for browser level
719 # The maximum number of header lines for browser level
720 # if the nesting is deeper, only the innermost levels are displayed
720 # if the nesting is deeper, only the innermost levels are displayed
721 maxheaders = 5
721 maxheaders = 5
722
722
723 # The approximate maximum length of a column entry
723 # The approximate maximum length of a column entry
724 maxattrlength = 200
724 maxattrlength = 200
725
725
726 # Styles for various parts of the GUI
726 # Styles for various parts of the GUI
727 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
727 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
728 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
728 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
729 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
729 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
730 style_colheader = astyle.Style.fromstr("blue:white:reverse")
730 style_colheader = astyle.Style.fromstr("blue:white:reverse")
731 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
731 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
732 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
732 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
733 style_number = astyle.Style.fromstr("blue:white:reverse")
733 style_number = astyle.Style.fromstr("blue:white:reverse")
734 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
734 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
735 style_sep = astyle.Style.fromstr("blue:black")
735 style_sep = astyle.Style.fromstr("blue:black")
736 style_data = astyle.Style.fromstr("white:black")
736 style_data = astyle.Style.fromstr("white:black")
737 style_datapad = astyle.Style.fromstr("blue:black:bold")
737 style_datapad = astyle.Style.fromstr("blue:black:bold")
738 style_footer = astyle.Style.fromstr("black:white")
738 style_footer = astyle.Style.fromstr("black:white")
739 style_report = astyle.Style.fromstr("white:black")
739 style_report = astyle.Style.fromstr("white:black")
740
740
741 # Column separator in header
741 # Column separator in header
742 headersepchar = "|"
742 headersepchar = "|"
743
743
744 # Character for padding data cell entries
744 # Character for padding data cell entries
745 datapadchar = "."
745 datapadchar = "."
746
746
747 # Column separator in data area
747 # Column separator in data area
748 datasepchar = "|"
748 datasepchar = "|"
749
749
750 # Character to use for "empty" cell (i.e. for non-existing attributes)
750 # Character to use for "empty" cell (i.e. for non-existing attributes)
751 nodatachar = "-"
751 nodatachar = "-"
752
752
753 # Prompts for modes that require keyboard input
753 # Prompts for modes that require keyboard input
754 prompts = {
754 prompts = {
755 "goto": _CommandGoto(),
755 "goto": _CommandGoto(),
756 "find": _CommandFind(),
756 "find": _CommandFind(),
757 "findbackwards": _CommandFindBackwards()
757 "findbackwards": _CommandFindBackwards()
758 }
758 }
759
759
760 # Maps curses key codes to "function" names
760 # Maps curses key codes to "function" names
761 keymap = Keymap()
761 keymap = Keymap()
762 keymap.register("quit", "q")
762 keymap.register("quit", "q")
763 keymap.register("up", curses.KEY_UP)
763 keymap.register("up", curses.KEY_UP)
764 keymap.register("down", curses.KEY_DOWN)
764 keymap.register("down", curses.KEY_DOWN)
765 keymap.register("pageup", curses.KEY_PPAGE)
765 keymap.register("pageup", curses.KEY_PPAGE)
766 keymap.register("pagedown", curses.KEY_NPAGE)
766 keymap.register("pagedown", curses.KEY_NPAGE)
767 keymap.register("left", curses.KEY_LEFT)
767 keymap.register("left", curses.KEY_LEFT)
768 keymap.register("right", curses.KEY_RIGHT)
768 keymap.register("right", curses.KEY_RIGHT)
769 keymap.register("home", curses.KEY_HOME, "\x01")
769 keymap.register("home", curses.KEY_HOME, "\x01")
770 keymap.register("end", curses.KEY_END, "\x05")
770 keymap.register("end", curses.KEY_END, "\x05")
771 keymap.register("prevattr", "<\x1b")
771 keymap.register("prevattr", "<\x1b")
772 keymap.register("nextattr", ">\t")
772 keymap.register("nextattr", ">\t")
773 keymap.register("pick", "p")
773 keymap.register("pick", "p")
774 keymap.register("pickattr", "P")
774 keymap.register("pickattr", "P")
775 keymap.register("pickallattrs", "C")
775 keymap.register("pickallattrs", "C")
776 keymap.register("pickmarked", "m")
776 keymap.register("pickmarked", "m")
777 keymap.register("pickmarkedattr", "M")
777 keymap.register("pickmarkedattr", "M")
778 keymap.register("pickinput", "i")
778 keymap.register("pickinput", "i")
779 keymap.register("pickinputattr", "I")
779 keymap.register("pickinputattr", "I")
780 keymap.register("hideattr", "h")
780 keymap.register("hideattr", "h")
781 keymap.register("unhideattrs", "H")
781 keymap.register("unhideattrs", "H")
782 keymap.register("help", "?")
782 keymap.register("help", "?")
783 keymap.register("enter", "\r\n")
783 keymap.register("enter", "\r\n")
784 keymap.register("enterattr", "E")
784 keymap.register("enterattr", "E")
785 # FIXME: What's happening here?
785 # FIXME: What's happening here?
786 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
786 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
787 keymap.register("detail", "d")
787 keymap.register("detail", "d")
788 keymap.register("detailattr", "D")
788 keymap.register("detailattr", "D")
789 keymap.register("tooglemark", " ")
789 keymap.register("tooglemark", " ")
790 keymap.register("markrange", "%")
790 keymap.register("markrange", "%")
791 keymap.register("sortattrasc", "v")
791 keymap.register("sortattrasc", "v")
792 keymap.register("sortattrdesc", "V")
792 keymap.register("sortattrdesc", "V")
793 keymap.register("goto", "g")
793 keymap.register("goto", "g")
794 keymap.register("find", "f")
794 keymap.register("find", "f")
795 keymap.register("findbackwards", "b")
795 keymap.register("findbackwards", "b")
796 keymap.register("refresh", "r")
796 keymap.register("refresh", "r")
797 keymap.register("refreshfind", "R")
797 keymap.register("refreshfind", "R")
798
798
799 def __init__(self, input=None, *attrs):
799 def __init__(self, input=None, *attrs):
800 """
800 """
801 Create a new browser. If ``attrs`` is not empty, it is the list
801 Create a new browser. If ``attrs`` is not empty, it is the list
802 of attributes that will be displayed in the browser, otherwise
802 of attributes that will be displayed in the browser, otherwise
803 these will be determined by the objects on screen.
803 these will be determined by the objects on screen.
804 """
804 """
805 ipipe.Display.__init__(self, input)
805 ipipe.Display.__init__(self, input)
806
806
807 self.attrs = attrs
807 self.attrs = attrs
808
808
809 # Stack of browser levels
809 # Stack of browser levels
810 self.levels = []
810 self.levels = []
811 # how many colums to scroll (Changes when accelerating)
811 # how many colums to scroll (Changes when accelerating)
812 self.stepx = 1.
812 self.stepx = 1.
813
813
814 # how many rows to scroll (Changes when accelerating)
814 # how many rows to scroll (Changes when accelerating)
815 self.stepy = 1.
815 self.stepy = 1.
816
816
817 # Beep on the edges of the data area? (Will be set to ``False``
817 # Beep on the edges of the data area? (Will be set to ``False``
818 # once the cursor hits the edge of the screen, so we don't get
818 # once the cursor hits the edge of the screen, so we don't get
819 # multiple beeps).
819 # multiple beeps).
820 self._dobeep = True
820 self._dobeep = True
821
821
822 # Cache for registered ``curses`` colors and styles.
822 # Cache for registered ``curses`` colors and styles.
823 self._styles = {}
823 self._styles = {}
824 self._colors = {}
824 self._colors = {}
825 self._maxcolor = 1
825 self._maxcolor = 1
826
826
827 # How many header lines do we want to paint (the numbers of levels
827 # How many header lines do we want to paint (the numbers of levels
828 # we have, but with an upper bound)
828 # we have, but with an upper bound)
829 self._headerlines = 1
829 self._headerlines = 1
830
830
831 # Index of first header line
831 # Index of first header line
832 self._firstheaderline = 0
832 self._firstheaderline = 0
833
833
834 # curses window
834 # curses window
835 self.scr = None
835 self.scr = None
836 # report in the footer line (error, executed command etc.)
836 # report in the footer line (error, executed command etc.)
837 self._report = None
837 self._report = None
838
838
839 # value to be returned to the caller (set by commands)
839 # value to be returned to the caller (set by commands)
840 self.returnvalue = None
840 self.returnvalue = None
841
841
842 # The mode the browser is in
842 # The mode the browser is in
843 # e.g. normal browsing or entering an argument for a command
843 # e.g. normal browsing or entering an argument for a command
844 self.mode = "default"
844 self.mode = "default"
845
845
846 # set by the SIGWINCH signal handler
846 # set by the SIGWINCH signal handler
847 self.resized = False
847 self.resized = False
848
848
849 def nextstepx(self, step):
849 def nextstepx(self, step):
850 """
850 """
851 Accelerate horizontally.
851 Accelerate horizontally.
852 """
852 """
853 return max(1., min(step*self.acceleratex,
853 return max(1., min(step*self.acceleratex,
854 self.maxspeedx*self.levels[-1].mainsizex))
854 self.maxspeedx*self.levels[-1].mainsizex))
855
855
856 def nextstepy(self, step):
856 def nextstepy(self, step):
857 """
857 """
858 Accelerate vertically.
858 Accelerate vertically.
859 """
859 """
860 return max(1., min(step*self.acceleratey,
860 return max(1., min(step*self.acceleratey,
861 self.maxspeedy*self.levels[-1].mainsizey))
861 self.maxspeedy*self.levels[-1].mainsizey))
862
862
863 def getstyle(self, style):
863 def getstyle(self, style):
864 """
864 """
865 Register the ``style`` with ``curses`` or get it from the cache,
865 Register the ``style`` with ``curses`` or get it from the cache,
866 if it has been registered before.
866 if it has been registered before.
867 """
867 """
868 try:
868 try:
869 return self._styles[style.fg, style.bg, style.attrs]
869 return self._styles[style.fg, style.bg, style.attrs]
870 except KeyError:
870 except KeyError:
871 attrs = 0
871 attrs = 0
872 for b in astyle.A2CURSES:
872 for b in astyle.A2CURSES:
873 if style.attrs & b:
873 if style.attrs & b:
874 attrs |= astyle.A2CURSES[b]
874 attrs |= astyle.A2CURSES[b]
875 try:
875 try:
876 color = self._colors[style.fg, style.bg]
876 color = self._colors[style.fg, style.bg]
877 except KeyError:
877 except KeyError:
878 curses.init_pair(
878 curses.init_pair(
879 self._maxcolor,
879 self._maxcolor,
880 astyle.COLOR2CURSES[style.fg],
880 astyle.COLOR2CURSES[style.fg],
881 astyle.COLOR2CURSES[style.bg]
881 astyle.COLOR2CURSES[style.bg]
882 )
882 )
883 color = curses.color_pair(self._maxcolor)
883 color = curses.color_pair(self._maxcolor)
884 self._colors[style.fg, style.bg] = color
884 self._colors[style.fg, style.bg] = color
885 self._maxcolor += 1
885 self._maxcolor += 1
886 c = color | attrs
886 c = color | attrs
887 self._styles[style.fg, style.bg, style.attrs] = c
887 self._styles[style.fg, style.bg, style.attrs] = c
888 return c
888 return c
889
889
890 def addstr(self, y, x, begx, endx, text, style):
890 def addstr(self, y, x, begx, endx, text, style):
891 """
891 """
892 A version of ``curses.addstr()`` that can handle ``x`` coordinates
892 A version of ``curses.addstr()`` that can handle ``x`` coordinates
893 that are outside the screen.
893 that are outside the screen.
894 """
894 """
895 text2 = text[max(0, begx-x):max(0, endx-x)]
895 text2 = text[max(0, begx-x):max(0, endx-x)]
896 if text2:
896 if text2:
897 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
897 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
898 return len(text)
898 return len(text)
899
899
900 def addchr(self, y, x, begx, endx, c, l, style):
900 def addchr(self, y, x, begx, endx, c, l, style):
901 x0 = max(x, begx)
901 x0 = max(x, begx)
902 x1 = min(x+l, endx)
902 x1 = min(x+l, endx)
903 if x1>x0:
903 if x1>x0:
904 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
904 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
905 return l
905 return l
906
906
907 def _calcheaderlines(self, levels):
907 def _calcheaderlines(self, levels):
908 # Calculate how many headerlines do we have to display, if we have
908 # Calculate how many headerlines do we have to display, if we have
909 # ``levels`` browser levels
909 # ``levels`` browser levels
910 if levels is None:
910 if levels is None:
911 levels = len(self.levels)
911 levels = len(self.levels)
912 self._headerlines = min(self.maxheaders, levels)
912 self._headerlines = min(self.maxheaders, levels)
913 self._firstheaderline = levels-self._headerlines
913 self._firstheaderline = levels-self._headerlines
914
914
915 def getstylehere(self, style):
915 def getstylehere(self, style):
916 """
916 """
917 Return a style for displaying the original style ``style``
917 Return a style for displaying the original style ``style``
918 in the row the cursor is on.
918 in the row the cursor is on.
919 """
919 """
920 return astyle.Style(style.fg, astyle.COLOR_BLUE, style.attrs | astyle.A_BOLD)
920 return astyle.Style(style.fg, astyle.COLOR_BLUE, style.attrs | astyle.A_BOLD)
921
921
922 def report(self, msg):
922 def report(self, msg):
923 """
923 """
924 Store the message ``msg`` for display below the footer line. This
924 Store the message ``msg`` for display below the footer line. This
925 will be displayed as soon as the screen is redrawn.
925 will be displayed as soon as the screen is redrawn.
926 """
926 """
927 self._report = msg
927 self._report = msg
928
928
929 def enter(self, item, *attrs):
929 def enter(self, item, *attrs):
930 """
930 """
931 Enter the object ``item``. If ``attrs`` is specified, it will be used
931 Enter the object ``item``. If ``attrs`` is specified, it will be used
932 as a fixed list of attributes to display.
932 as a fixed list of attributes to display.
933 """
933 """
934 if self.levels and item is self.levels[-1].input:
934 if self.levels and item is self.levels[-1].input:
935 curses.beep()
935 curses.beep()
936 self.report(CommandError("Recursion on input object"))
936 self.report(CommandError("Recursion on input object"))
937 else:
937 else:
938 oldlevels = len(self.levels)
938 oldlevels = len(self.levels)
939 self._calcheaderlines(oldlevels+1)
939 self._calcheaderlines(oldlevels+1)
940 try:
940 try:
941 level = _BrowserLevel(
941 level = _BrowserLevel(
942 self,
942 self,
943 item,
943 item,
944 self.scrsizey-1-self._headerlines-2,
944 self.scrsizey-1-self._headerlines-2,
945 *attrs
945 *attrs
946 )
946 )
947 except (KeyboardInterrupt, SystemExit):
947 except (KeyboardInterrupt, SystemExit):
948 raise
948 raise
949 except Exception, exc:
949 except Exception, exc:
950 if not self.levels:
950 if not self.levels:
951 raise
951 raise
952 self._calcheaderlines(oldlevels)
952 self._calcheaderlines(oldlevels)
953 curses.beep()
953 curses.beep()
954 self.report(exc)
954 self.report(exc)
955 else:
955 else:
956 self.levels.append(level)
956 self.levels.append(level)
957
957
958 def startkeyboardinput(self, mode):
958 def startkeyboardinput(self, mode):
959 """
959 """
960 Enter mode ``mode``, which requires keyboard input.
960 Enter mode ``mode``, which requires keyboard input.
961 """
961 """
962 self.mode = mode
962 self.mode = mode
963 self.prompts[mode].start()
963 self.prompts[mode].start()
964
964
965 def keylabel(self, keycode):
965 def keylabel(self, keycode):
966 """
966 """
967 Return a pretty name for the ``curses`` key ``keycode`` (used in the
967 Return a pretty name for the ``curses`` key ``keycode`` (used in the
968 help screen and in reports about unassigned keys).
968 help screen and in reports about unassigned keys).
969 """
969 """
970 if keycode <= 0xff:
970 if keycode <= 0xff:
971 specialsnames = {
971 specialsnames = {
972 ord("\n"): "RETURN",
972 ord("\n"): "RETURN",
973 ord(" "): "SPACE",
973 ord(" "): "SPACE",
974 ord("\t"): "TAB",
974 ord("\t"): "TAB",
975 ord("\x7f"): "DELETE",
975 ord("\x7f"): "DELETE",
976 ord("\x08"): "BACKSPACE",
976 ord("\x08"): "BACKSPACE",
977 }
977 }
978 if keycode in specialsnames:
978 if keycode in specialsnames:
979 return specialsnames[keycode]
979 return specialsnames[keycode]
980 elif 0x00 < keycode < 0x20:
980 elif 0x00 < keycode < 0x20:
981 return "CTRL-%s" % chr(keycode + 64)
981 return "CTRL-%s" % chr(keycode + 64)
982 return repr(chr(keycode))
982 return repr(chr(keycode))
983 for name in dir(curses):
983 for name in dir(curses):
984 if name.startswith("KEY_") and getattr(curses, name) == keycode:
984 if name.startswith("KEY_") and getattr(curses, name) == keycode:
985 return name
985 return name
986 return str(keycode)
986 return str(keycode)
987
987
988 def beep(self, force=False):
988 def beep(self, force=False):
989 if force or self._dobeep:
989 if force or self._dobeep:
990 curses.beep()
990 curses.beep()
991 # don't beep again (as long as the same key is pressed)
991 # don't beep again (as long as the same key is pressed)
992 self._dobeep = False
992 self._dobeep = False
993
993
994 def cmd_up(self):
994 def cmd_up(self):
995 """
995 """
996 Move the cursor to the previous row.
996 Move the cursor to the previous row.
997 """
997 """
998 level = self.levels[-1]
998 level = self.levels[-1]
999 self.report("up")
999 self.report("up")
1000 level.moveto(level.curx, level.cury-self.stepy)
1000 level.moveto(level.curx, level.cury-self.stepy)
1001
1001
1002 def cmd_down(self):
1002 def cmd_down(self):
1003 """
1003 """
1004 Move the cursor to the next row.
1004 Move the cursor to the next row.
1005 """
1005 """
1006 level = self.levels[-1]
1006 level = self.levels[-1]
1007 self.report("down")
1007 self.report("down")
1008 level.moveto(level.curx, level.cury+self.stepy)
1008 level.moveto(level.curx, level.cury+self.stepy)
1009
1009
1010 def cmd_pageup(self):
1010 def cmd_pageup(self):
1011 """
1011 """
1012 Move the cursor up one page.
1012 Move the cursor up one page.
1013 """
1013 """
1014 level = self.levels[-1]
1014 level = self.levels[-1]
1015 self.report("page up")
1015 self.report("page up")
1016 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
1016 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
1017
1017
1018 def cmd_pagedown(self):
1018 def cmd_pagedown(self):
1019 """
1019 """
1020 Move the cursor down one page.
1020 Move the cursor down one page.
1021 """
1021 """
1022 level = self.levels[-1]
1022 level = self.levels[-1]
1023 self.report("page down")
1023 self.report("page down")
1024 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
1024 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
1025
1025
1026 def cmd_left(self):
1026 def cmd_left(self):
1027 """
1027 """
1028 Move the cursor left.
1028 Move the cursor left.
1029 """
1029 """
1030 level = self.levels[-1]
1030 level = self.levels[-1]
1031 self.report("left")
1031 self.report("left")
1032 level.moveto(level.curx-self.stepx, level.cury)
1032 level.moveto(level.curx-self.stepx, level.cury)
1033
1033
1034 def cmd_right(self):
1034 def cmd_right(self):
1035 """
1035 """
1036 Move the cursor right.
1036 Move the cursor right.
1037 """
1037 """
1038 level = self.levels[-1]
1038 level = self.levels[-1]
1039 self.report("right")
1039 self.report("right")
1040 level.moveto(level.curx+self.stepx, level.cury)
1040 level.moveto(level.curx+self.stepx, level.cury)
1041
1041
1042 def cmd_home(self):
1042 def cmd_home(self):
1043 """
1043 """
1044 Move the cursor to the first column.
1044 Move the cursor to the first column.
1045 """
1045 """
1046 level = self.levels[-1]
1046 level = self.levels[-1]
1047 self.report("home")
1047 self.report("home")
1048 level.moveto(0, level.cury)
1048 level.moveto(0, level.cury)
1049
1049
1050 def cmd_end(self):
1050 def cmd_end(self):
1051 """
1051 """
1052 Move the cursor to the last column.
1052 Move the cursor to the last column.
1053 """
1053 """
1054 level = self.levels[-1]
1054 level = self.levels[-1]
1055 self.report("end")
1055 self.report("end")
1056 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1056 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1057
1057
1058 def cmd_prevattr(self):
1058 def cmd_prevattr(self):
1059 """
1059 """
1060 Move the cursor one attribute column to the left.
1060 Move the cursor one attribute column to the left.
1061 """
1061 """
1062 level = self.levels[-1]
1062 level = self.levels[-1]
1063 if level.displayattr[0] is None or level.displayattr[0] == 0:
1063 if level.displayattr[0] is None or level.displayattr[0] == 0:
1064 self.beep()
1064 self.beep()
1065 else:
1065 else:
1066 self.report("prevattr")
1066 self.report("prevattr")
1067 pos = 0
1067 pos = 0
1068 for (i, attrname) in enumerate(level.displayattrs):
1068 for (i, attrname) in enumerate(level.displayattrs):
1069 if i == level.displayattr[0]-1:
1069 if i == level.displayattr[0]-1:
1070 break
1070 break
1071 pos += level.colwidths[attrname] + 1
1071 pos += level.colwidths[attrname] + 1
1072 level.moveto(pos, level.cury)
1072 level.moveto(pos, level.cury)
1073
1073
1074 def cmd_nextattr(self):
1074 def cmd_nextattr(self):
1075 """
1075 """
1076 Move the cursor one attribute column to the right.
1076 Move the cursor one attribute column to the right.
1077 """
1077 """
1078 level = self.levels[-1]
1078 level = self.levels[-1]
1079 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
1079 if level.displayattr[0] is None or level.displayattr[0] == len(level.displayattrs)-1:
1080 self.beep()
1080 self.beep()
1081 else:
1081 else:
1082 self.report("nextattr")
1082 self.report("nextattr")
1083 pos = 0
1083 pos = 0
1084 for (i, attrname) in enumerate(level.displayattrs):
1084 for (i, attrname) in enumerate(level.displayattrs):
1085 if i == level.displayattr[0]+1:
1085 if i == level.displayattr[0]+1:
1086 break
1086 break
1087 pos += level.colwidths[attrname] + 1
1087 pos += level.colwidths[attrname] + 1
1088 level.moveto(pos, level.cury)
1088 level.moveto(pos, level.cury)
1089
1089
1090 def cmd_pick(self):
1090 def cmd_pick(self):
1091 """
1091 """
1092 'Pick' the object under the cursor (i.e. the row the cursor is on).
1092 'Pick' the object under the cursor (i.e. the row the cursor is on).
1093 This leaves the browser and returns the picked object to the caller.
1093 This leaves the browser and returns the picked object to the caller.
1094 (In IPython this object will be available as the ``_`` variable.)
1094 (In IPython this object will be available as the ``_`` variable.)
1095 """
1095 """
1096 level = self.levels[-1]
1096 level = self.levels[-1]
1097 self.returnvalue = level.items[level.cury].item
1097 self.returnvalue = level.items[level.cury].item
1098 return True
1098 return True
1099
1099
1100 def cmd_pickattr(self):
1100 def cmd_pickattr(self):
1101 """
1101 """
1102 'Pick' the attribute under the cursor (i.e. the row/column the
1102 'Pick' the attribute under the cursor (i.e. the row/column the
1103 cursor is on).
1103 cursor is on).
1104 """
1104 """
1105 level = self.levels[-1]
1105 level = self.levels[-1]
1106 attr = level.displayattr[1]
1106 attr = level.displayattr[1]
1107 if attr is ipipe.noitem:
1107 if attr is ipipe.noitem:
1108 curses.beep()
1108 curses.beep()
1109 self.report(CommandError("no column under cursor"))
1109 self.report(CommandError("no column under cursor"))
1110 return
1110 return
1111 value = attr.value(level.items[level.cury].item)
1111 value = attr.value(level.items[level.cury].item)
1112 if value is ipipe.noitem:
1112 if value is ipipe.noitem:
1113 curses.beep()
1113 curses.beep()
1114 self.report(AttributeError(attr.name()))
1114 self.report(AttributeError(attr.name()))
1115 else:
1115 else:
1116 self.returnvalue = value
1116 self.returnvalue = value
1117 return True
1117 return True
1118
1118
1119 def cmd_pickallattrs(self):
1119 def cmd_pickallattrs(self):
1120 """
1120 """
1121 Pick' the complete column under the cursor (i.e. the attribute under
1121 Pick' the complete column under the cursor (i.e. the attribute under
1122 the cursor) from all currently fetched objects. These attributes
1122 the cursor) from all currently fetched objects. These attributes
1123 will be returned as a list.
1123 will be returned as a list.
1124 """
1124 """
1125 level = self.levels[-1]
1125 level = self.levels[-1]
1126 attr = level.displayattr[1]
1126 attr = level.displayattr[1]
1127 if attr is ipipe.noitem:
1127 if attr is ipipe.noitem:
1128 curses.beep()
1128 curses.beep()
1129 self.report(CommandError("no column under cursor"))
1129 self.report(CommandError("no column under cursor"))
1130 return
1130 return
1131 result = []
1131 result = []
1132 for cache in level.items:
1132 for cache in level.items:
1133 value = attr.value(cache.item)
1133 value = attr.value(cache.item)
1134 if value is not ipipe.noitem:
1134 if value is not ipipe.noitem:
1135 result.append(value)
1135 result.append(value)
1136 self.returnvalue = result
1136 self.returnvalue = result
1137 return True
1137 return True
1138
1138
1139 def cmd_pickmarked(self):
1139 def cmd_pickmarked(self):
1140 """
1140 """
1141 'Pick' marked objects. Marked objects will be returned as a list.
1141 'Pick' marked objects. Marked objects will be returned as a list.
1142 """
1142 """
1143 level = self.levels[-1]
1143 level = self.levels[-1]
1144 self.returnvalue = [cache.item for cache in level.items if cache.marked]
1144 self.returnvalue = [cache.item for cache in level.items if cache.marked]
1145 return True
1145 return True
1146
1146
1147 def cmd_pickmarkedattr(self):
1147 def cmd_pickmarkedattr(self):
1148 """
1148 """
1149 'Pick' the attribute under the cursor from all marked objects
1149 'Pick' the attribute under the cursor from all marked objects
1150 (This returns a list).
1150 (This returns a list).
1151 """
1151 """
1152
1152
1153 level = self.levels[-1]
1153 level = self.levels[-1]
1154 attr = level.displayattr[1]
1154 attr = level.displayattr[1]
1155 if attr is ipipe.noitem:
1155 if attr is ipipe.noitem:
1156 curses.beep()
1156 curses.beep()
1157 self.report(CommandError("no column under cursor"))
1157 self.report(CommandError("no column under cursor"))
1158 return
1158 return
1159 result = []
1159 result = []
1160 for cache in level.items:
1160 for cache in level.items:
1161 if cache.marked:
1161 if cache.marked:
1162 value = attr.value(cache.item)
1162 value = attr.value(cache.item)
1163 if value is not ipipe.noitem:
1163 if value is not ipipe.noitem:
1164 result.append(value)
1164 result.append(value)
1165 self.returnvalue = result
1165 self.returnvalue = result
1166 return True
1166 return True
1167
1167
1168 def cmd_pickinput(self):
1168 def cmd_pickinput(self):
1169 """
1169 """
1170 Use the object under the cursor (i.e. the row the cursor is on) as
1170 Use the object under the cursor (i.e. the row the cursor is on) as
1171 the next input line. This leaves the browser and puts the picked object
1171 the next input line. This leaves the browser and puts the picked object
1172 in the input.
1172 in the input.
1173 """
1173 """
1174 level = self.levels[-1]
1174 level = self.levels[-1]
1175 value = level.items[level.cury].item
1175 value = level.items[level.cury].item
1176 self.returnvalue = None
1176 self.returnvalue = None
1177 api = ipapi.get()
1177 api = ipapi.get()
1178 api.set_next_input(str(value))
1178 api.set_next_input(str(value))
1179 return True
1179 return True
1180
1180
1181 def cmd_pickinputattr(self):
1181 def cmd_pickinputattr(self):
1182 """
1182 """
1183 Use the attribute under the cursor i.e. the row/column the cursor is on)
1183 Use the attribute under the cursor i.e. the row/column the cursor is on)
1184 as the next input line. This leaves the browser and puts the picked
1184 as the next input line. This leaves the browser and puts the picked
1185 object in the input.
1185 object in the input.
1186 """
1186 """
1187 level = self.levels[-1]
1187 level = self.levels[-1]
1188 attr = level.displayattr[1]
1188 attr = level.displayattr[1]
1189 if attr is ipipe.noitem:
1189 if attr is ipipe.noitem:
1190 curses.beep()
1190 curses.beep()
1191 self.report(CommandError("no column under cursor"))
1191 self.report(CommandError("no column under cursor"))
1192 return
1192 return
1193 value = attr.value(level.items[level.cury].item)
1193 value = attr.value(level.items[level.cury].item)
1194 if value is ipipe.noitem:
1194 if value is ipipe.noitem:
1195 curses.beep()
1195 curses.beep()
1196 self.report(AttributeError(attr.name()))
1196 self.report(AttributeError(attr.name()))
1197 self.returnvalue = None
1197 self.returnvalue = None
1198 api = ipapi.get()
1198 api = ipapi.get()
1199 api.set_next_input(str(value))
1199 api.set_next_input(str(value))
1200 return True
1200 return True
1201
1201
1202 def cmd_markrange(self):
1202 def cmd_markrange(self):
1203 """
1203 """
1204 Mark all objects from the last marked object before the current cursor
1204 Mark all objects from the last marked object before the current cursor
1205 position to the cursor position.
1205 position to the cursor position.
1206 """
1206 """
1207 level = self.levels[-1]
1207 level = self.levels[-1]
1208 self.report("markrange")
1208 self.report("markrange")
1209 start = None
1209 start = None
1210 if level.items:
1210 if level.items:
1211 for i in xrange(level.cury, -1, -1):
1211 for i in xrange(level.cury, -1, -1):
1212 if level.items[i].marked:
1212 if level.items[i].marked:
1213 start = i
1213 start = i
1214 break
1214 break
1215 if start is None:
1215 if start is None:
1216 self.report(CommandError("no mark before cursor"))
1216 self.report(CommandError("no mark before cursor"))
1217 curses.beep()
1217 curses.beep()
1218 else:
1218 else:
1219 for i in xrange(start, level.cury+1):
1219 for i in xrange(start, level.cury+1):
1220 cache = level.items[i]
1220 cache = level.items[i]
1221 if not cache.marked:
1221 if not cache.marked:
1222 cache.marked = True
1222 cache.marked = True
1223 level.marked += 1
1223 level.marked += 1
1224
1224
1225 def cmd_enter(self):
1225 def cmd_enter(self):
1226 """
1226 """
1227 Enter the object under the cursor. (what this mean depends on the object
1227 Enter the object under the cursor. (what this mean depends on the object
1228 itself (i.e. how it implements iteration). This opens a new browser 'level'.
1228 itself (i.e. how it implements iteration). This opens a new browser 'level'.
1229 """
1229 """
1230 level = self.levels[-1]
1230 level = self.levels[-1]
1231 try:
1231 try:
1232 item = level.items[level.cury].item
1232 item = level.items[level.cury].item
1233 except IndexError:
1233 except IndexError:
1234 self.report(CommandError("No object"))
1234 self.report(CommandError("No object"))
1235 curses.beep()
1235 curses.beep()
1236 else:
1236 else:
1237 self.report("entering object...")
1237 self.report("entering object...")
1238 self.enter(item)
1238 self.enter(item)
1239
1239
1240 def cmd_leave(self):
1240 def cmd_leave(self):
1241 """
1241 """
1242 Leave the current browser level and go back to the previous one.
1242 Leave the current browser level and go back to the previous one.
1243 """
1243 """
1244 self.report("leave")
1244 self.report("leave")
1245 if len(self.levels) > 1:
1245 if len(self.levels) > 1:
1246 self._calcheaderlines(len(self.levels)-1)
1246 self._calcheaderlines(len(self.levels)-1)
1247 self.levels.pop(-1)
1247 self.levels.pop(-1)
1248 else:
1248 else:
1249 self.report(CommandError("This is the last level"))
1249 self.report(CommandError("This is the last level"))
1250 curses.beep()
1250 curses.beep()
1251
1251
1252 def cmd_enterattr(self):
1252 def cmd_enterattr(self):
1253 """
1253 """
1254 Enter the attribute under the cursor.
1254 Enter the attribute under the cursor.
1255 """
1255 """
1256 level = self.levels[-1]
1256 level = self.levels[-1]
1257 attr = level.displayattr[1]
1257 attr = level.displayattr[1]
1258 if attr is ipipe.noitem:
1258 if attr is ipipe.noitem:
1259 curses.beep()
1259 curses.beep()
1260 self.report(CommandError("no column under cursor"))
1260 self.report(CommandError("no column under cursor"))
1261 return
1261 return
1262 try:
1262 try:
1263 item = level.items[level.cury].item
1263 item = level.items[level.cury].item
1264 except IndexError:
1264 except IndexError:
1265 self.report(CommandError("No object"))
1265 self.report(CommandError("No object"))
1266 curses.beep()
1266 curses.beep()
1267 else:
1267 else:
1268 value = attr.value(item)
1268 value = attr.value(item)
1269 name = attr.name()
1269 name = attr.name()
1270 if value is ipipe.noitem:
1270 if value is ipipe.noitem:
1271 self.report(AttributeError(name))
1271 self.report(AttributeError(name))
1272 else:
1272 else:
1273 self.report("entering object attribute %s..." % name)
1273 self.report("entering object attribute %s..." % name)
1274 self.enter(value)
1274 self.enter(value)
1275
1275
1276 def cmd_detail(self):
1276 def cmd_detail(self):
1277 """
1277 """
1278 Show a detail view of the object under the cursor. This shows the
1278 Show a detail view of the object under the cursor. This shows the
1279 name, type, doc string and value of the object attributes (and it
1279 name, type, doc string and value of the object attributes (and it
1280 might show more attributes than in the list view, depending on
1280 might show more attributes than in the list view, depending on
1281 the object).
1281 the object).
1282 """
1282 """
1283 level = self.levels[-1]
1283 level = self.levels[-1]
1284 try:
1284 try:
1285 item = level.items[level.cury].item
1285 item = level.items[level.cury].item
1286 except IndexError:
1286 except IndexError:
1287 self.report(CommandError("No object"))
1287 self.report(CommandError("No object"))
1288 curses.beep()
1288 curses.beep()
1289 else:
1289 else:
1290 self.report("entering detail view for object...")
1290 self.report("entering detail view for object...")
1291 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1291 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1292 self.enter(attrs)
1292 self.enter(attrs)
1293
1293
1294 def cmd_detailattr(self):
1294 def cmd_detailattr(self):
1295 """
1295 """
1296 Show a detail view of the attribute under the cursor.
1296 Show a detail view of the attribute under the cursor.
1297 """
1297 """
1298 level = self.levels[-1]
1298 level = self.levels[-1]
1299 attr = level.displayattr[1]
1299 attr = level.displayattr[1]
1300 if attr is ipipe.noitem:
1300 if attr is ipipe.noitem:
1301 curses.beep()
1301 curses.beep()
1302 self.report(CommandError("no attribute"))
1302 self.report(CommandError("no attribute"))
1303 return
1303 return
1304 try:
1304 try:
1305 item = level.items[level.cury].item
1305 item = level.items[level.cury].item
1306 except IndexError:
1306 except IndexError:
1307 self.report(CommandError("No object"))
1307 self.report(CommandError("No object"))
1308 curses.beep()
1308 curses.beep()
1309 else:
1309 else:
1310 try:
1310 try:
1311 item = attr.value(item)
1311 item = attr.value(item)
1312 except (KeyboardInterrupt, SystemExit):
1312 except (KeyboardInterrupt, SystemExit):
1313 raise
1313 raise
1314 except Exception, exc:
1314 except Exception, exc:
1315 self.report(exc)
1315 self.report(exc)
1316 else:
1316 else:
1317 self.report("entering detail view for attribute %s..." % attr.name())
1317 self.report("entering detail view for attribute %s..." % attr.name())
1318 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1318 attrs = [ipipe.AttributeDetail(item, attr) for attr in ipipe.xattrs(item, "detail")]
1319 self.enter(attrs)
1319 self.enter(attrs)
1320
1320
1321 def cmd_tooglemark(self):
1321 def cmd_tooglemark(self):
1322 """
1322 """
1323 Mark/unmark the object under the cursor. Marked objects have a '!'
1323 Mark/unmark the object under the cursor. Marked objects have a '!'
1324 after the row number).
1324 after the row number).
1325 """
1325 """
1326 level = self.levels[-1]
1326 level = self.levels[-1]
1327 self.report("toggle mark")
1327 self.report("toggle mark")
1328 try:
1328 try:
1329 item = level.items[level.cury]
1329 item = level.items[level.cury]
1330 except IndexError: # no items?
1330 except IndexError: # no items?
1331 pass
1331 pass
1332 else:
1332 else:
1333 if item.marked:
1333 if item.marked:
1334 item.marked = False
1334 item.marked = False
1335 level.marked -= 1
1335 level.marked -= 1
1336 else:
1336 else:
1337 item.marked = True
1337 item.marked = True
1338 level.marked += 1
1338 level.marked += 1
1339
1339
1340 def cmd_sortattrasc(self):
1340 def cmd_sortattrasc(self):
1341 """
1341 """
1342 Sort the objects (in ascending order) using the attribute under
1342 Sort the objects (in ascending order) using the attribute under
1343 the cursor as the sort key.
1343 the cursor as the sort key.
1344 """
1344 """
1345 level = self.levels[-1]
1345 level = self.levels[-1]
1346 attr = level.displayattr[1]
1346 attr = level.displayattr[1]
1347 if attr is ipipe.noitem:
1347 if attr is ipipe.noitem:
1348 curses.beep()
1348 curses.beep()
1349 self.report(CommandError("no column under cursor"))
1349 self.report(CommandError("no column under cursor"))
1350 return
1350 return
1351 self.report("sort by %s (ascending)" % attr.name())
1351 self.report("sort by %s (ascending)" % attr.name())
1352 def key(item):
1352 def key(item):
1353 try:
1353 try:
1354 return attr.value(item)
1354 return attr.value(item)
1355 except (KeyboardInterrupt, SystemExit):
1355 except (KeyboardInterrupt, SystemExit):
1356 raise
1356 raise
1357 except Exception:
1357 except Exception:
1358 return None
1358 return None
1359 level.sort(key)
1359 level.sort(key)
1360
1360
1361 def cmd_sortattrdesc(self):
1361 def cmd_sortattrdesc(self):
1362 """
1362 """
1363 Sort the objects (in descending order) using the attribute under
1363 Sort the objects (in descending order) using the attribute under
1364 the cursor as the sort key.
1364 the cursor as the sort key.
1365 """
1365 """
1366 level = self.levels[-1]
1366 level = self.levels[-1]
1367 attr = level.displayattr[1]
1367 attr = level.displayattr[1]
1368 if attr is ipipe.noitem:
1368 if attr is ipipe.noitem:
1369 curses.beep()
1369 curses.beep()
1370 self.report(CommandError("no column under cursor"))
1370 self.report(CommandError("no column under cursor"))
1371 return
1371 return
1372 self.report("sort by %s (descending)" % attr.name())
1372 self.report("sort by %s (descending)" % attr.name())
1373 def key(item):
1373 def key(item):
1374 try:
1374 try:
1375 return attr.value(item)
1375 return attr.value(item)
1376 except (KeyboardInterrupt, SystemExit):
1376 except (KeyboardInterrupt, SystemExit):
1377 raise
1377 raise
1378 except Exception:
1378 except Exception:
1379 return None
1379 return None
1380 level.sort(key, reverse=True)
1380 level.sort(key, reverse=True)
1381
1381
1382 def cmd_hideattr(self):
1382 def cmd_hideattr(self):
1383 """
1383 """
1384 Hide the attribute under the cursor.
1384 Hide the attribute under the cursor.
1385 """
1385 """
1386 level = self.levels[-1]
1386 level = self.levels[-1]
1387 if level.displayattr[0] is None:
1387 if level.displayattr[0] is None:
1388 self.beep()
1388 self.beep()
1389 else:
1389 else:
1390 self.report("hideattr")
1390 self.report("hideattr")
1391 level.hiddenattrs.add(level.displayattr[1])
1391 level.hiddenattrs.add(level.displayattr[1])
1392 level.moveto(level.curx, level.cury, refresh=True)
1392 level.moveto(level.curx, level.cury, refresh=True)
1393
1393
1394 def cmd_unhideattrs(self):
1394 def cmd_unhideattrs(self):
1395 """
1395 """
1396 Make all attributes visible again.
1396 Make all attributes visible again.
1397 """
1397 """
1398 level = self.levels[-1]
1398 level = self.levels[-1]
1399 self.report("unhideattrs")
1399 self.report("unhideattrs")
1400 level.hiddenattrs.clear()
1400 level.hiddenattrs.clear()
1401 level.moveto(level.curx, level.cury, refresh=True)
1401 level.moveto(level.curx, level.cury, refresh=True)
1402
1402
1403 def cmd_goto(self):
1403 def cmd_goto(self):
1404 """
1404 """
1405 Jump to a row. The row number can be entered at the
1405 Jump to a row. The row number can be entered at the
1406 bottom of the screen.
1406 bottom of the screen.
1407 """
1407 """
1408 self.startkeyboardinput("goto")
1408 self.startkeyboardinput("goto")
1409
1409
1410 def cmd_find(self):
1410 def cmd_find(self):
1411 """
1411 """
1412 Search forward for a row. The search condition can be entered at the
1412 Search forward for a row. The search condition can be entered at the
1413 bottom of the screen.
1413 bottom of the screen.
1414 """
1414 """
1415 self.startkeyboardinput("find")
1415 self.startkeyboardinput("find")
1416
1416
1417 def cmd_findbackwards(self):
1417 def cmd_findbackwards(self):
1418 """
1418 """
1419 Search backward for a row. The search condition can be entered at the
1419 Search backward for a row. The search condition can be entered at the
1420 bottom of the screen.
1420 bottom of the screen.
1421 """
1421 """
1422 self.startkeyboardinput("findbackwards")
1422 self.startkeyboardinput("findbackwards")
1423
1423
1424 def cmd_refresh(self):
1424 def cmd_refresh(self):
1425 """
1425 """
1426 Refreshes the display by restarting the iterator.
1426 Refreshes the display by restarting the iterator.
1427 """
1427 """
1428 level = self.levels[-1]
1428 level = self.levels[-1]
1429 self.report("refresh")
1429 self.report("refresh")
1430 level.refresh()
1430 level.refresh()
1431
1431
1432 def cmd_refreshfind(self):
1432 def cmd_refreshfind(self):
1433 """
1433 """
1434 Refreshes the display by restarting the iterator and goes back to the
1434 Refreshes the display by restarting the iterator and goes back to the
1435 same object the cursor was on before restarting (if this object can't be
1435 same object the cursor was on before restarting (if this object can't be
1436 found the cursor jumps back to the first object).
1436 found the cursor jumps back to the first object).
1437 """
1437 """
1438 level = self.levels[-1]
1438 level = self.levels[-1]
1439 self.report("refreshfind")
1439 self.report("refreshfind")
1440 level.refreshfind()
1440 level.refreshfind()
1441
1441
1442 def cmd_help(self):
1442 def cmd_help(self):
1443 """
1443 """
1444 Opens the help screen as a new browser level, describing keyboard
1444 Opens the help screen as a new browser level, describing keyboard
1445 shortcuts.
1445 shortcuts.
1446 """
1446 """
1447 for level in self.levels:
1447 for level in self.levels:
1448 if isinstance(level.input, _BrowserHelp):
1448 if isinstance(level.input, _BrowserHelp):
1449 curses.beep()
1449 curses.beep()
1450 self.report(CommandError("help already active"))
1450 self.report(CommandError("help already active"))
1451 return
1451 return
1452
1452
1453 self.enter(_BrowserHelp(self))
1453 self.enter(_BrowserHelp(self))
1454
1454
1455 def cmd_quit(self):
1455 def cmd_quit(self):
1456 """
1456 """
1457 Quit the browser and return to the IPython prompt.
1457 Quit the browser and return to the IPython prompt.
1458 """
1458 """
1459 self.returnvalue = None
1459 self.returnvalue = None
1460 return True
1460 return True
1461
1461
1462 def sigwinchhandler(self, signal, frame):
1462 def sigwinchhandler(self, signal, frame):
1463 self.resized = True
1463 self.resized = True
1464
1464
1465 def _dodisplay(self, scr):
1465 def _dodisplay(self, scr):
1466 """
1466 """
1467 This method is the workhorse of the browser. It handles screen
1467 This method is the workhorse of the browser. It handles screen
1468 drawing and the keyboard.
1468 drawing and the keyboard.
1469 """
1469 """
1470 self.scr = scr
1470 self.scr = scr
1471 curses.halfdelay(1)
1471 curses.halfdelay(1)
1472 footery = 2
1472 footery = 2
1473
1473
1474 keys = []
1474 keys = []
1475 for cmd in ("quit", "help"):
1475 for cmd in ("quit", "help"):
1476 key = self.keymap.findkey(cmd, None)
1476 key = self.keymap.findkey(cmd, None)
1477 if key is not None:
1477 if key is not None:
1478 keys.append("%s=%s" % (self.keylabel(key), cmd))
1478 keys.append("%s=%s" % (self.keylabel(key), cmd))
1479 helpmsg = " | %s" % " ".join(keys)
1479 helpmsg = " | %s" % " ".join(keys)
1480
1480
1481 scr.clear()
1481 scr.clear()
1482 msg = "Fetching first batch of objects..."
1482 msg = "Fetching first batch of objects..."
1483 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1483 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1484 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1484 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1485 scr.refresh()
1485 scr.refresh()
1486
1486
1487 lastc = -1
1487 lastc = -1
1488
1488
1489 self.levels = []
1489 self.levels = []
1490 # enter the first level
1490 # enter the first level
1491 self.enter(self.input, *self.attrs)
1491 self.enter(self.input, *self.attrs)
1492
1492
1493 self._calcheaderlines(None)
1493 self._calcheaderlines(None)
1494
1494
1495 while True:
1495 while True:
1496 level = self.levels[-1]
1496 level = self.levels[-1]
1497 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1497 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1498 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1498 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1499
1499
1500 # Paint object header
1500 # Paint object header
1501 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1501 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1502 lv = self.levels[i]
1502 lv = self.levels[i]
1503 posx = 0
1503 posx = 0
1504 posy = i-self._firstheaderline
1504 posy = i-self._firstheaderline
1505 endx = self.scrsizex
1505 endx = self.scrsizex
1506 if i: # not the first level
1506 if i: # not the first level
1507 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1507 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1508 if not self.levels[i-1].exhausted:
1508 if not self.levels[i-1].exhausted:
1509 msg += "+"
1509 msg += "+"
1510 msg += ") "
1510 msg += ") "
1511 endx -= len(msg)+1
1511 endx -= len(msg)+1
1512 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1512 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1513 for (style, text) in lv.header:
1513 for (style, text) in lv.header:
1514 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1514 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1515 if posx >= endx:
1515 if posx >= endx:
1516 break
1516 break
1517 if i:
1517 if i:
1518 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1518 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1519 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1519 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1520
1520
1521 if not level.items:
1521 if not level.items:
1522 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1522 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1523 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1523 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1524 scr.clrtobot()
1524 scr.clrtobot()
1525 else:
1525 else:
1526 # Paint column headers
1526 # Paint column headers
1527 scr.move(self._headerlines, 0)
1527 scr.move(self._headerlines, 0)
1528 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1528 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1529 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1529 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1530 begx = level.numbersizex+3
1530 begx = level.numbersizex+3
1531 posx = begx-level.datastartx
1531 posx = begx-level.datastartx
1532 for attr in level.displayattrs:
1532 for attr in level.displayattrs:
1533 attrname = attr.name()
1533 attrname = attr.name()
1534 cwidth = level.colwidths[attr]
1534 cwidth = level.colwidths[attr]
1535 header = attrname.ljust(cwidth)
1535 header = attrname.ljust(cwidth)
1536 if attr is level.displayattr[1]:
1536 if attr is level.displayattr[1]:
1537 style = self.style_colheaderhere
1537 style = self.style_colheaderhere
1538 else:
1538 else:
1539 style = self.style_colheader
1539 style = self.style_colheader
1540 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1540 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1541 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1541 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1542 if posx >= self.scrsizex:
1542 if posx >= self.scrsizex:
1543 break
1543 break
1544 else:
1544 else:
1545 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1545 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1546
1546
1547 # Paint rows
1547 # Paint rows
1548 posy = self._headerlines+1+level.datastarty
1548 posy = self._headerlines+1+level.datastarty
1549 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1549 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1550 cache = level.items[i]
1550 cache = level.items[i]
1551 if i == level.cury:
1551 if i == level.cury:
1552 style = self.style_numberhere
1552 style = self.style_numberhere
1553 else:
1553 else:
1554 style = self.style_number
1554 style = self.style_number
1555
1555
1556 posy = self._headerlines+1+i-level.datastarty
1556 posy = self._headerlines+1+i-level.datastarty
1557 posx = begx-level.datastartx
1557 posx = begx-level.datastartx
1558
1558
1559 scr.move(posy, 0)
1559 scr.move(posy, 0)
1560 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1560 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1561 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1561 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1562
1562
1563 for attrname in level.displayattrs:
1563 for attrname in level.displayattrs:
1564 cwidth = level.colwidths[attrname]
1564 cwidth = level.colwidths[attrname]
1565 try:
1565 try:
1566 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1566 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1567 except KeyError:
1567 except KeyError:
1568 align = 2
1568 align = 2
1569 style = astyle.style_nodata
1569 style = astyle.style_nodata
1570 if i == level.cury:
1570 if i == level.cury:
1571 style = self.getstylehere(style)
1571 style = self.getstylehere(style)
1572 padstyle = self.style_datapad
1572 padstyle = self.style_datapad
1573 sepstyle = self.style_sep
1573 sepstyle = self.style_sep
1574 if i == level.cury:
1574 if i == level.cury:
1575 padstyle = self.getstylehere(padstyle)
1575 padstyle = self.getstylehere(padstyle)
1576 sepstyle = self.getstylehere(sepstyle)
1576 sepstyle = self.getstylehere(sepstyle)
1577 if align == 2:
1577 if align == 2:
1578 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1578 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1579 else:
1579 else:
1580 if align == 1:
1580 if align == 1:
1581 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1581 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1582 elif align == 0:
1582 elif align == 0:
1583 pad1 = (cwidth-length)//2
1583 pad1 = (cwidth-length)//2
1584 pad2 = cwidth-length-len(pad1)
1584 pad2 = cwidth-length-len(pad1)
1585 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1585 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1586 for (style, text) in parts:
1586 for (style, text) in parts:
1587 if i == level.cury:
1587 if i == level.cury:
1588 style = self.getstylehere(style)
1588 style = self.getstylehere(style)
1589 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1589 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1590 if posx >= self.scrsizex:
1590 if posx >= self.scrsizex:
1591 break
1591 break
1592 if align == -1:
1592 if align == -1:
1593 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1593 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1594 elif align == 0:
1594 elif align == 0:
1595 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1595 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1596 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1596 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1597 else:
1597 else:
1598 scr.clrtoeol()
1598 scr.clrtoeol()
1599
1599
1600 # Add blank row headers for the rest of the screen
1600 # Add blank row headers for the rest of the screen
1601 for posy in xrange(posy+1, self.scrsizey-2):
1601 for posy in xrange(posy+1, self.scrsizey-2):
1602 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1602 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1603 scr.clrtoeol()
1603 scr.clrtoeol()
1604
1604
1605 posy = self.scrsizey-footery
1605 posy = self.scrsizey-footery
1606 # Display footer
1606 # Display footer
1607 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1607 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1608
1608
1609 if level.exhausted:
1609 if level.exhausted:
1610 flag = ""
1610 flag = ""
1611 else:
1611 else:
1612 flag = "+"
1612 flag = "+"
1613
1613
1614 endx = self.scrsizex-len(helpmsg)-1
1614 endx = self.scrsizex-len(helpmsg)-1
1615 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1615 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1616
1616
1617 posx = 0
1617 posx = 0
1618 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1618 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1619 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1619 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1620 try:
1620 try:
1621 item = level.items[level.cury].item
1621 item = level.items[level.cury].item
1622 except IndexError: # empty
1622 except IndexError: # empty
1623 pass
1623 pass
1624 else:
1624 else:
1625 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1625 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1626 if not isinstance(nostyle, int):
1626 if not isinstance(nostyle, int):
1627 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1627 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1628 if posx >= endx:
1628 if posx >= endx:
1629 break
1629 break
1630
1630
1631 attrstyle = [(astyle.style_default, "no attribute")]
1631 attrstyle = [(astyle.style_default, "no attribute")]
1632 attr = level.displayattr[1]
1632 attr = level.displayattr[1]
1633 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1633 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1634 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1634 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1635 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1635 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1636 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1636 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1637 try:
1637 try:
1638 value = attr.value(item)
1638 value = attr.value(item)
1639 except (SystemExit, KeyboardInterrupt):
1639 except (SystemExit, KeyboardInterrupt):
1640 raise
1640 raise
1641 except Exception, exc:
1641 except Exception, exc:
1642 value = exc
1642 value = exc
1643 if value is not ipipe.noitem:
1643 if value is not ipipe.noitem:
1644 attrstyle = ipipe.xrepr(value, "footer")
1644 attrstyle = ipipe.xrepr(value, "footer")
1645 for (nostyle, text) in attrstyle:
1645 for (nostyle, text) in attrstyle:
1646 if not isinstance(nostyle, int):
1646 if not isinstance(nostyle, int):
1647 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1647 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1648 if posx >= endx:
1648 if posx >= endx:
1649 break
1649 break
1650
1650
1651 try:
1651 try:
1652 # Display input prompt
1652 # Display input prompt
1653 if self.mode in self.prompts:
1653 if self.mode in self.prompts:
1654 history = self.prompts[self.mode]
1654 history = self.prompts[self.mode]
1655 posx = 0
1655 posx = 0
1656 posy = self.scrsizey-1
1656 posy = self.scrsizey-1
1657 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1657 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1658 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1658 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1659 if history.cury==-1:
1659 if history.cury==-1:
1660 text = "new"
1660 text = "new"
1661 else:
1661 else:
1662 text = str(history.cury+1)
1662 text = str(history.cury+1)
1663 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1663 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1664 if history.history:
1664 if history.history:
1665 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1665 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1666 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1666 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1667 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1667 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1668 inputstartx = posx
1668 inputstartx = posx
1669 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1669 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1670 # Display report
1670 # Display report
1671 else:
1671 else:
1672 if self._report is not None:
1672 if self._report is not None:
1673 if isinstance(self._report, Exception):
1673 if isinstance(self._report, Exception):
1674 style = self.getstyle(astyle.style_error)
1674 style = self.getstyle(astyle.style_error)
1675 if self._report.__class__.__module__ == "exceptions":
1675 if self._report.__class__.__module__ == "exceptions":
1676 msg = "%s: %s" % \
1676 msg = "%s: %s" % \
1677 (self._report.__class__.__name__, self._report)
1677 (self._report.__class__.__name__, self._report)
1678 else:
1678 else:
1679 msg = "%s.%s: %s" % \
1679 msg = "%s.%s: %s" % \
1680 (self._report.__class__.__module__,
1680 (self._report.__class__.__module__,
1681 self._report.__class__.__name__, self._report)
1681 self._report.__class__.__name__, self._report)
1682 else:
1682 else:
1683 style = self.getstyle(self.style_report)
1683 style = self.getstyle(self.style_report)
1684 msg = self._report
1684 msg = self._report
1685 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1685 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1686 self._report = None
1686 self._report = None
1687 else:
1687 else:
1688 scr.move(self.scrsizey-1, 0)
1688 scr.move(self.scrsizey-1, 0)
1689 except curses.error:
1689 except curses.error:
1690 # Protect against errors from writing to the last line
1690 # Protect against errors from writing to the last line
1691 pass
1691 pass
1692 scr.clrtoeol()
1692 scr.clrtoeol()
1693
1693
1694 # Position cursor
1694 # Position cursor
1695 if self.mode in self.prompts:
1695 if self.mode in self.prompts:
1696 history = self.prompts[self.mode]
1696 history = self.prompts[self.mode]
1697 scr.move(self.scrsizey-1, inputstartx+history.curx)
1697 scr.move(self.scrsizey-1, inputstartx+history.curx)
1698 else:
1698 else:
1699 scr.move(
1699 scr.move(
1700 1+self._headerlines+level.cury-level.datastarty,
1700 1+self._headerlines+level.cury-level.datastarty,
1701 level.numbersizex+3+level.curx-level.datastartx
1701 level.numbersizex+3+level.curx-level.datastartx
1702 )
1702 )
1703 scr.refresh()
1703 scr.refresh()
1704
1704
1705 # Check keyboard
1705 # Check keyboard
1706 while True:
1706 while True:
1707 c = scr.getch()
1707 c = scr.getch()
1708 if self.resized:
1708 if self.resized:
1709 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1709 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1710 size = struct.unpack("4H", size)
1710 size = struct.unpack("4H", size)
1711 oldsize = scr.getmaxyx()
1711 oldsize = scr.getmaxyx()
1712 scr.erase()
1712 scr.erase()
1713 curses.resize_term(size[0], size[1])
1713 curses.resize_term(size[0], size[1])
1714 newsize = scr.getmaxyx()
1714 newsize = scr.getmaxyx()
1715 scr.erase()
1715 scr.erase()
1716 for l in self.levels:
1716 for l in self.levels:
1717 l.mainsizey += newsize[0]-oldsize[0]
1717 l.mainsizey += newsize[0]-oldsize[0]
1718 l.moveto(l.curx, l.cury, refresh=True)
1718 l.moveto(l.curx, l.cury, refresh=True)
1719 scr.refresh()
1719 scr.refresh()
1720 self.resized = False
1720 self.resized = False
1721 break # Redisplay
1721 break # Redisplay
1722 if self.mode in self.prompts:
1722 if self.mode in self.prompts:
1723 if self.prompts[self.mode].handlekey(self, c):
1723 if self.prompts[self.mode].handlekey(self, c):
1724 break # Redisplay
1724 break # Redisplay
1725 else:
1725 else:
1726 # if no key is pressed slow down and beep again
1726 # if no key is pressed slow down and beep again
1727 if c == -1:
1727 if c == -1:
1728 self.stepx = 1.
1728 self.stepx = 1.
1729 self.stepy = 1.
1729 self.stepy = 1.
1730 self._dobeep = True
1730 self._dobeep = True
1731 else:
1731 else:
1732 # if a different key was pressed slow down and beep too
1732 # if a different key was pressed slow down and beep too
1733 if c != lastc:
1733 if c != lastc:
1734 lastc = c
1734 lastc = c
1735 self.stepx = 1.
1735 self.stepx = 1.
1736 self.stepy = 1.
1736 self.stepy = 1.
1737 self._dobeep = True
1737 self._dobeep = True
1738 cmdname = self.keymap.get(c, None)
1738 cmdname = self.keymap.get(c, None)
1739 if cmdname is None:
1739 if cmdname is None:
1740 self.report(
1740 self.report(
1741 UnassignedKeyError("Unassigned key %s" %
1741 UnassignedKeyError("Unassigned key %s" %
1742 self.keylabel(c)))
1742 self.keylabel(c)))
1743 else:
1743 else:
1744 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1744 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1745 if cmdfunc is None:
1745 if cmdfunc is None:
1746 self.report(
1746 self.report(
1747 UnknownCommandError("Unknown command %r" %
1747 UnknownCommandError("Unknown command %r" %
1748 (cmdname,)))
1748 (cmdname,)))
1749 elif cmdfunc():
1749 elif cmdfunc():
1750 returnvalue = self.returnvalue
1750 returnvalue = self.returnvalue
1751 self.returnvalue = None
1751 self.returnvalue = None
1752 return returnvalue
1752 return returnvalue
1753 self.stepx = self.nextstepx(self.stepx)
1753 self.stepx = self.nextstepx(self.stepx)
1754 self.stepy = self.nextstepy(self.stepy)
1754 self.stepy = self.nextstepy(self.stepy)
1755 curses.flushinp() # get rid of type ahead
1755 curses.flushinp() # get rid of type ahead
1756 break # Redisplay
1756 break # Redisplay
1757 self.scr = None
1757 self.scr = None
1758
1758
1759 def display(self):
1759 def display(self):
1760 if hasattr(curses, "resize_term"):
1760 if hasattr(curses, "resize_term") and hasattr(signal, 'SIGWINCH'):
1761 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1761 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1762 try:
1762 try:
1763 return curses.wrapper(self._dodisplay)
1763 return curses.wrapper(self._dodisplay)
1764 finally:
1764 finally:
1765 signal.signal(signal.SIGWINCH, oldhandler)
1765 signal.signal(signal.SIGWINCH, oldhandler)
1766 else:
1766 else:
1767 return curses.wrapper(self._dodisplay)
1767 return curses.wrapper(self._dodisplay)
General Comments 0
You need to be logged in to leave comments. Login now