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