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