Package SimPy :: Module tkconsole
[hide private]
[frames] | no frames]

Source Code for Module SimPy.tkconsole

  1  """A Tkinter-based console for conversing with the Python interpreter, 
  2  featuring more tolerant pasting of code from other interactive sessions, 
  3  better handling of continuations than the standard Python interpreter, 
  4  highlighting of the most recently-executed code block, the ability to 
  5  edit and reexecute previously entered code, a history of recently-entered 
  6  lines, automatic multi-level completion with pop-up menus, and pop-up help. 
  7   
  8  Ka-Ping Yee <ping@lfw.org>, 18 April 1999.  This software is in the public 
  9  domain and is provided without express or implied warranty.  Permission to 
 10  use, modify, or distribute the software for any purpose is hereby granted.""" 
 11   
 12  # TODO: autoindent to matching bracket after an unbalanced line (hard) 
 13  # TODO: outdent after line starting with "break", "raise", "return", etc. 
 14  # TODO: keep a stack of indent levels for backspace to jump back to 
 15  # TODO: blink or highlight matching brackets 
 16  # TODO: delete the prompt when joining lines; allow a way to break lines 
 17   
 18  from Tkinter import * 
 19  import sys, string, traceback, types, __builtin__ 
 20   
 21  REVISION = "$Revision: 1.1.1.4 $" 
 22  VERSION = string.split(REVISION)[1] 
 23   
