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