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