console control of a session

This combo puts a prompt on the console in a separate thread, then runs the engine in the main thread. Console commands are added to a queue, which is checked every frame for tasks. The console is checked for exceptions which are caught and printed. The commands are executed in an empty class, their own namespace, so local vars. are preserved, and global vars are still accessible.

import direct.directbase.DirectStart
from panda3d.core import *
import Queue
import traceback

queue= Queue.Queue( )
queuereturn= Queue.Queue( )
class Globals:
    pass
G= Globals

def taskly( task ):
    try:
        x= queue.get( False )
    except Queue.Empty:
        pass
    else:
        try:
            exec x in globals( ), G.__dict__
        except Exception:
            traceback.print_exc( )
        queuereturn.put( True )
    return task.cont

import thread
def threadly( ):
    while 1:
        x= raw_input( '-->' )
        queue.put( x )
        queuereturn.get( ) #await completion

taskMgr.add( taskly, 'taskly' )

thread.start_new_thread( threadly, () )

run( )

Session printout:

DirectStart: Starting the game.
Known pipe types:
  wglGraphicsPipe
(all display modules loaded.)
-->cm= CardMaker( )
Traceback (most recent call last):
  File "C:\Documents and Settings\usr\My Documents\working\panda-async.py", line 19, in ta
skly
    exec x in globals( ), G.__dict__
  File "<string>", line 1, in <module>
TypeError: Required argument 'name' (pos 1) not found
-->cm= CardMaker( 'cm' )
-->c= cm.generate( )
-->cp= NodePath( c )
-->cp.reparentTo( render )
-->base.cam.setPos( 0, -10, 0 )
-->print base.mouseWatcher
dataRoot/keyboard_mouse/watcher0
-->print base.mouseWatcher.getMouse( )
Traceback (most recent call last):
  File "C:\Documents and Settings\usr\My Documents\working\panda-async.py", line 19, in ta
skly
    exec x in globals( ), G.__dict__
  File "<string>", line 1, in <module>
AttributeError: 'libpanda.NodePath' object has no attribute 'getMouse'
-->print base.mouseWatcherNode.getMouse( )
Point2(0.75, 1.04333)

Wow, this is pretty nice. So short, but so useful.

I claim the window is responsive and rendering during the edits, which appear in the next frame after they’re executed. This example only made two visible changes:-the card’s parent
-the camera’s positionThe contents of the Globals class are the variables we defined (shown some steps later):

-->print dir( Globals )
['__doc__', '__module__', 'a', 'aa', 'c', 'cm', 'cp']
-->pprint.pprint( Globals.__dict__ )
{'__doc__': None,
 '__module__': '__main__',
 'a': a,
 'aa': GeomNode a (1 geoms: S:(ColorAttrib RenderModeAttrib)),
 'c': GeomNode cm (1 geoms),
 'cm': cm,
 'cp': render/cm,
 'pprint': <module 'pprint'...>}

very useful. +1

If you do it right, you could just import a module and suddenly have console control. But.

The crucial object is taskMgr, or you’d have to add the task yourself or pass it to an init function. But the namespace is much of the beauty, while not crucial. How do you get the globals from your program, to be able to be accessed from the exec call, if the context used is in another module?

Pass in the context class or pass in the main module? What would you want to see? Also, if you want multi-line commands to be immediate, we’ll need something much more sophisticated than ‘raw_input’, say, code.InteractiveConsole.

i downloaded from lfw.org/python/ console.py
did some modifications because of errors in the tab completion. I also block the panda thread until the console executes its code.
some benifits are:

  • comand history
  • tab command completion
  • method list with tab

but there are still some bugs

usage is something like this:

import Console2
Console2.RunConsole(globals())

Console2.py is:

"""A Tkinter-based console for conversing with the Python interpreter,
featuring more tolerant pasting of code from other interactive sessions,
better handling of continuations than the standard Python interpreter,
highlighting of the most recently-executed code block, the ability to
edit and reexecute previously entered code, a history of recently-entered
lines, automatic multi-level completion with pop-up menus, and pop-up help.

Ka-Ping Yee <ping@lfw.org>, 18 April 1999.  This software is in the public
domain and is provided without express or implied warranty.  Permission to
use, modify, or distribute the software for any purpose is hereby granted."""