24 -class OutputPipe:
25 """A substitute file object for redirecting output to a function.""" 26
27 - def __init__(self, writer):
28 self.writer = writer 29 self.closed = 0
30
31 - def __repr__(self):
32 return "<OutputPipe to %s>" % repr(self.writer)
33
34 - def read(self, length):
35 return ""
36
37 - def write(self, data):
38 if not self.closed: self.writer(data)
39
40 - def close(self):
41 self.closed = 1
42 43
44 -class Console(Frame):
45 - def __init__(self, parent=None, dict={}, **options):
46 """Construct from a parent widget, an optional dictionary to use 47 as the namespace for execution, and any configuration options.""" 48 Frame.__init__(self, parent) 49 50 # Continuation state. 51 52 self.continuation = 0 53 self.error = 0 54 self.intraceback = 0 55 self.pasted = 0 56 57 # The command history. 58 59 self.history = [] 60 self.historyindex = None 61 self.current = "" 62 63 # Completion state. 64 65 self.compmenus = [] 66 self.compindex = None 67 self.compfinish = "" 68 69 # Redirection. 70 71 self.stdout = OutputPipe(lambda data, w=self.write: w(data, "stdout")) 72 self.stderr = OutputPipe(lambda data, w=self.write: w(data, "stderr")) 73 74 # Interpreter state. 75 76 if not hasattr(sys, "ps1"): sys.ps1 = ">>> " 77 if not hasattr(sys, "ps2"): sys.ps2 = "... " 78 self.prefixes = [sys.ps1, sys.ps2, ">> ", "> "] 79 self.startup = "Python %s\n%s\n" % (sys.version, sys.copyright) + \ 80 "Python Console v%s by Ka-Ping Yee <ping@lfw.org>\n" % VERSION 81 self.dict = dict 82 83 # The text box. 84 85 self.text = Text(self, insertontime=200, insertofftime=150) 86 self.text.insert("end", self.startup) 87 self.text.insert("end", sys.ps1) 88 self.text.bind("<Return>", self.cb_return) 89 self.text.bind("<Button-1>", self.cb_select) 90 self.text.bind("<ButtonRelease-1>", self.cb_position) 91 self.text.bind("<ButtonRelease-2>", self.cb_paste) 92 self.text.bind("<Home>", self.cb_home) 93 self.text.bind("<Control-Home>", self.cb_ctrlhome) 94 self.text.bind("<Up>", self.cb_back) 95 self.text.bind("<Down>", self.cb_forward) 96 self.text.bind("<Configure>", self.cb_cleanup) 97 self.text.bind("<Expose>", self.cb_cleanup) 98 self.text.bind("<Key>", self.cb_cleanup) 99 self.text.bind("<Tab>", self.cb_complete) 100 self.text.bind("<Left>", self.cb_position) 101 self.text.bind("<space>", self.cb_space) 102 self.text.bind("<BackSpace>", self.cb_backspace) 103 self.text.bind("<KeyRelease-BackSpace>", self.cb_nothing) 104 self.text.bind("<F1>", self.cb_help) 105 self.text.bind("<Control-slash>", self.cb_help) 106 self.text.bind("<Alt-h>", self.cb_help) 107 108 # The scroll bar. 109 110 self.scroll = Scrollbar(self, command=self.text.yview) 111 self.text.config(yscrollcommand=self.scroll.set) 112 self.scroll.pack(side=RIGHT, fill=Y) 113 self.text.pack(fill=BOTH, expand=1) 114 self.text.focus() 115 116 # Configurable options. 117 118 self.options = {"stdoutcolour": "#7020c0", 119 "stderrcolour": "#c03020", 120 "morecolour": "#a0d0f0", 121 "badcolour": "#e0b0b0", 122 "runcolour": "#90d090"} 123 apply(self.config, (), self.options) 124 apply(self.config, (), options)
125
126 - def __getitem__(self, key):
127 return self.options[key]
128
129 - def __setitem__(self, key, value):
130 if not self.options.has_key(key): 131 raise KeyError, 'no such configuration option "%s"' % key 132 self.options[key] = value 133 if key == "stdoutcolour": 134 self.text.tag_configure("stdout", foreground=value) 135 if key == "stderrcolour": 136 self.text.tag_configure("stderr", foreground=value)
137
138 - def config(self, *args, **dict):
139 """Get or set configuration options in a Tkinter-like style.""" 140 if args == () and dict == {}: 141 return self.options 142 if len(args) == 1: 143 return self.options[args[0]] 144 for key, value in dict.items(): 145 self[key] = value
146 147 # Text box routines. 148
149 - def trim(self, command):
150 """Trim any matching prefix from the given command line, returning 151 the amount trimmed and the trimmed result.""" 152 for prefix in self.prefixes: 153 if command[:len(prefix)] == prefix: 154 return len(prefix), command[len(prefix):] 155 return 0, command
156
157 - def getline(self, line=None, trim=0):
158 """Return the command on the current line.""" 159 if line is None: 160 line, pos = self.cursor() 161 command = self.text.get("%d.0" % line, "%d.end" % line) 162 if trim: 163 trimmed, command = self.trim(command) 164 return command
165
166 - def cursor(self):
167 """Get the current line and position of the cursor.""" 168 cursor = self.text.index("insert") 169 [line, pos] = map(string.atoi, string.split(cursor, ".")) 170 return line, pos
171
172 - def write(self, data, tag=None):
173 """Show output from stdout or stderr in the console.""" 174 if self.intraceback and data[-2:] == "\n ": data = data[:-1] 175 start = self.text.index("insert") 176 self.text.insert("insert", data) 177 end = self.text.index("insert") 178 if tag: self.text.tag_add(tag, start, end)
179 180 # History mechanism. 181
182 - def cb_back(self, event):
183 """Step back in the history.""" 184 if self.history: 185 if self.historyindex == None: 186 self.current = self.getline(trim=1) 187 self.historyindex = len(self.history) - 1 188 elif self.historyindex > 0: 189 self.historyindex = self.historyindex - 1 190 self.recall() 191 192 return "break"
193
194 - def cb_forward(self, event):
195 """Step forward in the history.""" 196 if self.history and self.historyindex is not None: 197 self.historyindex = self.historyindex + 1 198 if self.historyindex < len(self.history): 199 self.recall() 200 else: 201 self.historyindex = None 202 self.recall(self.current) 203 204 return "break"
205
206 - def recall(self, command=None):
207 """Show a command from the history on the current line.""" 208 if command is None: 209 command = self.history[self.historyindex] 210 line, pos = self.cursor() 211 current = self.getline(line) 212 trimmed, trimmedline = self.trim(current) 213 cutpos = "%d.%d" % (line, trimmed) 214 self.text.delete(cutpos, "%d.end" % line) 215 self.text.insert(cutpos, command) 216 self.text.mark_set("insert", "%d.end" % line)
217 218 # Completion mechanism. 219
220 - def precontext(self):
221 # Scan back for the identifier currently being typed. 222 line, pos = self.cursor() 223 command = self.getline() 224 preceding = command[:pos] 225 startchars = string.letters + "_" 226 identchars = string.letters + string.digits + "_" 227 while pos > 0 and preceding[pos-1] in identchars: 228 pos = pos - 1 229 preceding, ident = preceding[:pos], preceding[pos:] 230 start = "%d.%d" % (line, pos) 231 232 preceding = string.strip(preceding) 233 context = "" 234 if not ident or ident[0] in startchars: 235 # Look for context before the start of the identifier. 236 while preceding[-1:] == ".": 237 preceding = string.strip(preceding[:-1]) 238 if preceding[-1] in identchars: 239 pos = len(preceding)-1 240 while pos > 0 and preceding[pos-1] in identchars: 241 pos = pos - 1 242 if preceding[pos] in startchars: 243 context = preceding[pos:] + "." + context 244 preceding = string.strip(preceding[:pos]) 245 else: break 246 else: break 247 248 line, pos = self.cursor() 249 endpos = pos 250 while endpos < len(command) and command[endpos] in identchars: 251 endpos = endpos + 1 252 end = "%d.%d" % (line, endpos) 253 254 return command, context, ident, start, end
255
256 - def cb_complete(self, event):
257 """Attempt to complete the identifier currently being typed.""" 258 if self.compmenus: 259 if self.cursor() == self.compindex: 260 # Second attempt to complete: add finishing char and continue. 261 self.text.insert("insert", self.compfinish) 262 self.compindex = None 263 self.unpostmenus() 264 return "break" 265 266 command, context, ident, start, end = self.precontext() 267 268 # Get the list of possible choices. 269 if context: 270 try: 271 object = eval(context[:-1], self.dict) 272 keys = members(object) 273 except: 274 object = None 275 keys = [] 276 else: 277 class Lookup: 278 def __init__(self, dicts): 279 self.dicts = dicts
280 281 def __getattr__(self, key): 282 for dict in self.dicts: 283 if dict.has_key(key): return dict[key] 284 return None
285 object = Lookup([self.dict, __builtin__.__dict__]) 286 keys = self.dict.keys() + dir(__builtin__) 287 288 keys = matchingkeys(keys, ident) 289 if not ident: 290 public = [] 291 for key in keys: 292 if key[:1] != "_": public.append(key) 293 keys = public 294 skip = len(ident) 295 296 # Produce the completion. 297 if len(keys) == 1: 298 # Complete with the single possible choice. 299 if self.cursor() == self.compindex: 300 # Second attempt to complete: add finisher and continue. 301 self.text.insert("insert", self.compfinish) 302 self.compindex = None 303 else: 304 self.text.delete("insert", end) 305 self.text.insert("insert", keys[0][skip:]) 306 try: self.compfinish = finisher(getattr(object, keys[0])) 307 except: self.compfinish = " " 308 if self.compfinish == " ": 309 # Object has no members; stop here. 310 self.text.insert("insert", " ") 311 else: 312 self.compindex = self.cursor() 313 elif len(keys) > 1: 314 # Present a menu. 315 prefix = commonprefix(keys) 316 keys.sort() 317 if len(prefix) > skip: 318 self.text.delete("insert", end) 319 self.text.insert("insert", keys[0][skip:len(prefix)]) 320 skip = len(prefix) 321 322 if len(keys[0]) == skip: 323 # Common prefix is a valid choice; next try can finish. 324 self.compindex = self.cursor() 325 try: self.compfinish = finisher(getattr(object, keys[0])) 326 except: self.compfinish = " " 327 328 self.postmenus(keys, skip, end, object) 329 330 return "break" 331
332 - def postmenus(self, keys, skip, cut, object):
333 """Post a series of menus listing all the given keys, given the 334 length of the existing part so we can position the menus under the 335 cursor, and the index at which to insert the completion.""" 336 width = self.winfo_screenwidth() 337 height = self.winfo_screenheight() 338 bbox = self.text.bbox("insert - %d c" % skip) 339 x = self.text.winfo_rootx() + bbox[0] - 4 340 y = self.text.winfo_rooty() + bbox[1] + bbox[3] 341 342 self.compmenus = [] 343 menufont = self.text.cget("font") 344 menu = Menu(font=menufont, bd=1, tearoff=0) 345 self.compmenus.append(menu) 346 while keys: 347 try: finishchar = finisher(getattr(object, keys[0])) 348 except: finishchar = " " 349 def complete(s=self, k=keys[0][skip:], c=cut, f=finishchar): 350 if f == " ": k = k + f 351 s.text.delete("insert", c) 352 s.text.insert("insert", k) 353 s.unpostmenus() 354 if f != " ": 355 s.compfinish = f 356 s.compindex = s.cursor()
357 menu.add_command(label=keys[0], command=complete) 358 menu.update() 359 if y + menu.winfo_reqheight() >= height: 360 menu.delete("end") 361 x = x + menu.winfo_reqwidth() 362 y = 0 363 menu = Menu(font=menufont, bd=1, tearoff=0) 364 self.compmenus.append(menu) 365 else: 366 keys = keys[1:] 367 if x + menu.winfo_reqwidth() > width: 368 menu.destroy() 369 self.compmenus = self.compmenus[:-1] 370 self.compmenus[-1].delete("end") 371 self.compmenus[-1].add_command(label="...") 372 break 373 374 x = self.text.winfo_rootx() + bbox[0] - 4 375 y = self.text.winfo_rooty() + bbox[1] + bbox[3] 376 for menu in self.compmenus: 377 maxtop = height - menu.winfo_reqheight() 378 if y > maxtop: y = maxtop 379 menu.post(x, y) 380 x = x + menu.winfo_reqwidth() 381 self.text.focus() 382 self.text.grab_set() 383
384 - def unpostmenus(self):
385 """Unpost the completion menus.""" 386 for menu in self.compmenus: 387 menu.destroy() 388 self.compmenus = [] 389 self.text.grab_release()
390
391 - def cb_cleanup(self, event=None):
392 if self.compmenus: 393 self.unpostmenus() 394 if self.pasted: 395 self.text.tag_remove("sel", "1.0", "end") 396 self.pasted = 0
397
398 - def cb_select(self, event):
399 """Handle a menu selection event. We have to check and invoke the 400 completion menus manually because we are grabbing events to give the 401 text box keyboard focus.""" 402 if self.compmenus: 403 for menu in self.compmenus: 404 x, y = menu.winfo_rootx(), menu.winfo_rooty() 405 w, h = menu.winfo_width(), menu.winfo_height() 406 if x < event.x_root < x + w and \ 407 y < event.y_root < y + h: 408 item = menu.index("@%d" % (event.y_root - y)) 409 menu.invoke(item) 410 break 411 else: 412 self.unpostmenus() 413 return "break"
414 415 # Help mechanism. 416
417 - def cb_help(self, event):
418 command, context, ident, start, end = self.precontext() 419 word = self.text.get(start, end) 420 421 object = parent = doc = None 422 skip = 0 423 424 try: 425 parent = eval(context[:-1], self.dict) 426 except: pass 427 428 # Go merrily searching for the help string. 429 if not object: 430 try: 431 object = getattr(parent, word) 432 skip = len(word) - len(ident) 433 except: pass 434 435 if not object: 436 try: 437 object = getattr(parent, ident) 438 except: pass 439 440 if not object: 441 try: 442 object = self.dict[word] 443 skip = len(word) - len(ident) 444 except: pass 445 446 if not object: 447 try: 448 object = self.dict[ident] 449 except: pass 450 451 if not object: 452 try: 453 object = __builtin__.__dict__[word] 454 skip = len(word) - len(ident) 455 except: pass 456 457 if not object: 458 try: 459 object = __builtins__.__dict__[ident] 460 except: pass 461 462 if not object: 463 if not ident: 464 object = parent 465 466 try: 467 doc = object.__doc__ 468 except: pass 469 470 try: 471 if hasattr(object, "__bases__"): 472 doc = object.__init__.__doc__ or doc 473 except: pass 474 475 if doc: 476 doc = string.rstrip(string.expandtabs(doc)) 477 leftmargin = 99 478 for line in string.split(doc, "\n")[1:]: 479 spaces = len(line) - len(string.lstrip(line)) 480 if line and spaces < leftmargin: leftmargin = spaces 481 482 bbox = self.text.bbox("insert + %d c" % skip) 483 width = self.winfo_screenwidth() 484 height = self.winfo_screenheight() 485 menufont = self.text.cget("font") 486 487 help = Menu(font=menufont, bd=1, tearoff=0) 488 try: 489 classname = object.__class__.__name__ 490 help.add_command(label="<object of class %s>" % classname) 491 help.add_command(label="") 492 except: pass 493 for line in string.split(doc, "\n"): 494 if string.strip(line[:leftmargin]) == "": 495 line = line[leftmargin:] 496 help.add_command(label=line) 497 self.compmenus.append(help) 498 499 x = self.text.winfo_rootx() + bbox[0] - 4 500 y = self.text.winfo_rooty() + bbox[1] + bbox[3] 501 maxtop = height - help.winfo_reqheight() 502 if y > maxtop: y = maxtop 503 help.post(x, y) 504 self.text.focus() 505 self.text.grab_set() 506 507 return "break"
508 509 # Entering commands. 510
511 - def cb_position(self, event):
512 """Avoid moving into the prompt area.""" 513 self.cb_cleanup() 514 line, pos = self.cursor() 515 trimmed, command = self.trim(self.getline()) 516 if pos <= trimmed: 517 self.text.mark_set("insert", "%d.%d" % (line, trimmed)) 518 return "break"
519
520 - def cb_backspace(self, event):
521 self.cb_cleanup() 522 if self.text.tag_ranges("sel"): return 523 524 # Avoid backspacing over the prompt. 525 line, pos = self.cursor() 526 trimmed, command = self.trim(self.getline()) 527 if pos <= trimmed: return "break" 528 529 # Extremely basic outdenting. Needs more work here. 530 if not string.strip(command[:pos-trimmed]): 531 step = (pos - trimmed) % 4 532 cut = pos - (step or 4) 533 if cut < trimmed: cut = trimmed 534 self.text.delete("%d.%d" % (line, cut), "%d.%d" % (line, pos)) 535 return "break"
536
537 - def cb_space(self, event):
538 self.cb_cleanup() 539 line, pos = self.cursor() 540 trimmed, command = self.trim(self.getline()) 541 542 # Extremely basic indenting. Needs more work here. 543 if not string.strip(command[:pos-trimmed]): 544 start = trimmed + len(command) - len(string.lstrip(command)) 545 self.text.delete("insert", "%d.%d" % (line, start)) 546 step = 4 - (pos - trimmed) % 4 547 self.text.insert("insert", " " * step) 548 return "break"
549
550 - def cb_home(self, event):
551 """Go to the first non-whitespace character in the line.""" 552 self.cb_cleanup() 553 line, pos = self.cursor() 554 trimmed, command = self.trim(self.getline()) 555 indent = len(command) - len(string.lstrip(command)) 556 self.text.mark_set("insert", "%d.%d" % (line, trimmed + indent)) 557 return "break"
558
559 - def cb_ctrlhome(self, event):
560 """Go to the beginning of the line just after the prompt.""" 561 self.cb_cleanup() 562 line, pos = self.cursor() 563 trimmed, command = self.trim(self.getline()) 564 self.text.mark_set("insert", "%d.%d" % (line, trimmed)) 565 return "break"
566
567 - def cb_nothing(self, event):
568 return "break"
569
570 - def cb_return(self, event, doindent=1):
571 """Handle a <Return> keystroke by running from the current line 572 and generating a new prompt.""" 573 self.cb_cleanup() 574 self.text.tag_delete("compiled") 575 self.historyindex = None 576 command = self.getline(trim=1) 577 if string.strip(command): 578 self.history.append(command) 579 580 line, pos = self.cursor() 581 self.text.mark_set("insert", "%d.end" % line) 582 self.text.insert("insert", "\n") 583 self.runline(line) 584 585 line, pos = self.cursor() 586 self.text.mark_set("insert", "%d.end" % line) 587 prompt = self.continuation and sys.ps2 or sys.ps1 588 if pos > 0: 589 self.text.insert("insert", "\n" + prompt) 590 else: 591 self.text.insert("insert", prompt) 592 593 if doindent and not self.error: 594 self.autoindent(command) 595 self.error = 0 596 self.text.see("insert") 597 return "break"
598
599 - def autoindent(self, command):
600 # Extremely basic autoindenting. Needs more work here. 601 indent = len(command) - len(string.lstrip(command)) 602 if string.lstrip(command): 603 self.text.insert("insert", command[:indent]) 604 if string.rstrip(command)[-1] == ":": 605 self.text.insert("insert", " ")
606
607 - def cb_paste(self, event):
608 """Handle a paste event (middle-click) in the text box. Pasted 609 text has any leading Python prompts stripped (at last!!).""" 610 self.text.tag_delete("compiled") 611 self.error = 0 612 self.pasted = 1 613 614 try: lines = string.split(self.selection_get(), "\n") 615 except: return 616 617 for i in range(len(lines)): 618 trimmed, line = self.trim(lines[i]) 619 line = string.rstrip(line) 620 if not line: continue 621 622 self.text.insert("end", line) 623 self.text.mark_set("insert", "end") 624 if i == len(lines) - 2 and lines[i+1] == "": 625 # Indent the last line if it's blank. 626 self.cb_return(None, doindent=1) 627 elif i < len(lines) - 1: 628 self.cb_return(None, doindent=0) 629 630 if self.error: break 631 632 return "break"
633 634 # Executing commands. 635
636 - def runline(self, line):
637 """Run some source code given the number of the last line in the 638 text box. Scan backwards to get the entire piece of code to run 639 if the line is a continuation of previous lines. Tag the compiled 640 code so that it can be highlighted according to whether it is 641 complete, incomplete, or illegal.""" 642 lastline = line 643 lines = [self.getline(line)] 644 while lines[0][:len(sys.ps2)] == sys.ps2: 645 trimmed, lines[0] = self.trim(lines[0]) 646 self.text.tag_add( 647 "compiled", "%d.%d" % (line, trimmed), "%d.0" % (line+1)) 648 line = line - 1 649 if line < 0: break 650 lines[:0] = [self.getline(line)] 651 if lines[0][:len(sys.ps1)] == sys.ps1: 652 trimmed, lines[0] = self.trim(lines[0]) 653 self.text.tag_add( 654 "compiled", "%d.%d" % (line, trimmed), "%d.0" % (line+1)) 655 else: 656 self.text.tag_add("compiled", "%d.0" % line, "%d.0" % (line+1)) 657 658 source = string.join(lines, "\n") 659 if not source: 660 self.continuation = 0 661 return 662 663 status, code = self.compile(source) 664 665 if status == "more": 666 self.text.tag_configure("compiled", background=self["morecolour"]) 667 self.continuation = 1 668 669 elif status == "bad": 670 self.text.tag_configure("compiled", background=self["badcolour"]) 671 self.error = 1 672 self.continuation = 0 673 self.intraceback = 1 674 oldout, olderr = sys.stdout, sys.stderr 675 sys.stdout, sys.stderr = self.stdout, self.stderr 676 traceback.print_exception(SyntaxError, code, None) 677 self.stdout, self.stderr = sys.stdout, sys.stderr 678 sys.stdout, sys.stderr = oldout, olderr 679 self.intraceback = 0 680 681 elif status == "okay": 682 if self.getline(lastline) == sys.ps2: 683 self.text.tag_remove("compiled", "%d.0" % lastline, "end") 684 self.text.tag_configure("compiled", background=self["runcolour"]) 685 self.continuation = 0 686 self.run(code)
687
688 - def compile(self, source):
689 """Try to compile a piece of source code, returning a status code 690 and the compiled result. If the status code is "okay" the code is 691 complete and compiled successfully; if it is "more" then the code 692 can be compiled, but an interactive session should wait for more 693 input; if it is "bad" then there is a syntax error in the code and 694 the second returned value is the error message.""" 695 err = err1 = err2 = None 696 code = code1 = code2 = None 697 698 try: 699 code = compile(source, "<console>", "single") 700 except SyntaxError, err: 701 pass 702 else: 703 return "okay", code 704 705 try: 706 code1 = compile(source + "\n", "<console>", "single") 707 except SyntaxError, err1: 708 pass 709 else: 710 return "more", code1 711 712 try: 713 code2 = compile(source + "\n\n", "<console>", "single") 714 except SyntaxError, err2: 715 pass 716 717 try: 718 code3 = compile(source + "\n", "<console>", "exec") 719 except SyntaxError, err3: 720 pass 721 else: 722 return "okay", code3 723 724 try: 725 code4 = compile(source + "\n\n", "<console>", "exec") 726 except SyntaxError, err4: 727 pass 728 729 if err3[1][2] != err4[1][2]: 730 return "more", None 731 732 if err1[1][2] != err2[1][2]: 733 return "more", None 734 735 return "bad", err1
736
737 - def run(self, code):
738 """Run a code object within the sandbox for this console. The 739 sandbox redirects stdout and stderr to the console, and executes 740 within the namespace associated with the console.""" 741 oldout, olderr = sys.stdout, sys.stderr 742 sys.stdout, sys.stderr = self.stdout, self.stderr 743 744 try: 745 exec code in self.dict 746 except: 747 self.error = 1 748 sys.last_type = sys.exc_type 749 sys.last_value = sys.exc_value 750 sys.last_traceback = sys.exc_traceback.tb_next 751 self.intraceback = 1 752 traceback.print_exception( 753 sys.last_type, sys.last_value, sys.last_traceback) 754 self.intraceback = 0 755 756 self.stdout, self.stderr = sys.stdout, sys.stderr 757 sys.stdout, sys.stderr = oldout, olderr
758 759 760 # Helpers for the completion mechanism. 761
762 -def scanclass(klass, result):
763 for key in klass.__dict__.keys(): result[key] = 1 764 for base in klass.__bases__: scanclass(base, result)
765
766 -def members(object):
767 result = {} 768 try: 769 for key in object.__members__: result[key] = 1 770 result["__members__"] = 1 771 except: pass 772 try: 773 for key in object.__methods__: result[key] = 1 774 result["__methods__"] = 1 775 except: pass 776 try: 777 for key in object.__dict__.keys(): result[key] = 1 778 result["__dict__"] = 1 779 except: pass 780 if type(object) is types.ClassType: 781 scanclass(object, result) 782 result["__name__"] = 1 783 result["__bases__"] = 1 784 if type(object) is types.InstanceType: 785 scanclass(object.__class__, result) 786 result["__class__"] = 1 787 return result.keys()
788
789 -def matchingkeys(keys, prefix):
790 prefixmatch = lambda key, l=len(prefix), p=prefix: key[:l] == p 791 return filter(prefixmatch, keys)
792
793 -def commonprefix(keys):
794 if not keys: return '' 795 max = len(keys[0]) 796 prefixes = map(lambda i, key=keys[0]: key[:i], range(max+1)) 797 for key in keys: 798 while key[:max] != prefixes[max]: 799 max = max - 1 800 if max == 0: return '' 801 return prefixes[max]
802 803 callabletypes = [types.FunctionType, types.MethodType, types.ClassType, 804 types.BuiltinFunctionType, types.BuiltinMethodType] 805 sequencetypes = [types.TupleType, types.ListType] 806 mappingtypes = [types.DictType] 807 808 try: 809 import ExtensionClass 810 callabletypes.append(ExtensionClass.ExtensionClassType) 811 except: pass 812 try: 813 import curve 814 c = curve.Curve() 815 callabletypes.append(type(c.read)) 816 except: pass 817
818 -def finisher(object):
819 if type(object) in callabletypes: 820 return "(" 821 elif type(object) in sequencetypes: 822 return "[" 823 elif type(object) in mappingtypes: 824 return "{" 825 elif members(object): 826 return "." 827 return " "
828 829 830 # Main program. 831 832 if __name__ == "__main__": 833 c = Console(dict={}) 834 c.dict["console"] = c 835 c.pack(fill=BOTH, expand=1) 836 c.master.title("Python Console v%s" % VERSION) 837 mainloop() 838