##// END OF EJS Templates
Add two new commands to ibrowse: hideattr (mapped to "h")...
walter.doerwald -
Show More

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

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