# TODO: autoindent to matching bracket after an unbalanced line (hard)
# TODO: outdent after line starting with "break", "raise", "return", etc.
# TODO: keep a stack of indent levels for backspace to jump back to
# TODO: blink or highlight matching brackets
# TODO: delete the prompt when joining lines; allow a way to break lines

from Tkinter import *
import sys, string, traceback, types, __builtin__
from direct.task import Task
REVISION = "$Revision: 1.4 $"
VERSION = string.split(REVISION)[1]
import threading
event1=threading.Event()
event2=threading.Event()
event2.clear()
event1.set()
class OutputPipe:
	"""A substitute file object for redirecting output to a function."""

	def __init__(self, writer):
		self.writer = writer
		self.closed = 0

	def __repr__(self):
		return "<OutputPipe to %s>" % repr(self.writer)

	def read(self, length):
		return ""

	def write(self, data):
		if not self.closed: self.writer(data)

	def close(self):
		self.closed = 1


class Console(Frame):
	def __init__(self, parent=None, dict={}, **options):
		"""Construct from a parent widget, an optional dictionary to use
		as the namespace for execution, and any configuration options."""
		Frame.__init__(self, parent)

		# Continuation state.

		self.continuation = 0
		self.error = 0
		self.intraceback = 0
		self.pasted = 0

		# The command history.

		self.history = []
		self.historyindex = None
		self.current = ""

		# Completion state.

		self.compmenus = []
		self.compindex = None
		self.compfinish = ""

		# Redirection.
			
		self.stdout = OutputPipe(lambda data, w=self.write: w(data, "stdout"))
		self.stderr = OutputPipe(lambda data, w=self.write: w(data, "stderr"))

		# Interpreter state.

		if not hasattr(sys, "ps1"): sys.ps1 = ">>> "
		if not hasattr(sys, "ps2"): sys.ps2 = "... "
		self.prefixes = [sys.ps1, sys.ps2, ">> ", "> "]
		self.startup = "Python %s\n%s\n" % (sys.version, sys.copyright) + \
			"Python Console v%s by Ka-Ping Yee <ping@lfw.org>\n" % VERSION
		self.dict = dict

		# The text box.

		self.text = Text(self, insertontime=200, insertofftime=150)
		self.text.insert("end", self.startup)
		self.text.insert("end", sys.ps1)
		self.text.bind("<Return>", self.cb_return)
		self.text.bind("<Button-1>", self.cb_select)
		self.text.bind("<ButtonRelease-1>", self.cb_position)
		self.text.bind("<ButtonRelease-2>", self.cb_paste)
		self.text.bind("<Home>", self.cb_home)
		self.text.bind("<Control-Home>", self.cb_ctrlhome)
		self.text.bind("<Up>", self.cb_back)
		self.text.bind("<Down>", self.cb_forward)
		self.text.bind("<Configure>", self.cb_cleanup)
		self.text.bind("<Expose>", self.cb_cleanup)
		self.text.bind("<Key>", self.cb_cleanup)
		self.text.bind("<Tab>", self.cb_complete)
		self.text.bind("<Left>", self.cb_position)
		self.text.bind("<space>", self.cb_space)
		self.text.bind("<BackSpace>", self.cb_backspace)
		self.text.bind("<KeyRelease-BackSpace>", self.cb_nothing)
		self.text.bind("<F1>", self.cb_help)
		self.text.bind("<Control-slash>", self.cb_help)
		self.text.bind("<Alt-h>", self.cb_help)

		# The scroll bar.

		self.scroll = Scrollbar(self, command=self.text.yview)
		self.text.config(yscrollcommand=self.scroll.set)
		self.scroll.pack(side=RIGHT, fill=Y)
		self.text.pack(fill=BOTH, expand=1)
		self.text.focus()

		# Configurable options.

		self.options = {"stdoutcolour": "#7020c0",
						"stderrcolour": "#c03020",
						"morecolour": "#a0d0f0",
						"badcolour": "#e0b0b0",
						"runcolour": "#90d090"}
		apply(self.config, (), self.options)
		apply(self.config, (), options)

	def __getitem__(self, key):
		return self.options[key]

	def __setitem__(self, key, value):
		if not self.options.has_key(key):
			raise KeyError, 'no such configuration option "%s"' % key
		self.options[key] = value
		if key == "stdoutcolour":
			self.text.tag_configure("stdout", foreground=value)
		if key == "stderrcolour":
			self.text.tag_configure("stderr", foreground=value)

	def config(self, *args, **dict):
		"""Get or set configuration options in a Tkinter-like style."""
		if args == () and dict == {}:
			return self.options
		if len(args) == 1:
			return self.options[args[0]]
		for key, value in dict.items():
			self[key] = value

	# Text box routines.

	def trim(self, command):
		"""Trim any matching prefix from the given command line, returning
		the amount trimmed and the trimmed result."""
		for prefix in self.prefixes:
			if command[:len(prefix)] == prefix:
				return len(prefix), command[len(prefix):]
		return 0, command

	def getline(self, line=None, trim=0):
		"""Return the command on the current line."""
		if line is None:
			line, pos = self.cursor()
		command = self.text.get("%d.0" % line, "%d.end" % line)
		if trim:
			trimmed, command = self.trim(command)
		return command

	def cursor(self):
		"""Get the current line and position of the cursor."""
		cursor = self.text.index("insert")
		[line, pos] = map(string.atoi, string.split(cursor, "."))
		return line, pos
	
	def write(self, data, tag=None):
		"""Show output from stdout or stderr in the console."""
		if self.intraceback and data[-2:] == "\n ": data = data[:-1]
		start = self.text.index("insert")
		self.text.insert("insert", data)
		end = self.text.index("insert")
		if tag: self.text.tag_add(tag, start, end)

	# History mechanism.

	def cb_back(self, event):
		"""Step back in the history."""
		if self.history:
			if self.historyindex == None:
				self.current = self.getline(trim=1)
				self.historyindex = len(self.history) - 1
			elif self.historyindex > 0:
				self.historyindex = self.historyindex - 1
			self.recall()
			
		return "break"

	def cb_forward(self, event):
		"""Step forward in the history."""
		if self.history and self.historyindex is not None:
			self.historyindex = self.historyindex + 1
			if self.historyindex < len(self.history):
				self.recall()
			else:
				self.historyindex = None
				self.recall(self.current)

		return "break"

	def recall(self, command=None):
		"""Show a command from the history on the current line."""
		if command is None:
			command = self.history[self.historyindex]
		line, pos = self.cursor()
		current = self.getline(line)
		trimmed, trimmedline = self.trim(current)
		cutpos = "%d.%d" % (line, trimmed)
		self.text.delete(cutpos, "%d.end" % line)
		self.text.insert(cutpos, command)
		self.text.mark_set("insert", "%d.end" % line)

	# Completion mechanism.

	def precontext(self):
		# Scan back for the identifier currently being typed.
		line, pos = self.cursor()
		command = self.getline()
		preceding = command[:pos]
		startchars = string.letters + "_"
		identchars = string.letters + string.digits + "_"
		while pos > 0 and preceding[pos-1] in identchars:
			pos = pos - 1
		preceding, ident = preceding[:pos], preceding[pos:]
		start = "%d.%d" % (line, pos)

		preceding = string.strip(preceding)
		context = ""
		if not ident or ident[0] in startchars:
			# Look for context before the start of the identifier.
			while preceding[-1:] == ".":
				preceding = string.strip(preceding[:-1])
				if preceding[-1] in identchars:
					pos = len(preceding)-1
					while pos > 0 and preceding[pos-1] in identchars:
						pos = pos - 1
					if preceding[pos] in startchars:
						context = preceding[pos:] + "." + context
						preceding = string.strip(preceding[:pos])
					else: break
				else: break

		line, pos = self.cursor()
		endpos = pos
		while endpos < len(command) and command[endpos] in identchars:
			endpos = endpos + 1
		end = "%d.%d" % (line, endpos)

		return command, context, ident, start, end

	def cb_complete(self, event):
		"""Attempt to complete the identifier currently being typed."""
		if self.compmenus:
			if self.cursor() == self.compindex:
				# Second attempt to complete: add finishing char and continue.
				self.text.insert("insert", self.compfinish)
				self.compindex = None
				self.unpostmenus()
			return "break"
		command2 = self.getline(trim=1)
		command, context, ident, start, end = self.precontext()
		"""print ident
		if context and context[len(context)-1]==".":
			
			
			line, pos = self.cursor()
			#self.text.mark_set("insert", "%d.end" % line)
			self.text.insert("insert", "\n")
			self.text.insert("insert", str(dir(eval(context[:-1],self.dict))) )

			line, pos = self.cursor()
			#self.text.mark_set("insert", "%d.end" % line)
			prompt = self.continuation and sys.ps2 or sys.ps1
			if pos > 0:
				self.text.insert("insert", "\n" + prompt)
			else:
				self.text.insert("insert", prompt)

			if not self.error:
				self.autoindent(command)
			self.error = 0
			self.text.see("insert")
			self.text.insert("insert", command2)"""
		
		# Get the list of possible choices.
		if context:
			try:
				object = eval(context[:-1], self.dict)
				keys = members(object)
			except:
				object = None
				keys = []
		else:
			class Lookup:
				def __init__(self, dicts):
					self.dicts = dicts

				def __getattr__(self, key):
					for dict in self.dicts:
						if dict.has_key(key): return dict[key]
					return None
			object = Lookup([self.dict, __builtin__.__dict__])
			keys = self.dict.keys() + dir(__builtin__)

		keys = matchingkeys(keys, ident)
		if not ident:
			public = []
			for key in keys:
				if key[:1] != "_": public.append(key)
			keys = public
		skip = len(ident)

		# Produce the completion.
		if len(keys) == 1:
			# Complete with the single possible choice.
			if self.cursor() == self.compindex:
				# Second attempt to complete: add finisher and continue.
				self.text.insert("insert", self.compfinish)
				self.compindex = None
			else:
				self.text.delete("insert", end)
				self.text.insert("insert", keys[0][skip:])
				try: self.compfinish = finisher(getattr(object, keys[0]))
				except: self.compfinish = " "
				if self.compfinish == " ":
					# Object has no members; stop here.
					self.text.insert("insert", " ")
				else:
					self.compindex = self.cursor()
		elif len(keys) > 1:
			# Present a menu.
			prefix = commonprefix(keys)
			keys.sort()
			if len(prefix) > skip:
				self.text.delete("insert", end)
				self.text.insert("insert", keys[0][skip:len(prefix)])
				skip = len(prefix)

			if len(keys[0]) == skip:
				# Common prefix is a valid choice; next try can finish.
				self.compindex = self.cursor()
				try: self.compfinish = finisher(getattr(object, keys[0]))
				except: self.compfinish = " "

			self.postmenus(keys, skip, end, object,command2)

		return "break" 

	def postmenus(self, keys, skip, cut, object, command2):
		"""Post a series of menus listing all the given keys, given the
		length of the existing part so we can position the menus under the
		cursor, and the index at which to insert the completion."""
		#width = self.winfo_screenwidth()
		#height = self.winfo_screenheight()
		#bbox = self.text.bbox("insert - %d c" % skip)
		#x = self.text.winfo_rootx() + bbox[0] - 4
		#y = self.text.winfo_rooty() + bbox[1] + bbox[3]
		line, pos = self.cursor()
		#self.text.mark_set("insert", "%d.end" % line)
		self.text.insert("insert", "\n")
		self.text.insert("insert", str(keys) )

		line, pos = self.cursor()
		#self.text.mark_set("insert", "%d.end" % line)
		prompt = self.continuation and sys.ps2 or sys.ps1
		if pos > 0:
			self.text.insert("insert", "\n" + prompt)
		else:
			self.text.insert("insert", prompt)
			
		self.recall(command2)
		#if not self.error:
		#	self.autoindent(command2)
		#self.error = 0
		#self.text.insert("insert", command2)
		self.text.see("insert")
		
		#self.compmenus = []
		#menufont = self.text.cget("font")
		#menu = Menu(font=menufont, bd=1, tearoff=0)
		#self.compmenus.append(menu)
		#while keys:
			#try: finishchar = finisher(getattr(object, keys[0]))
			#except: finishchar = " "
			#def complete(s=self, k=keys[0][skip:], c=cut, f=finishchar):
			#	if f == " ": k = k + f
			#self.text.delete("insert", c)
			#self.text.insert("insert", k)
			#self.unpostmenus()
			#		if f != " ":
			#		s.compfinish = f
			#		s.compindex = s.cursor()
			#menu.add_command(label=keys[0], command=complete)
			#menu.update()
			#if y + menu.winfo_reqheight() >= height:
			#	menu.delete("end")
			#	x = x + menu.winfo_reqwidth()
			#	y = 0
			#	menu = Menu(font=menufont, bd=1, tearoff=0)
			#	self.compmenus.append(menu)
			#else:
			#keys = keys[1:]
			#if x + menu.winfo_reqwidth() > width:
				#menu.destroy()
				#self.compmenus = self.compmenus[:-1]
				#self.compmenus[-1].delete("end")
				#self.compmenus[-1].add_command(label="...")
				#break
		
		#x = self.text.winfo_rootx() + bbox[0] - 4
		#y = self.text.winfo_rooty() + bbox[1] + bbox[3]
		#for menu in self.compmenus:
		#	maxtop = height - menu.winfo_reqheight()
		#	if y > maxtop: y = maxtop
		#	menu.post(x, y)
		#	x = x + menu.winfo_reqwidth()
		self.text.focus()
		self.text.grab_set()
		#print keys
						
	def unpostmenus(self):
		"""Unpost the completion menus."""
		#for menu in self.compmenus:
		#	menu.destroy()
		#self.compmenus = []
		self.text.grab_release()

	def cb_cleanup(self, event=None):
		if self.compmenus:
			self.unpostmenus()
		if self.pasted:
			self.text.tag_remove("sel", "1.0", "end")
			self.pasted = 0

	def cb_select(self, event):
		"""Handle a menu selection event.  We have to check and invoke the
		completion menus manually because we are grabbing events to give the
		text box keyboard focus."""
		if self.compmenus:
			for menu in self.compmenus:
				x, y = menu.winfo_rootx(), menu.winfo_rooty()
				w, h = menu.winfo_width(), menu.winfo_height()
				if x < event.x_root < x + w and \
				y < event.y_root < y + h:
					item = menu.index("@%d" % (event.y_root - y))
					menu.invoke(item)
					break
			else:
				self.unpostmenus()
			return "break"

	# Help mechanism.

	def cb_help(self, event):
		command, context, ident, start, end = self.precontext()
		word = self.text.get(start, end)

		object = parent = doc = None
		skip = 0

		try:
			parent = eval(context[:-1])
		except: pass

		# Go merrily searching for the help string.
		if not object:
			try:
				object = getattr(parent, word)
				skip = len(word) - len(ident)
			except: pass

		if not object:
			try:
				object = getattr(parent, ident)
			except: pass

		if not object:
			try:
				object = self.dict[word]
				skip = len(word) - len(ident)
			except: pass

		if not object:
			try:
				object = self.dict[ident]
			except: pass

		if not object:
			try:
				object = __builtin__.__dict__[word]
				skip = len(word) - len(ident)
			except: pass

		if not object:
			try:
				object = __builtins__.__dict__[ident]
			except: pass

		if not object:
			if not ident:
				object = parent

		try:
			doc = object.__doc__
		except: pass

		try:
			if hasattr(object, "__bases__"):
				doc = object.__init__.__doc__ or doc
		except: pass

		if doc:
			doc = string.rstrip(string.expandtabs(doc))
			leftmargin = 99
			for line in string.split(doc, "\n")[1:]:
				spaces = len(line) - len(string.lstrip(line))
				if line and spaces < leftmargin: leftmargin = spaces

			bbox = self.text.bbox("insert + %d c" % skip)
			width = self.winfo_screenwidth()
			height = self.winfo_screenheight()
			menufont = self.text.cget("font")

			help = Menu(font=menufont, bd=1, tearoff=0)
			try:
				classname = object.__class__.__name__
				help.add_command(label="<object of class %s>" % classname)
				help.add_command(label="")
			except: pass
			for line in string.split(doc, "\n"):
				if string.strip(line[:leftmargin]) == "":
					line = line[leftmargin:]
				help.add_command(label=line)
			self.compmenus.append(help)

			x = self.text.winfo_rootx() + bbox[0] - 4
			y = self.text.winfo_rooty() + bbox[1] + bbox[3]
			maxtop = height - help.winfo_reqheight()
			if y > maxtop: y = maxtop
			help.post(x, y)
			self.text.focus()
			self.text.grab_set()

		return "break"

	# Entering commands.

	def cb_position(self, event):
		"""Avoid moving into the prompt area."""
		self.cb_cleanup()
		line, pos = self.cursor()
		trimmed, command = self.trim(self.getline())
		if pos <= trimmed:
			self.text.mark_set("insert", "%d.%d" % (line, trimmed))
			return "break"

	def cb_backspace(self, event):
		self.cb_cleanup()
		if self.text.tag_ranges("sel"): return

		# Avoid backspacing over the prompt.
		line, pos = self.cursor()
		trimmed, command = self.trim(self.getline())
		if pos <= trimmed: return "break"

		# Extremely basic outdenting.  Needs more work here.
		if not string.strip(command[:pos-trimmed]):
			step = (pos - trimmed) % 4
			cut = pos - (step or 4)
			if cut < trimmed: cut = trimmed
			self.text.delete("%d.%d" % (line, cut), "%d.%d" % (line, pos))
			return "break"

	def cb_space(self, event):
		self.cb_cleanup()
		line, pos = self.cursor()
		trimmed, command = self.trim(self.getline())

		# Extremely basic indenting.  Needs more work here.
		if not string.strip(command[:pos-trimmed]):
			start = trimmed + len(command) - len(string.lstrip(command))
			self.text.delete("insert", "%d.%d" % (line, start))
			step = 4 - (pos - trimmed) % 4
			self.text.insert("insert", " " * step)
			return "break"

	def cb_home(self, event):
		"""Go to the first non-whitespace character in the line."""
		self.cb_cleanup()
		line, pos = self.cursor()
		trimmed, command = self.trim(self.getline())
		indent = len(command) - len(string.lstrip(command))
		self.text.mark_set("insert", "%d.%d" % (line, trimmed + indent))
		return "break"

	def cb_ctrlhome(self, event):
		"""Go to the beginning of the line just after the prompt."""
		self.cb_cleanup()
		line, pos = self.cursor()
		trimmed, command = self.trim(self.getline())
		self.text.mark_set("insert", "%d.%d" % (line, trimmed))
		return "break"

	def cb_nothing(self, event):
		return "break"

	def cb_return(self, event, doindent=1):
		"""Handle a <Return> keystroke by running from the current line
		and generating a new prompt."""
		self.cb_cleanup()
		self.text.tag_delete("compiled")
		self.historyindex = None
		command = self.getline(trim=1)
		if string.strip(command):
			self.history.append(command)

		line, pos = self.cursor()
		self.text.mark_set("insert", "%d.end" % line)
		self.text.insert("insert", "\n")
		self.runline(line)

		line, pos = self.cursor()
		self.text.mark_set("insert", "%d.end" % line)
		prompt = self.continuation and sys.ps2 or sys.ps1
		if pos > 0:
			self.text.insert("insert", "\n" + prompt)
		else:
			self.text.insert("insert", prompt)

		if doindent and not self.error:
			self.autoindent(command)
		self.error = 0
		self.text.see("insert")
		return "break"

	def autoindent(self, command):
		# Extremely basic autoindenting.  Needs more work here.
		indent = len(command) - len(string.lstrip(command))
		if string.lstrip(command):
			self.text.insert("insert", command[:indent])
			if string.rstrip(command)[-1] == ":":
				self.text.insert("insert", "    ")

	def cb_paste(self, event):
		"""Handle a paste event (middle-click) in the text box.  Pasted
		text has any leading Python prompts stripped (at last!!)."""
		self.text.tag_delete("compiled")
		self.error = 0
		self.pasted = 1

		try: lines = string.split(self.selection_get(), "\n")
		except: return

		for i in range(len(lines)):
			trimmed, line = self.trim(lines[i])
			line = string.rstrip(line)
			if not line: continue

			self.text.insert("end", line)
			self.text.mark_set("insert", "end")
			if i == len(lines) - 2 and lines[i+1] == "":
				# Indent the last line if it's blank.
				self.cb_return(None, doindent=1)
			elif i < len(lines) - 1:
				self.cb_return(None, doindent=0)

			if self.error: break

		return "break"

	# Executing commands.

	def runline(self, line):
		"""Run some source code given the number of the last line in the
		text box.  Scan backwards to get the entire piece of code to run
		if the line is a continuation of previous lines.  Tag the compiled
		code so that it can be highlighted according to whether it is
		complete, incomplete, or illegal."""
		lastline = line
		lines = [self.getline(line)]
		while lines[0][:len(sys.ps2)] == sys.ps2:
			trimmed, lines[0] = self.trim(lines[0])
			self.text.tag_add(
				"compiled", "%d.%d" % (line, trimmed), "%d.0" % (line+1))
			line = line - 1
			if line < 0: break
			lines[:0] = [self.getline(line)]
		if lines[0][:len(sys.ps1)] == sys.ps1:
			trimmed, lines[0] = self.trim(lines[0])
			self.text.tag_add(
				"compiled", "%d.%d" % (line, trimmed), "%d.0" % (line+1))
		else:
			self.text.tag_add("compiled", "%d.0" % line, "%d.0" % (line+1))

		source = string.join(lines, "\n")
		if not source:
			self.continuation = 0
			return
			
		status, code = self.compile(source)

		if status == "more":
			self.text.tag_configure("compiled", background=self["morecolour"])
			self.continuation = 1

		elif status == "bad":
			self.text.tag_configure("compiled", background=self["badcolour"])
			self.error = 1
			self.continuation = 0
			self.intraceback = 1
			oldout, olderr = sys.stdout, sys.stderr
			sys.stdout, sys.stderr = self.stdout, self.stderr
			traceback.print_exception(SyntaxError, code, None)
			self.stdout, self.stderr = sys.stdout, sys.stderr
			sys.stdout, sys.stderr = oldout, olderr
			self.intraceback = 0

		elif status == "okay":
			if self.getline(lastline) == sys.ps2:
				self.text.tag_remove("compiled", "%d.0" % lastline, "end")
			self.text.tag_configure("compiled", background=self["runcolour"])
			self.continuation = 0
			self.run(code)

	def compile(self, source):
		"""Try to compile a piece of source code, returning a status code
		and the compiled result.  If the status code is "okay" the code is
		complete and compiled successfully; if it is "more" then the code
		can be compiled, but an interactive session should wait for more
		input; if it is "bad" then there is a syntax error in the code and
		the second returned value is the error message."""
		err = err1 = err2 = None
		code = code1 = code2 = None

		try:
			code = compile(source, "<console>", "single")
		except SyntaxError, err:
			pass
		else:
			return "okay", code

		try:
			code1 = compile(source + "\n", "<console>", "single")
		except SyntaxError, err1:
			pass
		else:
			return "more", code1

		try:
			code2 = compile(source + "\n\n", "<console>", "single")
		except SyntaxError, err2:
			pass

		try:
			code3 = compile(source + "\n", "<console>", "exec")
		except SyntaxError, err3:
			pass
		else:
			return "okay", code3

		try:
			code4 = compile(source + "\n\n", "<console>", "exec")
		except SyntaxError, err4:
			pass

		if err3[1][2] != err4[1][2]:
			return "more", None

		if err1[1][2] != err2[1][2]:
			return "more", None

		return "bad", err1

	def run(self, code):
		"""Run a code object within the sandbox for this console.  The
		sandbox redirects stdout and stderr to the console, and executes
		within the namespace associated with the console."""
		oldout, olderr = sys.stdout, sys.stderr
		sys.stdout, sys.stderr = self.stdout, self.stderr
		
		##block panda main thread until code is executed
		
		event1.clear()
		event2.wait()
		try:
			exec code in self.dict
		except:
			self.error = 1
			sys.last_type = sys.exc_type
			sys.last_value = sys.exc_value
			sys.last_traceback = sys.exc_traceback.tb_next
			self.intraceback = 1
			traceback.print_exception(
				sys.last_type, sys.last_value, sys.last_traceback)
			self.intraceback = 0
		
		##release panda main thread after code is executed
		event1.set()	
		
		self.stdout, self.stderr = sys.stdout, sys.stderr
		sys.stdout, sys.stderr = oldout, olderr


