##// END OF EJS Templates
Add two new commands to ibrowse:...
walter.doerwald -
Show More

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

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