##// END OF EJS Templates
Enhance cmd_refreshfind docstring....
walter.doerwald -
Show More
@@ -1,1717 +1,1718 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,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 = ipipe.xiter(input)
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):
420 def refresh(self):
421 """
421 """
422 Restart iterating the input.
422 Restart iterating the input.
423 """
423 """
424 self.iterator = ipipe.xiter(self.input)
424 self.iterator = ipipe.xiter(self.input)
425 self.items.clear()
425 self.items.clear()
426 self.exhausted = False
426 self.exhausted = False
427 self.moveto(0, 0, refresh=True)
427 self.moveto(0, 0, refresh=True)
428
428
429 def refreshfind(self):
429 def refreshfind(self):
430 """
430 """
431 Restart iterating the input and go back to the same object as before
431 Restart iterating the input and go back to the same object as before
432 (if it can be found in the new iterator).
432 (if it can be found in the new iterator).
433 """
433 """
434 try:
434 try:
435 oldobject = self.items[self.cury].item
435 oldobject = self.items[self.cury].item
436 except IndexError:
436 except IndexError:
437 oldobject = ipipe.noitem
437 oldobject = ipipe.noitem
438 self.iterator = ipipe.xiter(self.input)
438 self.iterator = ipipe.xiter(self.input)
439 self.items.clear()
439 self.items.clear()
440 self.exhausted = False
440 self.exhausted = False
441 while True:
441 while True:
442 self.fetch(len(self.items)+1)
442 self.fetch(len(self.items)+1)
443 if self.exhausted:
443 if self.exhausted:
444 curses.beep()
444 curses.beep()
445 self.moveto(0, 0, refresh=True)
445 self.moveto(self.curx, 0, refresh=True)
446 break
446 break
447 if self.items[-1].item == oldobject:
447 if self.items[-1].item == oldobject:
448 self.moveto(self.curx, len(self.items)-1, refresh=True)
448 self.moveto(self.curx, len(self.items)-1, refresh=True)
449 break
449 break
450
450
451
451
452 class _CommandInput(object):
452 class _CommandInput(object):
453 keymap = Keymap()
453 keymap = Keymap()
454 keymap.register("left", curses.KEY_LEFT)
454 keymap.register("left", curses.KEY_LEFT)
455 keymap.register("right", curses.KEY_RIGHT)
455 keymap.register("right", curses.KEY_RIGHT)
456 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
456 keymap.register("home", curses.KEY_HOME, "\x01") # Ctrl-A
457 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
457 keymap.register("end", curses.KEY_END, "\x05") # Ctrl-E
458 # FIXME: What's happening here?
458 # FIXME: What's happening here?
459 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
459 keymap.register("backspace", curses.KEY_BACKSPACE, "\x08\x7f")
460 keymap.register("delete", curses.KEY_DC)
460 keymap.register("delete", curses.KEY_DC)
461 keymap.register("delend", 0x0b) # Ctrl-K
461 keymap.register("delend", 0x0b) # Ctrl-K
462 keymap.register("execute", "\r\n")
462 keymap.register("execute", "\r\n")
463 keymap.register("up", curses.KEY_UP)
463 keymap.register("up", curses.KEY_UP)
464 keymap.register("down", curses.KEY_DOWN)
464 keymap.register("down", curses.KEY_DOWN)
465 keymap.register("incsearchup", curses.KEY_PPAGE)
465 keymap.register("incsearchup", curses.KEY_PPAGE)
466 keymap.register("incsearchdown", curses.KEY_NPAGE)
466 keymap.register("incsearchdown", curses.KEY_NPAGE)
467 keymap.register("exit", "\x18"), # Ctrl-X
467 keymap.register("exit", "\x18"), # Ctrl-X
468
468
469 def __init__(self, prompt):
469 def __init__(self, prompt):
470 self.prompt = prompt
470 self.prompt = prompt
471 self.history = []
471 self.history = []
472 self.maxhistory = 100
472 self.maxhistory = 100
473 self.input = ""
473 self.input = ""
474 self.curx = 0
474 self.curx = 0
475 self.cury = -1 # blank line
475 self.cury = -1 # blank line
476
476
477 def start(self):
477 def start(self):
478 self.input = ""
478 self.input = ""
479 self.curx = 0
479 self.curx = 0
480 self.cury = -1 # blank line
480 self.cury = -1 # blank line
481
481
482 def handlekey(self, browser, key):
482 def handlekey(self, browser, key):
483 cmdname = self.keymap.get(key, None)
483 cmdname = self.keymap.get(key, None)
484 if cmdname is not None:
484 if cmdname is not None:
485 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
485 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
486 if cmdfunc is not None:
486 if cmdfunc is not None:
487 return cmdfunc(browser)
487 return cmdfunc(browser)
488 curses.beep()
488 curses.beep()
489 elif key != -1:
489 elif key != -1:
490 try:
490 try:
491 char = chr(key)
491 char = chr(key)
492 except ValueError:
492 except ValueError:
493 curses.beep()
493 curses.beep()
494 else:
494 else:
495 return self.handlechar(browser, char)
495 return self.handlechar(browser, char)
496
496
497 def handlechar(self, browser, char):
497 def handlechar(self, browser, char):
498 self.input = self.input[:self.curx] + char + self.input[self.curx:]
498 self.input = self.input[:self.curx] + char + self.input[self.curx:]
499 self.curx += 1
499 self.curx += 1
500 return True
500 return True
501
501
502 def dohistory(self):
502 def dohistory(self):
503 self.history.insert(0, self.input)
503 self.history.insert(0, self.input)
504 del self.history[:-self.maxhistory]
504 del self.history[:-self.maxhistory]
505
505
506 def cmd_backspace(self, browser):
506 def cmd_backspace(self, browser):
507 if self.curx:
507 if self.curx:
508 self.input = self.input[:self.curx-1] + self.input[self.curx:]
508 self.input = self.input[:self.curx-1] + self.input[self.curx:]
509 self.curx -= 1
509 self.curx -= 1
510 return True
510 return True
511 else:
511 else:
512 curses.beep()
512 curses.beep()
513
513
514 def cmd_delete(self, browser):
514 def cmd_delete(self, browser):
515 if self.curx<len(self.input):
515 if self.curx<len(self.input):
516 self.input = self.input[:self.curx] + self.input[self.curx+1:]
516 self.input = self.input[:self.curx] + self.input[self.curx+1:]
517 return True
517 return True
518 else:
518 else:
519 curses.beep()
519 curses.beep()
520
520
521 def cmd_delend(self, browser):
521 def cmd_delend(self, browser):
522 if self.curx<len(self.input):
522 if self.curx<len(self.input):
523 self.input = self.input[:self.curx]
523 self.input = self.input[:self.curx]
524 return True
524 return True
525
525
526 def cmd_left(self, browser):
526 def cmd_left(self, browser):
527 if self.curx:
527 if self.curx:
528 self.curx -= 1
528 self.curx -= 1
529 return True
529 return True
530 else:
530 else:
531 curses.beep()
531 curses.beep()
532
532
533 def cmd_right(self, browser):
533 def cmd_right(self, browser):
534 if self.curx < len(self.input):
534 if self.curx < len(self.input):
535 self.curx += 1
535 self.curx += 1
536 return True
536 return True
537 else:
537 else:
538 curses.beep()
538 curses.beep()
539
539
540 def cmd_home(self, browser):
540 def cmd_home(self, browser):
541 if self.curx:
541 if self.curx:
542 self.curx = 0
542 self.curx = 0
543 return True
543 return True
544 else:
544 else:
545 curses.beep()
545 curses.beep()
546
546
547 def cmd_end(self, browser):
547 def cmd_end(self, browser):
548 if self.curx < len(self.input):
548 if self.curx < len(self.input):
549 self.curx = len(self.input)
549 self.curx = len(self.input)
550 return True
550 return True
551 else:
551 else:
552 curses.beep()
552 curses.beep()
553
553
554 def cmd_up(self, browser):
554 def cmd_up(self, browser):
555 if self.cury < len(self.history)-1:
555 if self.cury < len(self.history)-1:
556 self.cury += 1
556 self.cury += 1
557 self.input = self.history[self.cury]
557 self.input = self.history[self.cury]
558 self.curx = len(self.input)
558 self.curx = len(self.input)
559 return True
559 return True
560 else:
560 else:
561 curses.beep()
561 curses.beep()
562
562
563 def cmd_down(self, browser):
563 def cmd_down(self, browser):
564 if self.cury >= 0:
564 if self.cury >= 0:
565 self.cury -= 1
565 self.cury -= 1
566 if self.cury>=0:
566 if self.cury>=0:
567 self.input = self.history[self.cury]
567 self.input = self.history[self.cury]
568 else:
568 else:
569 self.input = ""
569 self.input = ""
570 self.curx = len(self.input)
570 self.curx = len(self.input)
571 return True
571 return True
572 else:
572 else:
573 curses.beep()
573 curses.beep()
574
574
575 def cmd_incsearchup(self, browser):
575 def cmd_incsearchup(self, browser):
576 prefix = self.input[:self.curx]
576 prefix = self.input[:self.curx]
577 cury = self.cury
577 cury = self.cury
578 while True:
578 while True:
579 cury += 1
579 cury += 1
580 if cury >= len(self.history):
580 if cury >= len(self.history):
581 break
581 break
582 if self.history[cury].startswith(prefix):
582 if self.history[cury].startswith(prefix):
583 self.input = self.history[cury]
583 self.input = self.history[cury]
584 self.cury = cury
584 self.cury = cury
585 return True
585 return True
586 curses.beep()
586 curses.beep()
587
587
588 def cmd_incsearchdown(self, browser):
588 def cmd_incsearchdown(self, browser):
589 prefix = self.input[:self.curx]
589 prefix = self.input[:self.curx]
590 cury = self.cury
590 cury = self.cury
591 while True:
591 while True:
592 cury -= 1
592 cury -= 1
593 if cury <= 0:
593 if cury <= 0:
594 break
594 break
595 if self.history[cury].startswith(prefix):
595 if self.history[cury].startswith(prefix):
596 self.input = self.history[self.cury]
596 self.input = self.history[self.cury]
597 self.cury = cury
597 self.cury = cury
598 return True
598 return True
599 curses.beep()
599 curses.beep()
600
600
601 def cmd_exit(self, browser):
601 def cmd_exit(self, browser):
602 browser.mode = "default"
602 browser.mode = "default"
603 return True
603 return True
604
604
605 def cmd_execute(self, browser):
605 def cmd_execute(self, browser):
606 raise NotImplementedError
606 raise NotImplementedError
607
607
608
608
609 class _CommandGoto(_CommandInput):
609 class _CommandGoto(_CommandInput):
610 def __init__(self):
610 def __init__(self):
611 _CommandInput.__init__(self, "goto object #")
611 _CommandInput.__init__(self, "goto object #")
612
612
613 def handlechar(self, browser, char):
613 def handlechar(self, browser, char):
614 # Only accept digits
614 # Only accept digits
615 if not "0" <= char <= "9":
615 if not "0" <= char <= "9":
616 curses.beep()
616 curses.beep()
617 else:
617 else:
618 return _CommandInput.handlechar(self, browser, char)
618 return _CommandInput.handlechar(self, browser, char)
619
619
620 def cmd_execute(self, browser):
620 def cmd_execute(self, browser):
621 level = browser.levels[-1]
621 level = browser.levels[-1]
622 if self.input:
622 if self.input:
623 self.dohistory()
623 self.dohistory()
624 level.moveto(level.curx, int(self.input))
624 level.moveto(level.curx, int(self.input))
625 browser.mode = "default"
625 browser.mode = "default"
626 return True
626 return True
627
627
628
628
629 class _CommandFind(_CommandInput):
629 class _CommandFind(_CommandInput):
630 def __init__(self):
630 def __init__(self):
631 _CommandInput.__init__(self, "find expression")
631 _CommandInput.__init__(self, "find expression")
632
632
633 def cmd_execute(self, browser):
633 def cmd_execute(self, browser):
634 level = browser.levels[-1]
634 level = browser.levels[-1]
635 if self.input:
635 if self.input:
636 self.dohistory()
636 self.dohistory()
637 while True:
637 while True:
638 cury = level.cury
638 cury = level.cury
639 level.moveto(level.curx, cury+1)
639 level.moveto(level.curx, cury+1)
640 if cury == level.cury:
640 if cury == level.cury:
641 curses.beep()
641 curses.beep()
642 break # hit end
642 break # hit end
643 item = level.items[level.cury].item
643 item = level.items[level.cury].item
644 try:
644 try:
645 globals = ipipe.getglobals(None)
645 globals = ipipe.getglobals(None)
646 if eval(self.input, globals, ipipe.AttrNamespace(item)):
646 if eval(self.input, globals, ipipe.AttrNamespace(item)):
647 break # found something
647 break # found something
648 except (KeyboardInterrupt, SystemExit):
648 except (KeyboardInterrupt, SystemExit):
649 raise
649 raise
650 except Exception, exc:
650 except Exception, exc:
651 browser.report(exc)
651 browser.report(exc)
652 curses.beep()
652 curses.beep()
653 break # break on error
653 break # break on error
654 browser.mode = "default"
654 browser.mode = "default"
655 return True
655 return True
656
656
657
657
658 class _CommandFindBackwards(_CommandInput):
658 class _CommandFindBackwards(_CommandInput):
659 def __init__(self):
659 def __init__(self):
660 _CommandInput.__init__(self, "find backwards expression")
660 _CommandInput.__init__(self, "find backwards expression")
661
661
662 def cmd_execute(self, browser):
662 def cmd_execute(self, browser):
663 level = browser.levels[-1]
663 level = browser.levels[-1]
664 if self.input:
664 if self.input:
665 self.dohistory()
665 self.dohistory()
666 while level.cury:
666 while level.cury:
667 level.moveto(level.curx, level.cury-1)
667 level.moveto(level.curx, level.cury-1)
668 item = level.items[level.cury].item
668 item = level.items[level.cury].item
669 try:
669 try:
670 globals = ipipe.getglobals(None)
670 globals = ipipe.getglobals(None)
671 if eval(self.input, globals, ipipe.AttrNamespace(item)):
671 if eval(self.input, globals, ipipe.AttrNamespace(item)):
672 break # found something
672 break # found something
673 except (KeyboardInterrupt, SystemExit):
673 except (KeyboardInterrupt, SystemExit):
674 raise
674 raise
675 except Exception, exc:
675 except Exception, exc:
676 browser.report(exc)
676 browser.report(exc)
677 curses.beep()
677 curses.beep()
678 break # break on error
678 break # break on error
679 else:
679 else:
680 curses.beep()
680 curses.beep()
681 browser.mode = "default"
681 browser.mode = "default"
682 return True
682 return True
683
683
684
684
685 class ibrowse(ipipe.Display):
685 class ibrowse(ipipe.Display):
686 # Show this many lines from the previous screen when paging horizontally
686 # Show this many lines from the previous screen when paging horizontally
687 pageoverlapx = 1
687 pageoverlapx = 1
688
688
689 # Show this many lines from the previous screen when paging vertically
689 # Show this many lines from the previous screen when paging vertically
690 pageoverlapy = 1
690 pageoverlapy = 1
691
691
692 # 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
693 # away from the left or right screen edge
693 # away from the left or right screen edge
694 scrollborderx = 10
694 scrollborderx = 10
695
695
696 # 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
697 # away from the top or bottom screen edge
697 # away from the top or bottom screen edge
698 scrollbordery = 5
698 scrollbordery = 5
699
699
700 # Accelerate by this factor when scrolling horizontally
700 # Accelerate by this factor when scrolling horizontally
701 acceleratex = 1.05
701 acceleratex = 1.05
702
702
703 # Accelerate by this factor when scrolling vertically
703 # Accelerate by this factor when scrolling vertically
704 acceleratey = 1.05
704 acceleratey = 1.05
705
705
706 # The maximum horizontal scroll speed
706 # The maximum horizontal scroll speed
707 # (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)
708 maxspeedx = 0.5
708 maxspeedx = 0.5
709
709
710 # The maximum vertical scroll speed
710 # The maximum vertical scroll speed
711 # (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)
712 maxspeedy = 0.5
712 maxspeedy = 0.5
713
713
714 # The maximum number of header lines for browser level
714 # The maximum number of header lines for browser level
715 # if the nesting is deeper, only the innermost levels are displayed
715 # if the nesting is deeper, only the innermost levels are displayed
716 maxheaders = 5
716 maxheaders = 5
717
717
718 # The approximate maximum length of a column entry
718 # The approximate maximum length of a column entry
719 maxattrlength = 200
719 maxattrlength = 200
720
720
721 # Styles for various parts of the GUI
721 # Styles for various parts of the GUI
722 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
722 style_objheadertext = astyle.Style.fromstr("white:black:bold|reverse")
723 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
723 style_objheadernumber = astyle.Style.fromstr("white:blue:bold|reverse")
724 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
724 style_objheaderobject = astyle.Style.fromstr("white:black:reverse")
725 style_colheader = astyle.Style.fromstr("blue:white:reverse")
725 style_colheader = astyle.Style.fromstr("blue:white:reverse")
726 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
726 style_colheaderhere = astyle.Style.fromstr("green:black:bold|reverse")
727 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
727 style_colheadersep = astyle.Style.fromstr("blue:black:reverse")
728 style_number = astyle.Style.fromstr("blue:white:reverse")
728 style_number = astyle.Style.fromstr("blue:white:reverse")
729 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
729 style_numberhere = astyle.Style.fromstr("green:black:bold|reverse")
730 style_sep = astyle.Style.fromstr("blue:black")
730 style_sep = astyle.Style.fromstr("blue:black")
731 style_data = astyle.Style.fromstr("white:black")
731 style_data = astyle.Style.fromstr("white:black")
732 style_datapad = astyle.Style.fromstr("blue:black:bold")
732 style_datapad = astyle.Style.fromstr("blue:black:bold")
733 style_footer = astyle.Style.fromstr("black:white")
733 style_footer = astyle.Style.fromstr("black:white")
734 style_report = astyle.Style.fromstr("white:black")
734 style_report = astyle.Style.fromstr("white:black")
735
735
736 # Column separator in header
736 # Column separator in header
737 headersepchar = "|"
737 headersepchar = "|"
738
738
739 # Character for padding data cell entries
739 # Character for padding data cell entries
740 datapadchar = "."
740 datapadchar = "."
741
741
742 # Column separator in data area
742 # Column separator in data area
743 datasepchar = "|"
743 datasepchar = "|"
744
744
745 # 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)
746 nodatachar = "-"
746 nodatachar = "-"
747
747
748 # Prompts for modes that require keyboard input
748 # Prompts for modes that require keyboard input
749 prompts = {
749 prompts = {
750 "goto": _CommandGoto(),
750 "goto": _CommandGoto(),
751 "find": _CommandFind(),
751 "find": _CommandFind(),
752 "findbackwards": _CommandFindBackwards()
752 "findbackwards": _CommandFindBackwards()
753 }
753 }
754
754
755 # Maps curses key codes to "function" names
755 # Maps curses key codes to "function" names
756 keymap = Keymap()
756 keymap = Keymap()
757 keymap.register("quit", "q")
757 keymap.register("quit", "q")
758 keymap.register("up", curses.KEY_UP)
758 keymap.register("up", curses.KEY_UP)
759 keymap.register("down", curses.KEY_DOWN)
759 keymap.register("down", curses.KEY_DOWN)
760 keymap.register("pageup", curses.KEY_PPAGE)
760 keymap.register("pageup", curses.KEY_PPAGE)
761 keymap.register("pagedown", curses.KEY_NPAGE)
761 keymap.register("pagedown", curses.KEY_NPAGE)
762 keymap.register("left", curses.KEY_LEFT)
762 keymap.register("left", curses.KEY_LEFT)
763 keymap.register("right", curses.KEY_RIGHT)
763 keymap.register("right", curses.KEY_RIGHT)
764 keymap.register("home", curses.KEY_HOME, "\x01")
764 keymap.register("home", curses.KEY_HOME, "\x01")
765 keymap.register("end", curses.KEY_END, "\x05")
765 keymap.register("end", curses.KEY_END, "\x05")
766 keymap.register("prevattr", "<\x1b")
766 keymap.register("prevattr", "<\x1b")
767 keymap.register("nextattr", ">\t")
767 keymap.register("nextattr", ">\t")
768 keymap.register("pick", "p")
768 keymap.register("pick", "p")
769 keymap.register("pickattr", "P")
769 keymap.register("pickattr", "P")
770 keymap.register("pickallattrs", "C")
770 keymap.register("pickallattrs", "C")
771 keymap.register("pickmarked", "m")
771 keymap.register("pickmarked", "m")
772 keymap.register("pickmarkedattr", "M")
772 keymap.register("pickmarkedattr", "M")
773 keymap.register("hideattr", "h")
773 keymap.register("hideattr", "h")
774 keymap.register("unhideattrs", "H")
774 keymap.register("unhideattrs", "H")
775 keymap.register("help", "?")
775 keymap.register("help", "?")
776 keymap.register("enter", "\r\n")
776 keymap.register("enter", "\r\n")
777 keymap.register("enterattr", "E")
777 keymap.register("enterattr", "E")
778 # FIXME: What's happening here?
778 # FIXME: What's happening here?
779 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
779 keymap.register("leave", curses.KEY_BACKSPACE, "x\x08\x7f")
780 keymap.register("detail", "d")
780 keymap.register("detail", "d")
781 keymap.register("detailattr", "D")
781 keymap.register("detailattr", "D")
782 keymap.register("tooglemark", " ")
782 keymap.register("tooglemark", " ")
783 keymap.register("markrange", "%")
783 keymap.register("markrange", "%")
784 keymap.register("sortattrasc", "v")
784 keymap.register("sortattrasc", "v")
785 keymap.register("sortattrdesc", "V")
785 keymap.register("sortattrdesc", "V")
786 keymap.register("goto", "g")
786 keymap.register("goto", "g")
787 keymap.register("find", "f")
787 keymap.register("find", "f")
788 keymap.register("findbackwards", "b")
788 keymap.register("findbackwards", "b")
789 keymap.register("refresh", "r")
789 keymap.register("refresh", "r")
790 keymap.register("refreshfind", "R")
790 keymap.register("refreshfind", "R")
791
791
792 def __init__(self, *attrs):
792 def __init__(self, *attrs):
793 """
793 """
794 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
795 of attributes that will be displayed in the browser, otherwise
795 of attributes that will be displayed in the browser, otherwise
796 these will be determined by the objects on screen.
796 these will be determined by the objects on screen.
797 """
797 """
798 self.attrs = attrs
798 self.attrs = attrs
799
799
800 # Stack of browser levels
800 # Stack of browser levels
801 self.levels = []
801 self.levels = []
802 # how many colums to scroll (Changes when accelerating)
802 # how many colums to scroll (Changes when accelerating)
803 self.stepx = 1.
803 self.stepx = 1.
804
804
805 # how many rows to scroll (Changes when accelerating)
805 # how many rows to scroll (Changes when accelerating)
806 self.stepy = 1.
806 self.stepy = 1.
807
807
808 # 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``
809 # 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
810 # multiple beeps).
810 # multiple beeps).
811 self._dobeep = True
811 self._dobeep = True
812
812
813 # Cache for registered ``curses`` colors and styles.
813 # Cache for registered ``curses`` colors and styles.
814 self._styles = {}
814 self._styles = {}
815 self._colors = {}
815 self._colors = {}
816 self._maxcolor = 1
816 self._maxcolor = 1
817
817
818 # 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
819 # we have, but with an upper bound)
819 # we have, but with an upper bound)
820 self._headerlines = 1
820 self._headerlines = 1
821
821
822 # Index of first header line
822 # Index of first header line
823 self._firstheaderline = 0
823 self._firstheaderline = 0
824
824
825 # curses window
825 # curses window
826 self.scr = None
826 self.scr = None
827 # report in the footer line (error, executed command etc.)
827 # report in the footer line (error, executed command etc.)
828 self._report = None
828 self._report = None
829
829
830 # value to be returned to the caller (set by commands)
830 # value to be returned to the caller (set by commands)
831 self.returnvalue = None
831 self.returnvalue = None
832
832
833 # The mode the browser is in
833 # The mode the browser is in
834 # e.g. normal browsing or entering an argument for a command
834 # e.g. normal browsing or entering an argument for a command
835 self.mode = "default"
835 self.mode = "default"
836
836
837 # set by the SIGWINCH signal handler
837 # set by the SIGWINCH signal handler
838 self.resized = False
838 self.resized = False
839
839
840 def nextstepx(self, step):
840 def nextstepx(self, step):
841 """
841 """
842 Accelerate horizontally.
842 Accelerate horizontally.
843 """
843 """
844 return max(1., min(step*self.acceleratex,
844 return max(1., min(step*self.acceleratex,
845 self.maxspeedx*self.levels[-1].mainsizex))
845 self.maxspeedx*self.levels[-1].mainsizex))
846
846
847 def nextstepy(self, step):
847 def nextstepy(self, step):
848 """
848 """
849 Accelerate vertically.
849 Accelerate vertically.
850 """
850 """
851 return max(1., min(step*self.acceleratey,
851 return max(1., min(step*self.acceleratey,
852 self.maxspeedy*self.levels[-1].mainsizey))
852 self.maxspeedy*self.levels[-1].mainsizey))
853
853
854 def getstyle(self, style):
854 def getstyle(self, style):
855 """
855 """
856 Register the ``style`` with ``curses`` or get it from the cache,
856 Register the ``style`` with ``curses`` or get it from the cache,
857 if it has been registered before.
857 if it has been registered before.
858 """
858 """
859 try:
859 try:
860 return self._styles[style.fg, style.bg, style.attrs]
860 return self._styles[style.fg, style.bg, style.attrs]
861 except KeyError:
861 except KeyError:
862 attrs = 0
862 attrs = 0
863 for b in astyle.A2CURSES:
863 for b in astyle.A2CURSES:
864 if style.attrs & b:
864 if style.attrs & b:
865 attrs |= astyle.A2CURSES[b]
865 attrs |= astyle.A2CURSES[b]
866 try:
866 try:
867 color = self._colors[style.fg, style.bg]
867 color = self._colors[style.fg, style.bg]
868 except KeyError:
868 except KeyError:
869 curses.init_pair(
869 curses.init_pair(
870 self._maxcolor,
870 self._maxcolor,
871 astyle.COLOR2CURSES[style.fg],
871 astyle.COLOR2CURSES[style.fg],
872 astyle.COLOR2CURSES[style.bg]
872 astyle.COLOR2CURSES[style.bg]
873 )
873 )
874 color = curses.color_pair(self._maxcolor)
874 color = curses.color_pair(self._maxcolor)
875 self._colors[style.fg, style.bg] = color
875 self._colors[style.fg, style.bg] = color
876 self._maxcolor += 1
876 self._maxcolor += 1
877 c = color | attrs
877 c = color | attrs
878 self._styles[style.fg, style.bg, style.attrs] = c
878 self._styles[style.fg, style.bg, style.attrs] = c
879 return c
879 return c
880
880
881 def addstr(self, y, x, begx, endx, text, style):
881 def addstr(self, y, x, begx, endx, text, style):
882 """
882 """
883 A version of ``curses.addstr()`` that can handle ``x`` coordinates
883 A version of ``curses.addstr()`` that can handle ``x`` coordinates
884 that are outside the screen.
884 that are outside the screen.
885 """
885 """
886 text2 = text[max(0, begx-x):max(0, endx-x)]
886 text2 = text[max(0, begx-x):max(0, endx-x)]
887 if text2:
887 if text2:
888 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
888 self.scr.addstr(y, max(x, begx), text2, self.getstyle(style))
889 return len(text)
889 return len(text)
890
890
891 def addchr(self, y, x, begx, endx, c, l, style):
891 def addchr(self, y, x, begx, endx, c, l, style):
892 x0 = max(x, begx)
892 x0 = max(x, begx)
893 x1 = min(x+l, endx)
893 x1 = min(x+l, endx)
894 if x1>x0:
894 if x1>x0:
895 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
895 self.scr.addstr(y, x0, c*(x1-x0), self.getstyle(style))
896 return l
896 return l
897
897
898 def _calcheaderlines(self, levels):
898 def _calcheaderlines(self, levels):
899 # 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
900 # ``levels`` browser levels
900 # ``levels`` browser levels
901 if levels is None:
901 if levels is None:
902 levels = len(self.levels)
902 levels = len(self.levels)
903 self._headerlines = min(self.maxheaders, levels)
903 self._headerlines = min(self.maxheaders, levels)
904 self._firstheaderline = levels-self._headerlines
904 self._firstheaderline = levels-self._headerlines
905
905
906 def getstylehere(self, style):
906 def getstylehere(self, style):
907 """
907 """
908 Return a style for displaying the original style ``style``
908 Return a style for displaying the original style ``style``
909 in the row the cursor is on.
909 in the row the cursor is on.
910 """
910 """
911 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)
912
912
913 def report(self, msg):
913 def report(self, msg):
914 """
914 """
915 Store the message ``msg`` for display below the footer line. This
915 Store the message ``msg`` for display below the footer line. This
916 will be displayed as soon as the screen is redrawn.
916 will be displayed as soon as the screen is redrawn.
917 """
917 """
918 self._report = msg
918 self._report = msg
919
919
920 def enter(self, item, *attrs):
920 def enter(self, item, *attrs):
921 """
921 """
922 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
923 as a fixed list of attributes to display.
923 as a fixed list of attributes to display.
924 """
924 """
925 oldlevels = len(self.levels)
925 oldlevels = len(self.levels)
926 self._calcheaderlines(oldlevels+1)
926 self._calcheaderlines(oldlevels+1)
927 try:
927 try:
928 level = _BrowserLevel(
928 level = _BrowserLevel(
929 self,
929 self,
930 item,
930 item,
931 self.scrsizey-1-self._headerlines-2,
931 self.scrsizey-1-self._headerlines-2,
932 *attrs
932 *attrs
933 )
933 )
934 except (KeyboardInterrupt, SystemExit):
934 except (KeyboardInterrupt, SystemExit):
935 raise
935 raise
936 except Exception, exc:
936 except Exception, exc:
937 self._calcheaderlines(oldlevels)
937 self._calcheaderlines(oldlevels)
938 curses.beep()
938 curses.beep()
939 self.report(exc)
939 self.report(exc)
940 else:
940 else:
941 self.levels.append(level)
941 self.levels.append(level)
942
942
943 def startkeyboardinput(self, mode):
943 def startkeyboardinput(self, mode):
944 """
944 """
945 Enter mode ``mode``, which requires keyboard input.
945 Enter mode ``mode``, which requires keyboard input.
946 """
946 """
947 self.mode = mode
947 self.mode = mode
948 self.prompts[mode].start()
948 self.prompts[mode].start()
949
949
950 def keylabel(self, keycode):
950 def keylabel(self, keycode):
951 """
951 """
952 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
953 help screen and in reports about unassigned keys).
953 help screen and in reports about unassigned keys).
954 """
954 """
955 if keycode <= 0xff:
955 if keycode <= 0xff:
956 specialsnames = {
956 specialsnames = {
957 ord("\n"): "RETURN",
957 ord("\n"): "RETURN",
958 ord(" "): "SPACE",
958 ord(" "): "SPACE",
959 ord("\t"): "TAB",
959 ord("\t"): "TAB",
960 ord("\x7f"): "DELETE",
960 ord("\x7f"): "DELETE",
961 ord("\x08"): "BACKSPACE",
961 ord("\x08"): "BACKSPACE",
962 }
962 }
963 if keycode in specialsnames:
963 if keycode in specialsnames:
964 return specialsnames[keycode]
964 return specialsnames[keycode]
965 elif 0x00 < keycode < 0x20:
965 elif 0x00 < keycode < 0x20:
966 return "CTRL-%s" % chr(keycode + 64)
966 return "CTRL-%s" % chr(keycode + 64)
967 return repr(chr(keycode))
967 return repr(chr(keycode))
968 for name in dir(curses):
968 for name in dir(curses):
969 if name.startswith("KEY_") and getattr(curses, name) == keycode:
969 if name.startswith("KEY_") and getattr(curses, name) == keycode:
970 return name
970 return name
971 return str(keycode)
971 return str(keycode)
972
972
973 def beep(self, force=False):
973 def beep(self, force=False):
974 if force or self._dobeep:
974 if force or self._dobeep:
975 curses.beep()
975 curses.beep()
976 # 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)
977 self._dobeep = False
977 self._dobeep = False
978
978
979 def cmd_up(self):
979 def cmd_up(self):
980 """
980 """
981 Move the cursor to the previous row.
981 Move the cursor to the previous row.
982 """
982 """
983 level = self.levels[-1]
983 level = self.levels[-1]
984 self.report("up")
984 self.report("up")
985 level.moveto(level.curx, level.cury-self.stepy)
985 level.moveto(level.curx, level.cury-self.stepy)
986
986
987 def cmd_down(self):
987 def cmd_down(self):
988 """
988 """
989 Move the cursor to the next row.
989 Move the cursor to the next row.
990 """
990 """
991 level = self.levels[-1]
991 level = self.levels[-1]
992 self.report("down")
992 self.report("down")
993 level.moveto(level.curx, level.cury+self.stepy)
993 level.moveto(level.curx, level.cury+self.stepy)
994
994
995 def cmd_pageup(self):
995 def cmd_pageup(self):
996 """
996 """
997 Move the cursor up one page.
997 Move the cursor up one page.
998 """
998 """
999 level = self.levels[-1]
999 level = self.levels[-1]
1000 self.report("page up")
1000 self.report("page up")
1001 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
1001 level.moveto(level.curx, level.cury-level.mainsizey+self.pageoverlapy)
1002
1002
1003 def cmd_pagedown(self):
1003 def cmd_pagedown(self):
1004 """
1004 """
1005 Move the cursor down one page.
1005 Move the cursor down one page.
1006 """
1006 """
1007 level = self.levels[-1]
1007 level = self.levels[-1]
1008 self.report("page down")
1008 self.report("page down")
1009 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
1009 level.moveto(level.curx, level.cury+level.mainsizey-self.pageoverlapy)
1010
1010
1011 def cmd_left(self):
1011 def cmd_left(self):
1012 """
1012 """
1013 Move the cursor left.
1013 Move the cursor left.
1014 """
1014 """
1015 level = self.levels[-1]
1015 level = self.levels[-1]
1016 self.report("left")
1016 self.report("left")
1017 level.moveto(level.curx-self.stepx, level.cury)
1017 level.moveto(level.curx-self.stepx, level.cury)
1018
1018
1019 def cmd_right(self):
1019 def cmd_right(self):
1020 """
1020 """
1021 Move the cursor right.
1021 Move the cursor right.
1022 """
1022 """
1023 level = self.levels[-1]
1023 level = self.levels[-1]
1024 self.report("right")
1024 self.report("right")
1025 level.moveto(level.curx+self.stepx, level.cury)
1025 level.moveto(level.curx+self.stepx, level.cury)
1026
1026
1027 def cmd_home(self):
1027 def cmd_home(self):
1028 """
1028 """
1029 Move the cursor to the first column.
1029 Move the cursor to the first column.
1030 """
1030 """
1031 level = self.levels[-1]
1031 level = self.levels[-1]
1032 self.report("home")
1032 self.report("home")
1033 level.moveto(0, level.cury)
1033 level.moveto(0, level.cury)
1034
1034
1035 def cmd_end(self):
1035 def cmd_end(self):
1036 """
1036 """
1037 Move the cursor to the last column.
1037 Move the cursor to the last column.
1038 """
1038 """
1039 level = self.levels[-1]
1039 level = self.levels[-1]
1040 self.report("end")
1040 self.report("end")
1041 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1041 level.moveto(level.datasizex+level.mainsizey-self.pageoverlapx, level.cury)
1042
1042
1043 def cmd_prevattr(self):
1043 def cmd_prevattr(self):
1044 """
1044 """
1045 Move the cursor one attribute column to the left.
1045 Move the cursor one attribute column to the left.
1046 """
1046 """
1047 level = self.levels[-1]
1047 level = self.levels[-1]
1048 if level.displayattr[0] is None or level.displayattr[0] == 0:
1048 if level.displayattr[0] is None or level.displayattr[0] == 0:
1049 self.beep()
1049 self.beep()
1050 else:
1050 else:
1051 self.report("prevattr")
1051 self.report("prevattr")
1052 pos = 0
1052 pos = 0
1053 for (i, attrname) in enumerate(level.displayattrs):
1053 for (i, attrname) in enumerate(level.displayattrs):
1054 if i == level.displayattr[0]-1:
1054 if i == level.displayattr[0]-1:
1055 break
1055 break
1056 pos += level.colwidths[attrname] + 1
1056 pos += level.colwidths[attrname] + 1
1057 level.moveto(pos, level.cury)
1057 level.moveto(pos, level.cury)
1058
1058
1059 def cmd_nextattr(self):
1059 def cmd_nextattr(self):
1060 """
1060 """
1061 Move the cursor one attribute column to the right.
1061 Move the cursor one attribute column to the right.
1062 """
1062 """
1063 level = self.levels[-1]
1063 level = self.levels[-1]
1064 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:
1065 self.beep()
1065 self.beep()
1066 else:
1066 else:
1067 self.report("nextattr")
1067 self.report("nextattr")
1068 pos = 0
1068 pos = 0
1069 for (i, attrname) in enumerate(level.displayattrs):
1069 for (i, attrname) in enumerate(level.displayattrs):
1070 if i == level.displayattr[0]+1:
1070 if i == level.displayattr[0]+1:
1071 break
1071 break
1072 pos += level.colwidths[attrname] + 1
1072 pos += level.colwidths[attrname] + 1
1073 level.moveto(pos, level.cury)
1073 level.moveto(pos, level.cury)
1074
1074
1075 def cmd_pick(self):
1075 def cmd_pick(self):
1076 """
1076 """
1077 '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).
1078 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.
1079 (In IPython this object will be available as the ``_`` variable.)
1079 (In IPython this object will be available as the ``_`` variable.)
1080 """
1080 """
1081 level = self.levels[-1]
1081 level = self.levels[-1]
1082 self.returnvalue = level.items[level.cury].item
1082 self.returnvalue = level.items[level.cury].item
1083 return True
1083 return True
1084
1084
1085 def cmd_pickattr(self):
1085 def cmd_pickattr(self):
1086 """
1086 """
1087 '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
1088 cursor is on).
1088 cursor is on).
1089 """
1089 """
1090 level = self.levels[-1]
1090 level = self.levels[-1]
1091 attr = level.displayattr[1]
1091 attr = level.displayattr[1]
1092 if attr is ipipe.noitem:
1092 if attr is ipipe.noitem:
1093 curses.beep()
1093 curses.beep()
1094 self.report(CommandError("no column under cursor"))
1094 self.report(CommandError("no column under cursor"))
1095 return
1095 return
1096 value = attr.value(level.items[level.cury].item)
1096 value = attr.value(level.items[level.cury].item)
1097 if value is ipipe.noitem:
1097 if value is ipipe.noitem:
1098 curses.beep()
1098 curses.beep()
1099 self.report(AttributeError(attr.name()))
1099 self.report(AttributeError(attr.name()))
1100 else:
1100 else:
1101 self.returnvalue = value
1101 self.returnvalue = value
1102 return True
1102 return True
1103
1103
1104 def cmd_pickallattrs(self):
1104 def cmd_pickallattrs(self):
1105 """
1105 """
1106 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
1107 the cursor) from all currently fetched objects. These attributes
1107 the cursor) from all currently fetched objects. These attributes
1108 will be returned as a list.
1108 will be returned as a list.
1109 """
1109 """
1110 level = self.levels[-1]
1110 level = self.levels[-1]
1111 attr = level.displayattr[1]
1111 attr = level.displayattr[1]
1112 if attr is ipipe.noitem:
1112 if attr is ipipe.noitem:
1113 curses.beep()
1113 curses.beep()
1114 self.report(CommandError("no column under cursor"))
1114 self.report(CommandError("no column under cursor"))
1115 return
1115 return
1116 result = []
1116 result = []
1117 for cache in level.items:
1117 for cache in level.items:
1118 value = attr.value(cache.item)
1118 value = attr.value(cache.item)
1119 if value is not ipipe.noitem:
1119 if value is not ipipe.noitem:
1120 result.append(value)
1120 result.append(value)
1121 self.returnvalue = result
1121 self.returnvalue = result
1122 return True
1122 return True
1123
1123
1124 def cmd_pickmarked(self):
1124 def cmd_pickmarked(self):
1125 """
1125 """
1126 'Pick' marked objects. Marked objects will be returned as a list.
1126 'Pick' marked objects. Marked objects will be returned as a list.
1127 """
1127 """
1128 level = self.levels[-1]
1128 level = self.levels[-1]
1129 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]
1130 return True
1130 return True
1131
1131
1132 def cmd_pickmarkedattr(self):
1132 def cmd_pickmarkedattr(self):
1133 """
1133 """
1134 'Pick' the attribute under the cursor from all marked objects
1134 'Pick' the attribute under the cursor from all marked objects
1135 (This returns a list).
1135 (This returns a list).
1136 """
1136 """
1137
1137
1138 level = self.levels[-1]
1138 level = self.levels[-1]
1139 attr = level.displayattr[1]
1139 attr = level.displayattr[1]
1140 if attr is ipipe.noitem:
1140 if attr is ipipe.noitem:
1141 curses.beep()
1141 curses.beep()
1142 self.report(CommandError("no column under cursor"))
1142 self.report(CommandError("no column under cursor"))
1143 return
1143 return
1144 result = []
1144 result = []
1145 for cache in level.items:
1145 for cache in level.items:
1146 if cache.marked:
1146 if cache.marked:
1147 value = attr.value(cache.item)
1147 value = attr.value(cache.item)
1148 if value is not ipipe.noitem:
1148 if value is not ipipe.noitem:
1149 result.append(value)
1149 result.append(value)
1150 self.returnvalue = result
1150 self.returnvalue = result
1151 return True
1151 return True
1152
1152
1153 def cmd_markrange(self):
1153 def cmd_markrange(self):
1154 """
1154 """
1155 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
1156 position to the cursor position.
1156 position to the cursor position.
1157 """
1157 """
1158 level = self.levels[-1]
1158 level = self.levels[-1]
1159 self.report("markrange")
1159 self.report("markrange")
1160 start = None
1160 start = None
1161 if level.items:
1161 if level.items:
1162 for i in xrange(level.cury, -1, -1):
1162 for i in xrange(level.cury, -1, -1):
1163 if level.items[i].marked:
1163 if level.items[i].marked:
1164 start = i
1164 start = i
1165 break
1165 break
1166 if start is None:
1166 if start is None:
1167 self.report(CommandError("no mark before cursor"))
1167 self.report(CommandError("no mark before cursor"))
1168 curses.beep()
1168 curses.beep()
1169 else:
1169 else:
1170 for i in xrange(start, level.cury+1):
1170 for i in xrange(start, level.cury+1):
1171 cache = level.items[i]
1171 cache = level.items[i]
1172 if not cache.marked:
1172 if not cache.marked:
1173 cache.marked = True
1173 cache.marked = True
1174 level.marked += 1
1174 level.marked += 1
1175
1175
1176 def cmd_enter(self):
1176 def cmd_enter(self):
1177 """
1177 """
1178 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
1179 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'.
1180 """
1180 """
1181 level = self.levels[-1]
1181 level = self.levels[-1]
1182 try:
1182 try:
1183 item = level.items[level.cury].item
1183 item = level.items[level.cury].item
1184 except IndexError:
1184 except IndexError:
1185 self.report(CommandError("No object"))
1185 self.report(CommandError("No object"))
1186 curses.beep()
1186 curses.beep()
1187 else:
1187 else:
1188 self.report("entering object...")
1188 self.report("entering object...")
1189 self.enter(item)
1189 self.enter(item)
1190
1190
1191 def cmd_leave(self):
1191 def cmd_leave(self):
1192 """
1192 """
1193 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.
1194 """
1194 """
1195 self.report("leave")
1195 self.report("leave")
1196 if len(self.levels) > 1:
1196 if len(self.levels) > 1:
1197 self._calcheaderlines(len(self.levels)-1)
1197 self._calcheaderlines(len(self.levels)-1)
1198 self.levels.pop(-1)
1198 self.levels.pop(-1)
1199 else:
1199 else:
1200 self.report(CommandError("This is the last level"))
1200 self.report(CommandError("This is the last level"))
1201 curses.beep()
1201 curses.beep()
1202
1202
1203 def cmd_enterattr(self):
1203 def cmd_enterattr(self):
1204 """
1204 """
1205 Enter the attribute under the cursor.
1205 Enter the attribute under the cursor.
1206 """
1206 """
1207 level = self.levels[-1]
1207 level = self.levels[-1]
1208 attr = level.displayattr[1]
1208 attr = level.displayattr[1]
1209 if attr is ipipe.noitem:
1209 if attr is ipipe.noitem:
1210 curses.beep()
1210 curses.beep()
1211 self.report(CommandError("no column under cursor"))
1211 self.report(CommandError("no column under cursor"))
1212 return
1212 return
1213 try:
1213 try:
1214 item = level.items[level.cury].item
1214 item = level.items[level.cury].item
1215 except IndexError:
1215 except IndexError:
1216 self.report(CommandError("No object"))
1216 self.report(CommandError("No object"))
1217 curses.beep()
1217 curses.beep()
1218 else:
1218 else:
1219 value = attr.value(item)
1219 value = attr.value(item)
1220 name = attr.name()
1220 name = attr.name()
1221 if value is ipipe.noitem:
1221 if value is ipipe.noitem:
1222 self.report(AttributeError(name))
1222 self.report(AttributeError(name))
1223 else:
1223 else:
1224 self.report("entering object attribute %s..." % name)
1224 self.report("entering object attribute %s..." % name)
1225 self.enter(value)
1225 self.enter(value)
1226
1226
1227 def cmd_detail(self):
1227 def cmd_detail(self):
1228 """
1228 """
1229 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
1230 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
1231 might show more attributes than in the list view, depending on
1231 might show more attributes than in the list view, depending on
1232 the object).
1232 the object).
1233 """
1233 """
1234 level = self.levels[-1]
1234 level = self.levels[-1]
1235 try:
1235 try:
1236 item = level.items[level.cury].item
1236 item = level.items[level.cury].item
1237 except IndexError:
1237 except IndexError:
1238 self.report(CommandError("No object"))
1238 self.report(CommandError("No object"))
1239 curses.beep()
1239 curses.beep()
1240 else:
1240 else:
1241 self.report("entering detail view for object...")
1241 self.report("entering detail view for object...")
1242 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")]
1243 self.enter(attrs)
1243 self.enter(attrs)
1244
1244
1245 def cmd_detailattr(self):
1245 def cmd_detailattr(self):
1246 """
1246 """
1247 Show a detail view of the attribute under the cursor.
1247 Show a detail view of the attribute under the cursor.
1248 """
1248 """
1249 level = self.levels[-1]
1249 level = self.levels[-1]
1250 attr = level.displayattr[1]
1250 attr = level.displayattr[1]
1251 if attr is ipipe.noitem:
1251 if attr is ipipe.noitem:
1252 curses.beep()
1252 curses.beep()
1253 self.report(CommandError("no attribute"))
1253 self.report(CommandError("no attribute"))
1254 return
1254 return
1255 try:
1255 try:
1256 item = level.items[level.cury].item
1256 item = level.items[level.cury].item
1257 except IndexError:
1257 except IndexError:
1258 self.report(CommandError("No object"))
1258 self.report(CommandError("No object"))
1259 curses.beep()
1259 curses.beep()
1260 else:
1260 else:
1261 try:
1261 try:
1262 item = attr.value(item)
1262 item = attr.value(item)
1263 except (KeyboardInterrupt, SystemExit):
1263 except (KeyboardInterrupt, SystemExit):
1264 raise
1264 raise
1265 except Exception, exc:
1265 except Exception, exc:
1266 self.report(exc)
1266 self.report(exc)
1267 else:
1267 else:
1268 self.report("entering detail view for attribute %s..." % attr.name())
1268 self.report("entering detail view for attribute %s..." % attr.name())
1269 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")]
1270 self.enter(attrs)
1270 self.enter(attrs)
1271
1271
1272 def cmd_tooglemark(self):
1272 def cmd_tooglemark(self):
1273 """
1273 """
1274 Mark/unmark the object under the cursor. Marked objects have a '!'
1274 Mark/unmark the object under the cursor. Marked objects have a '!'
1275 after the row number).
1275 after the row number).
1276 """
1276 """
1277 level = self.levels[-1]
1277 level = self.levels[-1]
1278 self.report("toggle mark")
1278 self.report("toggle mark")
1279 try:
1279 try:
1280 item = level.items[level.cury]
1280 item = level.items[level.cury]
1281 except IndexError: # no items?
1281 except IndexError: # no items?
1282 pass
1282 pass
1283 else:
1283 else:
1284 if item.marked:
1284 if item.marked:
1285 item.marked = False
1285 item.marked = False
1286 level.marked -= 1
1286 level.marked -= 1
1287 else:
1287 else:
1288 item.marked = True
1288 item.marked = True
1289 level.marked += 1
1289 level.marked += 1
1290
1290
1291 def cmd_sortattrasc(self):
1291 def cmd_sortattrasc(self):
1292 """
1292 """
1293 Sort the objects (in ascending order) using the attribute under
1293 Sort the objects (in ascending order) using the attribute under
1294 the cursor as the sort key.
1294 the cursor as the sort key.
1295 """
1295 """
1296 level = self.levels[-1]
1296 level = self.levels[-1]
1297 attr = level.displayattr[1]
1297 attr = level.displayattr[1]
1298 if attr is ipipe.noitem:
1298 if attr is ipipe.noitem:
1299 curses.beep()
1299 curses.beep()
1300 self.report(CommandError("no column under cursor"))
1300 self.report(CommandError("no column under cursor"))
1301 return
1301 return
1302 self.report("sort by %s (ascending)" % attr.name())
1302 self.report("sort by %s (ascending)" % attr.name())
1303 def key(item):
1303 def key(item):
1304 try:
1304 try:
1305 return attr.value(item)
1305 return attr.value(item)
1306 except (KeyboardInterrupt, SystemExit):
1306 except (KeyboardInterrupt, SystemExit):
1307 raise
1307 raise
1308 except Exception:
1308 except Exception:
1309 return None
1309 return None
1310 level.sort(key)
1310 level.sort(key)
1311
1311
1312 def cmd_sortattrdesc(self):
1312 def cmd_sortattrdesc(self):
1313 """
1313 """
1314 Sort the objects (in descending order) using the attribute under
1314 Sort the objects (in descending order) using the attribute under
1315 the cursor as the sort key.
1315 the cursor as the sort key.
1316 """
1316 """
1317 level = self.levels[-1]
1317 level = self.levels[-1]
1318 attr = level.displayattr[1]
1318 attr = level.displayattr[1]
1319 if attr is ipipe.noitem:
1319 if attr is ipipe.noitem:
1320 curses.beep()
1320 curses.beep()
1321 self.report(CommandError("no column under cursor"))
1321 self.report(CommandError("no column under cursor"))
1322 return
1322 return
1323 self.report("sort by %s (descending)" % attr.name())
1323 self.report("sort by %s (descending)" % attr.name())
1324 def key(item):
1324 def key(item):
1325 try:
1325 try:
1326 return attr.value(item)
1326 return attr.value(item)
1327 except (KeyboardInterrupt, SystemExit):
1327 except (KeyboardInterrupt, SystemExit):
1328 raise
1328 raise
1329 except Exception:
1329 except Exception:
1330 return None
1330 return None
1331 level.sort(key, reverse=True)
1331 level.sort(key, reverse=True)
1332
1332
1333 def cmd_hideattr(self):
1333 def cmd_hideattr(self):
1334 """
1334 """
1335 Hide the attribute under the cursor.
1335 Hide the attribute under the cursor.
1336 """
1336 """
1337 level = self.levels[-1]
1337 level = self.levels[-1]
1338 if level.displayattr[0] is None:
1338 if level.displayattr[0] is None:
1339 self.beep()
1339 self.beep()
1340 else:
1340 else:
1341 self.report("hideattr")
1341 self.report("hideattr")
1342 level.hiddenattrs.add(level.displayattr[1])
1342 level.hiddenattrs.add(level.displayattr[1])
1343 level.moveto(level.curx, level.cury, refresh=True)
1343 level.moveto(level.curx, level.cury, refresh=True)
1344
1344
1345 def cmd_unhideattrs(self):
1345 def cmd_unhideattrs(self):
1346 """
1346 """
1347 Make all attributes visible again.
1347 Make all attributes visible again.
1348 """
1348 """
1349 level = self.levels[-1]
1349 level = self.levels[-1]
1350 self.report("unhideattrs")
1350 self.report("unhideattrs")
1351 level.hiddenattrs.clear()
1351 level.hiddenattrs.clear()
1352 level.moveto(level.curx, level.cury, refresh=True)
1352 level.moveto(level.curx, level.cury, refresh=True)
1353
1353
1354 def cmd_goto(self):
1354 def cmd_goto(self):
1355 """
1355 """
1356 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
1357 bottom of the screen.
1357 bottom of the screen.
1358 """
1358 """
1359 self.startkeyboardinput("goto")
1359 self.startkeyboardinput("goto")
1360
1360
1361 def cmd_find(self):
1361 def cmd_find(self):
1362 """
1362 """
1363 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
1364 bottom of the screen.
1364 bottom of the screen.
1365 """
1365 """
1366 self.startkeyboardinput("find")
1366 self.startkeyboardinput("find")
1367
1367
1368 def cmd_findbackwards(self):
1368 def cmd_findbackwards(self):
1369 """
1369 """
1370 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
1371 bottom of the screen.
1371 bottom of the screen.
1372 """
1372 """
1373 self.startkeyboardinput("findbackwards")
1373 self.startkeyboardinput("findbackwards")
1374
1374
1375 def cmd_refresh(self):
1375 def cmd_refresh(self):
1376 """
1376 """
1377 Refreshes the display by restarting the iterator.
1377 Refreshes the display by restarting the iterator.
1378 """
1378 """
1379 level = self.levels[-1]
1379 level = self.levels[-1]
1380 self.report("refresh")
1380 self.report("refresh")
1381 level.refresh()
1381 level.refresh()
1382
1382
1383 def cmd_refreshfind(self):
1383 def cmd_refreshfind(self):
1384 """
1384 """
1385 Refreshes the display by restarting the iterator and goes back to the
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).
1386 same object the cursor was on before restarting (if this object can't be
1387 found the cursor jumps back to the first object).
1387 """
1388 """
1388 level = self.levels[-1]
1389 level = self.levels[-1]
1389 self.report("refreshfind")
1390 self.report("refreshfind")
1390 level.refreshfind()
1391 level.refreshfind()
1391
1392
1392 def cmd_help(self):
1393 def cmd_help(self):
1393 """
1394 """
1394 Opens the help screen as a new browser level, describing keyboard
1395 Opens the help screen as a new browser level, describing keyboard
1395 shortcuts.
1396 shortcuts.
1396 """
1397 """
1397 for level in self.levels:
1398 for level in self.levels:
1398 if isinstance(level.input, _BrowserHelp):
1399 if isinstance(level.input, _BrowserHelp):
1399 curses.beep()
1400 curses.beep()
1400 self.report(CommandError("help already active"))
1401 self.report(CommandError("help already active"))
1401 return
1402 return
1402
1403
1403 self.enter(_BrowserHelp(self))
1404 self.enter(_BrowserHelp(self))
1404
1405
1405 def cmd_quit(self):
1406 def cmd_quit(self):
1406 """
1407 """
1407 Quit the browser and return to the IPython prompt.
1408 Quit the browser and return to the IPython prompt.
1408 """
1409 """
1409 self.returnvalue = None
1410 self.returnvalue = None
1410 return True
1411 return True
1411
1412
1412 def sigwinchhandler(self, signal, frame):
1413 def sigwinchhandler(self, signal, frame):
1413 self.resized = True
1414 self.resized = True
1414
1415
1415 def _dodisplay(self, scr):
1416 def _dodisplay(self, scr):
1416 """
1417 """
1417 This method is the workhorse of the browser. It handles screen
1418 This method is the workhorse of the browser. It handles screen
1418 drawing and the keyboard.
1419 drawing and the keyboard.
1419 """
1420 """
1420 self.scr = scr
1421 self.scr = scr
1421 curses.halfdelay(1)
1422 curses.halfdelay(1)
1422 footery = 2
1423 footery = 2
1423
1424
1424 keys = []
1425 keys = []
1425 for cmd in ("quit", "help"):
1426 for cmd in ("quit", "help"):
1426 key = self.keymap.findkey(cmd, None)
1427 key = self.keymap.findkey(cmd, None)
1427 if key is not None:
1428 if key is not None:
1428 keys.append("%s=%s" % (self.keylabel(key), cmd))
1429 keys.append("%s=%s" % (self.keylabel(key), cmd))
1429 helpmsg = " | %s" % " ".join(keys)
1430 helpmsg = " | %s" % " ".join(keys)
1430
1431
1431 scr.clear()
1432 scr.clear()
1432 msg = "Fetching first batch of objects..."
1433 msg = "Fetching first batch of objects..."
1433 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1434 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1434 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1435 scr.addstr(self.scrsizey//2, (self.scrsizex-len(msg))//2, msg)
1435 scr.refresh()
1436 scr.refresh()
1436
1437
1437 lastc = -1
1438 lastc = -1
1438
1439
1439 self.levels = []
1440 self.levels = []
1440 # enter the first level
1441 # enter the first level
1441 self.enter(self.input, *self.attrs)
1442 self.enter(self.input, *self.attrs)
1442
1443
1443 self._calcheaderlines(None)
1444 self._calcheaderlines(None)
1444
1445
1445 while True:
1446 while True:
1446 level = self.levels[-1]
1447 level = self.levels[-1]
1447 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1448 (self.scrsizey, self.scrsizex) = scr.getmaxyx()
1448 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1449 level.mainsizey = self.scrsizey-1-self._headerlines-footery
1449
1450
1450 # Paint object header
1451 # Paint object header
1451 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1452 for i in xrange(self._firstheaderline, self._firstheaderline+self._headerlines):
1452 lv = self.levels[i]
1453 lv = self.levels[i]
1453 posx = 0
1454 posx = 0
1454 posy = i-self._firstheaderline
1455 posy = i-self._firstheaderline
1455 endx = self.scrsizex
1456 endx = self.scrsizex
1456 if i: # not the first level
1457 if i: # not the first level
1457 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1458 msg = " (%d/%d" % (self.levels[i-1].cury, len(self.levels[i-1].items))
1458 if not self.levels[i-1].exhausted:
1459 if not self.levels[i-1].exhausted:
1459 msg += "+"
1460 msg += "+"
1460 msg += ") "
1461 msg += ") "
1461 endx -= len(msg)+1
1462 endx -= len(msg)+1
1462 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1463 posx += self.addstr(posy, posx, 0, endx, " ibrowse #%d: " % i, self.style_objheadertext)
1463 for (style, text) in lv.header:
1464 for (style, text) in lv.header:
1464 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1465 posx += self.addstr(posy, posx, 0, endx, text, self.style_objheaderobject)
1465 if posx >= endx:
1466 if posx >= endx:
1466 break
1467 break
1467 if i:
1468 if i:
1468 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1469 posx += self.addstr(posy, posx, 0, self.scrsizex, msg, self.style_objheadernumber)
1469 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1470 posx += self.addchr(posy, posx, 0, self.scrsizex, " ", self.scrsizex-posx, self.style_objheadernumber)
1470
1471
1471 if not level.items:
1472 if not level.items:
1472 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1473 self.addchr(self._headerlines, 0, 0, self.scrsizex, " ", self.scrsizex, self.style_colheader)
1473 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1474 self.addstr(self._headerlines+1, 0, 0, self.scrsizex, " <empty>", astyle.style_error)
1474 scr.clrtobot()
1475 scr.clrtobot()
1475 else:
1476 else:
1476 # Paint column headers
1477 # Paint column headers
1477 scr.move(self._headerlines, 0)
1478 scr.move(self._headerlines, 0)
1478 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1479 scr.addstr(" %*s " % (level.numbersizex, "#"), self.getstyle(self.style_colheader))
1479 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1480 scr.addstr(self.headersepchar, self.getstyle(self.style_colheadersep))
1480 begx = level.numbersizex+3
1481 begx = level.numbersizex+3
1481 posx = begx-level.datastartx
1482 posx = begx-level.datastartx
1482 for attr in level.displayattrs:
1483 for attr in level.displayattrs:
1483 attrname = attr.name()
1484 attrname = attr.name()
1484 cwidth = level.colwidths[attr]
1485 cwidth = level.colwidths[attr]
1485 header = attrname.ljust(cwidth)
1486 header = attrname.ljust(cwidth)
1486 if attr is level.displayattr[1]:
1487 if attr is level.displayattr[1]:
1487 style = self.style_colheaderhere
1488 style = self.style_colheaderhere
1488 else:
1489 else:
1489 style = self.style_colheader
1490 style = self.style_colheader
1490 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1491 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, header, style)
1491 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1492 posx += self.addstr(self._headerlines, posx, begx, self.scrsizex, self.headersepchar, self.style_colheadersep)
1492 if posx >= self.scrsizex:
1493 if posx >= self.scrsizex:
1493 break
1494 break
1494 else:
1495 else:
1495 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1496 scr.addstr(" "*(self.scrsizex-posx), self.getstyle(self.style_colheader))
1496
1497
1497 # Paint rows
1498 # Paint rows
1498 posy = self._headerlines+1+level.datastarty
1499 posy = self._headerlines+1+level.datastarty
1499 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1500 for i in xrange(level.datastarty, min(level.datastarty+level.mainsizey, len(level.items))):
1500 cache = level.items[i]
1501 cache = level.items[i]
1501 if i == level.cury:
1502 if i == level.cury:
1502 style = self.style_numberhere
1503 style = self.style_numberhere
1503 else:
1504 else:
1504 style = self.style_number
1505 style = self.style_number
1505
1506
1506 posy = self._headerlines+1+i-level.datastarty
1507 posy = self._headerlines+1+i-level.datastarty
1507 posx = begx-level.datastartx
1508 posx = begx-level.datastartx
1508
1509
1509 scr.move(posy, 0)
1510 scr.move(posy, 0)
1510 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1511 scr.addstr(" %*d%s" % (level.numbersizex, i, " !"[cache.marked]), self.getstyle(style))
1511 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1512 scr.addstr(self.headersepchar, self.getstyle(self.style_sep))
1512
1513
1513 for attrname in level.displayattrs:
1514 for attrname in level.displayattrs:
1514 cwidth = level.colwidths[attrname]
1515 cwidth = level.colwidths[attrname]
1515 try:
1516 try:
1516 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1517 (align, length, parts) = level.displayrows[i-level.datastarty][attrname]
1517 except KeyError:
1518 except KeyError:
1518 align = 2
1519 align = 2
1519 style = astyle.style_nodata
1520 style = astyle.style_nodata
1520 if i == level.cury:
1521 if i == level.cury:
1521 style = self.getstylehere(style)
1522 style = self.getstylehere(style)
1522 padstyle = self.style_datapad
1523 padstyle = self.style_datapad
1523 sepstyle = self.style_sep
1524 sepstyle = self.style_sep
1524 if i == level.cury:
1525 if i == level.cury:
1525 padstyle = self.getstylehere(padstyle)
1526 padstyle = self.getstylehere(padstyle)
1526 sepstyle = self.getstylehere(sepstyle)
1527 sepstyle = self.getstylehere(sepstyle)
1527 if align == 2:
1528 if align == 2:
1528 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1529 posx += self.addchr(posy, posx, begx, self.scrsizex, self.nodatachar, cwidth, style)
1529 else:
1530 else:
1530 if align == 1:
1531 if align == 1:
1531 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1532 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1532 elif align == 0:
1533 elif align == 0:
1533 pad1 = (cwidth-length)//2
1534 pad1 = (cwidth-length)//2
1534 pad2 = cwidth-length-len(pad1)
1535 pad2 = cwidth-length-len(pad1)
1535 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1536 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad1, padstyle)
1536 for (style, text) in parts:
1537 for (style, text) in parts:
1537 if i == level.cury:
1538 if i == level.cury:
1538 style = self.getstylehere(style)
1539 style = self.getstylehere(style)
1539 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1540 posx += self.addstr(posy, posx, begx, self.scrsizex, text, style)
1540 if posx >= self.scrsizex:
1541 if posx >= self.scrsizex:
1541 break
1542 break
1542 if align == -1:
1543 if align == -1:
1543 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1544 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, cwidth-length, padstyle)
1544 elif align == 0:
1545 elif align == 0:
1545 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1546 posx += self.addchr(posy, posx, begx, self.scrsizex, self.datapadchar, pad2, padstyle)
1546 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1547 posx += self.addstr(posy, posx, begx, self.scrsizex, self.datasepchar, sepstyle)
1547 else:
1548 else:
1548 scr.clrtoeol()
1549 scr.clrtoeol()
1549
1550
1550 # Add blank row headers for the rest of the screen
1551 # Add blank row headers for the rest of the screen
1551 for posy in xrange(posy+1, self.scrsizey-2):
1552 for posy in xrange(posy+1, self.scrsizey-2):
1552 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1553 scr.addstr(posy, 0, " " * (level.numbersizex+2), self.getstyle(self.style_colheader))
1553 scr.clrtoeol()
1554 scr.clrtoeol()
1554
1555
1555 posy = self.scrsizey-footery
1556 posy = self.scrsizey-footery
1556 # Display footer
1557 # Display footer
1557 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1558 scr.addstr(posy, 0, " "*self.scrsizex, self.getstyle(self.style_footer))
1558
1559
1559 if level.exhausted:
1560 if level.exhausted:
1560 flag = ""
1561 flag = ""
1561 else:
1562 else:
1562 flag = "+"
1563 flag = "+"
1563
1564
1564 endx = self.scrsizex-len(helpmsg)-1
1565 endx = self.scrsizex-len(helpmsg)-1
1565 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1566 scr.addstr(posy, endx, helpmsg, self.getstyle(self.style_footer))
1566
1567
1567 posx = 0
1568 posx = 0
1568 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1569 msg = " %d%s objects (%d marked): " % (len(level.items), flag, level.marked)
1569 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1570 posx += self.addstr(posy, posx, 0, endx, msg, self.style_footer)
1570 try:
1571 try:
1571 item = level.items[level.cury].item
1572 item = level.items[level.cury].item
1572 except IndexError: # empty
1573 except IndexError: # empty
1573 pass
1574 pass
1574 else:
1575 else:
1575 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1576 for (nostyle, text) in ipipe.xrepr(item, "footer"):
1576 if not isinstance(nostyle, int):
1577 if not isinstance(nostyle, int):
1577 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1578 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1578 if posx >= endx:
1579 if posx >= endx:
1579 break
1580 break
1580
1581
1581 attrstyle = [(astyle.style_default, "no attribute")]
1582 attrstyle = [(astyle.style_default, "no attribute")]
1582 attr = level.displayattr[1]
1583 attr = level.displayattr[1]
1583 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1584 if attr is not ipipe.noitem and not isinstance(attr, ipipe.SelfDescriptor):
1584 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1585 posx += self.addstr(posy, posx, 0, endx, " | ", self.style_footer)
1585 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1586 posx += self.addstr(posy, posx, 0, endx, attr.name(), self.style_footer)
1586 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1587 posx += self.addstr(posy, posx, 0, endx, ": ", self.style_footer)
1587 try:
1588 try:
1588 value = attr.value(item)
1589 value = attr.value(item)
1589 except (SystemExit, KeyboardInterrupt):
1590 except (SystemExit, KeyboardInterrupt):
1590 raise
1591 raise
1591 except Exception, exc:
1592 except Exception, exc:
1592 value = exc
1593 value = exc
1593 if value is not ipipe.noitem:
1594 if value is not ipipe.noitem:
1594 attrstyle = ipipe.xrepr(value, "footer")
1595 attrstyle = ipipe.xrepr(value, "footer")
1595 for (nostyle, text) in attrstyle:
1596 for (nostyle, text) in attrstyle:
1596 if not isinstance(nostyle, int):
1597 if not isinstance(nostyle, int):
1597 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1598 posx += self.addstr(posy, posx, 0, endx, text, self.style_footer)
1598 if posx >= endx:
1599 if posx >= endx:
1599 break
1600 break
1600
1601
1601 try:
1602 try:
1602 # Display input prompt
1603 # Display input prompt
1603 if self.mode in self.prompts:
1604 if self.mode in self.prompts:
1604 history = self.prompts[self.mode]
1605 history = self.prompts[self.mode]
1605 posx = 0
1606 posx = 0
1606 posy = self.scrsizey-1
1607 posy = self.scrsizey-1
1607 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1608 posx += self.addstr(posy, posx, 0, endx, history.prompt, astyle.style_default)
1608 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1609 posx += self.addstr(posy, posx, 0, endx, " [", astyle.style_default)
1609 if history.cury==-1:
1610 if history.cury==-1:
1610 text = "new"
1611 text = "new"
1611 else:
1612 else:
1612 text = str(history.cury+1)
1613 text = str(history.cury+1)
1613 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1614 posx += self.addstr(posy, posx, 0, endx, text, astyle.style_type_number)
1614 if history.history:
1615 if history.history:
1615 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1616 posx += self.addstr(posy, posx, 0, endx, "/", astyle.style_default)
1616 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1617 posx += self.addstr(posy, posx, 0, endx, str(len(history.history)), astyle.style_type_number)
1617 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1618 posx += self.addstr(posy, posx, 0, endx, "]: ", astyle.style_default)
1618 inputstartx = posx
1619 inputstartx = posx
1619 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1620 posx += self.addstr(posy, posx, 0, endx, history.input, astyle.style_default)
1620 # Display report
1621 # Display report
1621 else:
1622 else:
1622 if self._report is not None:
1623 if self._report is not None:
1623 if isinstance(self._report, Exception):
1624 if isinstance(self._report, Exception):
1624 style = self.getstyle(astyle.style_error)
1625 style = self.getstyle(astyle.style_error)
1625 if self._report.__class__.__module__ == "exceptions":
1626 if self._report.__class__.__module__ == "exceptions":
1626 msg = "%s: %s" % \
1627 msg = "%s: %s" % \
1627 (self._report.__class__.__name__, self._report)
1628 (self._report.__class__.__name__, self._report)
1628 else:
1629 else:
1629 msg = "%s.%s: %s" % \
1630 msg = "%s.%s: %s" % \
1630 (self._report.__class__.__module__,
1631 (self._report.__class__.__module__,
1631 self._report.__class__.__name__, self._report)
1632 self._report.__class__.__name__, self._report)
1632 else:
1633 else:
1633 style = self.getstyle(self.style_report)
1634 style = self.getstyle(self.style_report)
1634 msg = self._report
1635 msg = self._report
1635 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1636 scr.addstr(self.scrsizey-1, 0, msg[:self.scrsizex], style)
1636 self._report = None
1637 self._report = None
1637 else:
1638 else:
1638 scr.move(self.scrsizey-1, 0)
1639 scr.move(self.scrsizey-1, 0)
1639 except curses.error:
1640 except curses.error:
1640 # Protect against errors from writing to the last line
1641 # Protect against errors from writing to the last line
1641 pass
1642 pass
1642 scr.clrtoeol()
1643 scr.clrtoeol()
1643
1644
1644 # Position cursor
1645 # Position cursor
1645 if self.mode in self.prompts:
1646 if self.mode in self.prompts:
1646 history = self.prompts[self.mode]
1647 history = self.prompts[self.mode]
1647 scr.move(self.scrsizey-1, inputstartx+history.curx)
1648 scr.move(self.scrsizey-1, inputstartx+history.curx)
1648 else:
1649 else:
1649 scr.move(
1650 scr.move(
1650 1+self._headerlines+level.cury-level.datastarty,
1651 1+self._headerlines+level.cury-level.datastarty,
1651 level.numbersizex+3+level.curx-level.datastartx
1652 level.numbersizex+3+level.curx-level.datastartx
1652 )
1653 )
1653 scr.refresh()
1654 scr.refresh()
1654
1655
1655 # Check keyboard
1656 # Check keyboard
1656 while True:
1657 while True:
1657 c = scr.getch()
1658 c = scr.getch()
1658 if self.resized:
1659 if self.resized:
1659 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1660 size = fcntl.ioctl(0, tty.TIOCGWINSZ, "12345678")
1660 size = struct.unpack("4H", size)
1661 size = struct.unpack("4H", size)
1661 oldsize = scr.getmaxyx()
1662 oldsize = scr.getmaxyx()
1662 scr.erase()
1663 scr.erase()
1663 curses.resize_term(size[0], size[1])
1664 curses.resize_term(size[0], size[1])
1664 newsize = scr.getmaxyx()
1665 newsize = scr.getmaxyx()
1665 scr.erase()
1666 scr.erase()
1666 for l in self.levels:
1667 for l in self.levels:
1667 l.mainsizey += newsize[0]-oldsize[0]
1668 l.mainsizey += newsize[0]-oldsize[0]
1668 l.moveto(l.curx, l.cury, refresh=True)
1669 l.moveto(l.curx, l.cury, refresh=True)
1669 scr.refresh()
1670 scr.refresh()
1670 self.resized = False
1671 self.resized = False
1671 break # Redisplay
1672 break # Redisplay
1672 if self.mode in self.prompts:
1673 if self.mode in self.prompts:
1673 if self.prompts[self.mode].handlekey(self, c):
1674 if self.prompts[self.mode].handlekey(self, c):
1674 break # Redisplay
1675 break # Redisplay
1675 else:
1676 else:
1676 # if no key is pressed slow down and beep again
1677 # if no key is pressed slow down and beep again
1677 if c == -1:
1678 if c == -1:
1678 self.stepx = 1.
1679 self.stepx = 1.
1679 self.stepy = 1.
1680 self.stepy = 1.
1680 self._dobeep = True
1681 self._dobeep = True
1681 else:
1682 else:
1682 # if a different key was pressed slow down and beep too
1683 # if a different key was pressed slow down and beep too
1683 if c != lastc:
1684 if c != lastc:
1684 lastc = c
1685 lastc = c
1685 self.stepx = 1.
1686 self.stepx = 1.
1686 self.stepy = 1.
1687 self.stepy = 1.
1687 self._dobeep = True
1688 self._dobeep = True
1688 cmdname = self.keymap.get(c, None)
1689 cmdname = self.keymap.get(c, None)
1689 if cmdname is None:
1690 if cmdname is None:
1690 self.report(
1691 self.report(
1691 UnassignedKeyError("Unassigned key %s" %
1692 UnassignedKeyError("Unassigned key %s" %
1692 self.keylabel(c)))
1693 self.keylabel(c)))
1693 else:
1694 else:
1694 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1695 cmdfunc = getattr(self, "cmd_%s" % cmdname, None)
1695 if cmdfunc is None:
1696 if cmdfunc is None:
1696 self.report(
1697 self.report(
1697 UnknownCommandError("Unknown command %r" %
1698 UnknownCommandError("Unknown command %r" %
1698 (cmdname,)))
1699 (cmdname,)))
1699 elif cmdfunc():
1700 elif cmdfunc():
1700 returnvalue = self.returnvalue
1701 returnvalue = self.returnvalue
1701 self.returnvalue = None
1702 self.returnvalue = None
1702 return returnvalue
1703 return returnvalue
1703 self.stepx = self.nextstepx(self.stepx)
1704 self.stepx = self.nextstepx(self.stepx)
1704 self.stepy = self.nextstepy(self.stepy)
1705 self.stepy = self.nextstepy(self.stepy)
1705 curses.flushinp() # get rid of type ahead
1706 curses.flushinp() # get rid of type ahead
1706 break # Redisplay
1707 break # Redisplay
1707 self.scr = None
1708 self.scr = None
1708
1709
1709 def display(self):
1710 def display(self):
1710 if hasattr(curses, "resize_term"):
1711 if hasattr(curses, "resize_term"):
1711 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1712 oldhandler = signal.signal(signal.SIGWINCH, self.sigwinchhandler)
1712 try:
1713 try:
1713 return curses.wrapper(self._dodisplay)
1714 return curses.wrapper(self._dodisplay)
1714 finally:
1715 finally:
1715 signal.signal(signal.SIGWINCH, oldhandler)
1716 signal.signal(signal.SIGWINCH, oldhandler)
1716 else:
1717 else:
1717 return curses.wrapper(self._dodisplay)
1718 return curses.wrapper(self._dodisplay)
General Comments 0
You need to be logged in to leave comments. Login now