# Helpers for the completion mechanism.

def scanclass(klass, result):
	for key in klass.__dict__.keys(): result[key] = 1
	for base in klass.__bases__: scanclass(base, result)

def members(object):
	result = {}
	try:
		for key in object.__members__: result[key] = 1
		result["__members__"] = 1
	except: pass
	try:
		for key in object.__methods__: result[key] = 1
		result["__methods__"] = 1
	except: pass
	try:
		for key in object.__dict__.keys(): result[key] = 1
		result["__dict__"] = 1
	except: pass
	try:
		for key in dir(object): result[key] = 1
		result["__methods__"] = 1
	except: pass
	if type(object) is types.ClassType:
		scanclass(object, result)
		result["__name__"] = 1
		result["__bases__"] = 1
	if type(object) is types.InstanceType:
		scanclass(object.__class__, result)
		result["__class__"] = 1
	return result.keys()

def matchingkeys(keys, prefix):
	prefixmatch = lambda key, l=len(prefix), p=prefix: key[:l] == p
	return filter(prefixmatch, keys)

def commonprefix(keys):
	if not keys: return ''
	max = len(keys[0])
	prefixes = map(lambda i, key=keys[0]: key[:i], range(max+1))
	for key in keys:
		while key[:max] != prefixes[max]:
			max = max - 1
			if max == 0: return ''
	return prefixes[max]

