Home | History | Annotate | Download | only in functional
      1 """This is a substantially improved version of the older Interpreter.py demo
      2 It creates a simple GUI JPython console window with simple history
      3 as well as the ability to interupt running code (with the ESC key).
      4 
      5 Like Interpreter.py, this is still just a demo, and needs substantial
      6 work before serious use.
      7 
      8 Thanks to Geza Groma (groma (a] everx.szbk.u-szeged.hu) for several valuable
      9 ideas for this tool -- his JPConsole is a more refined implementation
     10 of similar ideas.
     11 """
     12 
     13 from Styles import Styles
     14 from Keymap import Keymap
     15 
     16 from pawt import swing, colors
     17 from java.awt.event.KeyEvent import VK_UP, VK_DOWN
     18 from java.awt.event import ActionEvent
     19 from java.lang import Thread, System
     20 from code import compile_command
     21 import string, sys, re
     22 
     23 class OutputBuffer:
     24 	def __init__(self, console, stylename):
     25 		self.console = console
     26 		self.stylename = stylename
     27 		
     28 	def flush(self):
     29 		pass
     30 		
     31 	def write(self, text):
     32 		self.console.write(text, self.stylename)
     33 
     34 class Console:
     35 	def __init__(self, styles=None, keymap=None):
     36 		if styles is None:
     37 			styles = Styles()
     38 			basic = styles.add('normal', tabsize=3, fontSize=12, fontFamily="Courier")
     39 			styles.add('error', parent=basic, foreground=colors.red)
     40 			styles.add('output', parent=basic, foreground=colors.blue)
     41 			styles.add('input', parent=basic, foreground=colors.black)
     42 			styles.add('prompt', parent=basic, foreground=colors.purple)
     43 		self.styles = styles
     44 		
     45 		# This is a hack to get at an inner class
     46 		# This will not be required in JPython-1.1
     47 		ForegroundAction = getattr(swing.text, 'StyledEditorKit$ForegroundAction')
     48 		self.inputAction = ForegroundAction("start input", colors.black)
     49 
     50 		if keymap is None:
     51 			keymap = Keymap()
     52 		keymap.bind('enter', self.enter)
     53 		keymap.bind('tab', self.tab)
     54 		keymap.bind('escape', self.escape)
     55 		keymap.bind('up', self.uphistory)
     56 		keymap.bind('down', self.downhistory)
     57 		
     58 		self.keymap = keymap
     59 		
     60 		self.document = swing.text.DefaultStyledDocument(self.styles)
     61 		self.document.setLogicalStyle(0, self.styles.get('normal'))
     62 
     63 		self.textpane = swing.JTextPane(self.document)
     64 		self.textpane.keymap = self.keymap
     65 		
     66 		self.history = []
     67 		self.oldHistoryLength = 0
     68 		self.historyPosition = 0
     69 		
     70 		self.command = []
     71 		self.locals = {}
     72 
     73 	def write(self, text, stylename='normal'):
     74 		style = self.styles.get(stylename)
     75 		self.document.insertString(self.document.length, text, style)
     76 		
     77 	def beep(self):
     78 		self.textpane.toolkit.beep()
     79 
     80 	def startUserInput(self, prompt=None):
     81 		if prompt is not None:
     82 			self.write(prompt, 'prompt')
     83 		self.startInput = self.document.createPosition(self.document.length-1)
     84 		#self.document.setCharacterAttributes(self.document.length-1, 1, self.styles.get('input'), 1)
     85 		self.textpane.caretPosition = self.document.length
     86 		ae = ActionEvent(self.textpane, ActionEvent.ACTION_PERFORMED, 'start input')
     87 		self.inputAction.actionPerformed(ae)
     88 
     89 	def getinput(self):
     90 		offset = self.startInput.offset
     91 		line = self.document.getText(offset+1, self.document.length-offset)
     92 		return string.rstrip(line)
     93 
     94 	def replaceinput(self, text):
     95 		offset = self.startInput.offset + 1
     96 		self.document.remove(offset, self.document.length-offset)
     97 		self.write(text, 'input')
     98 		
     99 	def enter(self):
    100 		line = self.getinput()
    101 		self.write('\n', 'input')
    102 		
    103 		self.history.append(line)
    104 		self.handleLine(line)
    105 		
    106 	def gethistory(self, direction):
    107 		historyLength = len(self.history)
    108 		if self.oldHistoryLength < historyLength:
    109 			# new line was entered after last call
    110 			self.oldHistoryLength = historyLength
    111 			if self.history[self.historyPosition] != self.history[-1]:
    112 				self.historyPosition = historyLength
    113 
    114 		pos = self.historyPosition + direction
    115 
    116 		if 0 <= pos < historyLength:
    117 			self.historyPosition = pos
    118 			self.replaceinput(self.history[pos])
    119 		else:
    120 			self.beep()
    121 
    122 	def uphistory(self):
    123 		self.gethistory(-1)
    124 
    125 	def downhistory(self):
    126 		self.gethistory(1)
    127 
    128 	def tab(self):
    129 		self.write('\t', 'input')
    130 		
    131 	def escape(self):
    132 		if (not hasattr(self, 'pythonThread') or self.pythonThread is None or not self.pythonThread.alive):
    133 			self.beep()
    134 			return
    135 			
    136 		self.pythonThread.stopPython()
    137 
    138 	def capturePythonOutput(self, stdoutStyle='output', stderrStyle='error'):
    139 		import sys
    140 		sys.stdout = OutputBuffer(self, stdoutStyle)
    141 		sys.stderr = OutputBuffer(self, stderrStyle)
    142 
    143 	def handleLine(self, text):
    144 		self.command.append(text)
    145 		
    146 		try:
    147 			code = compile_command(string.join(self.command, '\n'))
    148 		except SyntaxError:
    149 			traceback.print_exc(0)
    150 			self.command = []
    151 			self.startUserInput(str(sys.ps1)+'\t')
    152 			return
    153 
    154 		if code is None:
    155 			self.startUserInput(str(sys.ps2)+'\t')
    156 			return
    157 		
    158 		self.command = []
    159 		
    160 		pt = PythonThread(code, self)
    161 		self.pythonThread = pt
    162 		pt.start()
    163 		
    164 	def newInput(self):
    165 		self.startUserInput(str(sys.ps1)+'\t')
    166 		
    167 import traceback
    168 
    169 class PythonThread(Thread):
    170 	def __init__(self, code, console):
    171 		self.code = code
    172 		self.console = console
    173 		self.locals = console.locals
    174 		
    175 	def run(self):
    176 		try:
    177 			exec self.code in self.locals
    178 			
    179 		#Include these lines to actually exit on a sys.exit() call
    180 		#except SystemExit, value:
    181 		#	raise SystemExit, value
    182 		
    183 		except:
    184 			exc_type, exc_value, exc_traceback = sys.exc_info()
    185 			l = len(traceback.extract_tb(sys.exc_traceback))
    186 			try:
    187 				1/0
    188 			except:
    189 				m = len(traceback.extract_tb(sys.exc_traceback))
    190 			traceback.print_exception(exc_type, exc_value, exc_traceback, l-m)
    191 			
    192 		self.console.newInput()
    193 
    194 	def stopPython(self):
    195 		#Should spend 2 seconds trying to kill thread in nice Python style first...
    196 		self.stop()
    197 
    198 header = """\
    199 JPython %(version)s on %(platform)s
    200 %(copyright)s
    201 """ % {'version':sys.version, 'platform':sys.platform, 'copyright':sys.copyright}
    202 
    203 if __name__ == '__main__':
    204 	c = Console()
    205 	pane = swing.JScrollPane(c.textpane)
    206 	swing.test(pane, size=(500,400), name='JPython Console')
    207 	c.write(header, 'output')
    208 	c.capturePythonOutput()
    209 	c.textpane.requestFocus()
    210 	c.newInput()
    211