##// END OF EJS Templates
Fixed the help message in the footer (which was displaying "quit" twice).
walter.doerwald -
Show More

The requested changes are too big and content was truncated. Show full diff

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