callabletypes = [types.FunctionType, types.MethodType, types.ClassType,
				types.BuiltinFunctionType, types.BuiltinMethodType]
sequencetypes = [types.TupleType, types.ListType]
mappingtypes = [types.DictType]

try:
	import ExtensionClass
	callabletypes.append(ExtensionClass.ExtensionClassType)
except: pass
try:
	import curve
	c = curve.Curve()
	callabletypes.append(type(c.read))
except: pass

def finisher(object):
	if type(object) in callabletypes:
		return "("
	elif type(object) in sequencetypes:
		return "["
	elif type(object) in mappingtypes:
		return "{"
	elif members(object):
		return "."
	return " "


# Main program.
import thread

def ConsoleThread(dictX):
	c = Console(dict=dictX)
	c.dict["console"] = c
	c.pack(fill=BOTH, expand=1)
	c.master.title("Python Console v%s" % VERSION)
	mainloop()
		

def ExectutionThreadPanda():
	event2.set()
	event1.wait()
	event2.clear()
	event1.wait()
	return Task.again
		
def RunConsole(dictX):
	thread.start_new_thread(ConsoleThread,(dictX,))
	taskMgr.doMethodLater(0.03, ExectutionThreadPanda, 'Tkinter Window', extraArgs = [])

The main module’s global is :

sys.modules['__main__'].__dict__