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