gvsig-scripting / org.gvsig.scripting.app / trunk / org.gvsig.scripting.app / org.gvsig.scripting.app.extension / src / main / resources / scripting / lib / console / console.py @ 359
History | View | Annotate | Download (14 KB)
1 |
"""
|
---|---|
2 |
Jython Console with Code Completion
|
3 |
|
4 |
This uses the basic Jython Interactive Interpreter.
|
5 |
The UI uses code from Carlos Quiroz's 'Jython Interpreter for JEdit' http://www.jedit.org
|
6 |
"""
|
7 |
from javax.swing import JFrame, JScrollPane, JWindow, JTextPane, Action, KeyStroke, WindowConstants |
8 |
from javax.swing.text import JTextComponent, TextAction, SimpleAttributeSet, StyleConstants |
9 |
from java.awt import Color, Font, FontMetrics, Point |
10 |
from java.awt.event import InputEvent, KeyEvent, WindowAdapter |
11 |
|
12 |
import jintrospect |
13 |
from popup import Popup |
14 |
from tip import Tip |
15 |
from history import History |
16 |
|
17 |
import sys |
18 |
import traceback |
19 |
from code import InteractiveInterpreter |
20 |
#from org.python.util import InteractiveConsole
|
21 |
InteractiveConsole=sys.gvSIG.classForName("org.python.util.InteractiveConsole")
|
22 |
|
23 |
__author__ = "Don Coleman <dcoleman@chariotsolutions.com>"
|
24 |
__cvsid__ = "$Id: console.py 5910 2006-06-20 10:03:31Z jmvivo $"
|
25 |
|
26 |
def debug(name, value=None): |
27 |
if value == None: |
28 |
print >> sys.stderr, name
|
29 |
else:
|
30 |
print >> sys.stderr, "%s = %s" % (name, value) |
31 |
|
32 |
|
33 |
class Console: |
34 |
PROMPT = sys.ps1 |
35 |
PROCESS = sys.ps2 |
36 |
BANNER = ["Jython Completion Shell", InteractiveConsole.getDefaultBanner()]
|
37 |
|
38 |
def __init__(self, frame): |
39 |
|
40 |
self.frame = frame # TODO do I need a reference to frame after the constructor? |
41 |
self.history = History(self) |
42 |
self.bs = 0 # what is this? |
43 |
|
44 |
# command buffer
|
45 |
self.buffer = []
|
46 |
self.locals = {"gvSIG":sys.gvSIG} |
47 |
|
48 |
self.interp = Interpreter(self, self.locals) |
49 |
sys.stdout = StdOutRedirector(self)
|
50 |
|
51 |
# create a textpane
|
52 |
self.output = JTextPane(keyTyped = self.keyTyped, keyPressed = self.keyPressed) |
53 |
# TODO rename output to textpane
|
54 |
|
55 |
# CTRL UP AND DOWN don't work
|
56 |
keyBindings = [ |
57 |
(KeyEvent.VK_ENTER, 0, "jython.enter", self.enter), |
58 |
(KeyEvent.VK_DELETE, 0, "jython.delete", self.delete), |
59 |
(KeyEvent.VK_HOME, 0, "jython.home", self.home), |
60 |
(KeyEvent.VK_UP, 0, "jython.up", self.history.historyUp), |
61 |
(KeyEvent.VK_DOWN, 0, "jython.down", self.history.historyDown), |
62 |
(KeyEvent.VK_PERIOD, 0, "jython.showPopup", self.showPopup), |
63 |
(KeyEvent.VK_ESCAPE, 0, "jython.hide", self.hide), |
64 |
|
65 |
('(', 0, "jython.showTip", self.showTip), |
66 |
(')', 0, "jython.hideTip", self.hideTip), |
67 |
|
68 |
#(KeyEvent.VK_UP, InputEvent.CTRL_MASK, DefaultEditorKit.upAction, self.output.keymap.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0))),
|
69 |
#(KeyEvent.VK_DOWN, InputEvent.CTRL_MASK, DefaultEditorKit.downAction, self.output.keymap.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)))
|
70 |
] |
71 |
# TODO rename newmap to keymap
|
72 |
newmap = JTextComponent.addKeymap("jython", self.output.keymap) |
73 |
for (key, modifier, name, function) in keyBindings: |
74 |
newmap.addActionForKeyStroke(KeyStroke.getKeyStroke(key, modifier), ActionDelegator(name, function)) |
75 |
|
76 |
self.output.keymap = newmap
|
77 |
|
78 |
self.doc = self.output.document |
79 |
#self.panel.add(BorderLayout.CENTER, JScrollPane(self.output))
|
80 |
self.__propertiesChanged()
|
81 |
self.__inittext()
|
82 |
self.initialLocation = self.doc.createPosition(self.doc.length-1) |
83 |
|
84 |
# Don't pass frame to popups. JWindows with null owners are not focusable
|
85 |
# this fixes the focus problem on Win32, but make the mouse problem worse
|
86 |
self.popup = Popup(None, self.output) |
87 |
self.tip = Tip(None) |
88 |
|
89 |
# get fontmetrics info so we can position the popup
|
90 |
metrics = self.output.getFontMetrics(self.output.getFont()) |
91 |
self.dotWidth = metrics.charWidth('.') |
92 |
self.textHeight = metrics.getHeight()
|
93 |
|
94 |
# add some handles to our objects
|
95 |
self.locals['console'] = self |
96 |
|
97 |
self.caret = self.output.getCaret() |
98 |
|
99 |
# TODO refactor me
|
100 |
def getinput(self): |
101 |
offsets = self.__lastLine()
|
102 |
text = self.doc.getText(offsets[0], offsets[1]-offsets[0]) |
103 |
return text
|
104 |
|
105 |
def getDisplayPoint(self): |
106 |
"""Get the point where the popup window should be displayed"""
|
107 |
screenPoint = self.output.getLocationOnScreen()
|
108 |
caretPoint = self.output.caret.getMagicCaretPosition()
|
109 |
|
110 |
# TODO use SwingUtils to do this translation
|
111 |
x = screenPoint.getX() + caretPoint.getX() + self.dotWidth
|
112 |
y = screenPoint.getY() + caretPoint.getY() + self.textHeight
|
113 |
return Point(int(x),int(y)) |
114 |
|
115 |
def hide(self, event=None): |
116 |
"""Hide the popup or tip window if visible"""
|
117 |
if self.popup.visible: |
118 |
self.popup.hide()
|
119 |
if self.tip.visible: |
120 |
self.tip.hide()
|
121 |
|
122 |
def hideTip(self, event=None): |
123 |
self.tip.hide()
|
124 |
# TODO this needs to insert ')' at caret!
|
125 |
self.write(')') |
126 |
|
127 |
def showTip(self, event=None): |
128 |
# get the display point before writing text
|
129 |
# otherwise magicCaretPosition is None
|
130 |
displayPoint = self.getDisplayPoint()
|
131 |
|
132 |
if self.popup.visible: |
133 |
self.popup.hide()
|
134 |
|
135 |
line = self.getinput()
|
136 |
#debug("line", line)
|
137 |
# Hack 'o rama
|
138 |
line = line[:-1] # remove \n |
139 |
line += '('
|
140 |
#debug("line", line)
|
141 |
|
142 |
# TODO this needs to insert '(' at caret!
|
143 |
self.write('(') |
144 |
|
145 |
(name, argspec, tip) = jintrospect.getCallTipJava(line, self.locals)
|
146 |
#debug("name", name)
|
147 |
#debug("argspec", argspec)
|
148 |
#debug("tip", tip)
|
149 |
|
150 |
if tip:
|
151 |
self.tip.setLocation(displayPoint)
|
152 |
self.tip.setText(tip)
|
153 |
self.tip.show()
|
154 |
|
155 |
|
156 |
def showPopup(self, event=None): |
157 |
|
158 |
line = self.getinput()
|
159 |
# this is silly, I have to add the '.' and the other code removes it.
|
160 |
line = line[:-1] # remove \n |
161 |
line = line + '.'
|
162 |
#print >> sys.stderr, "line:",line
|
163 |
|
164 |
# TODO get this code into Popup
|
165 |
# TODO handle errors gracefully
|
166 |
try: |
167 |
list = jintrospect.getAutoCompleteList(line, self.locals)
|
168 |
except Exception, e: |
169 |
# TODO handle this gracefully
|
170 |
print >> sys.stderr, e
|
171 |
return
|
172 |
|
173 |
if len(list) == 0: |
174 |
#print >> sys.stderr, "list was empty"
|
175 |
return
|
176 |
|
177 |
self.popup.setLocation(self.getDisplayPoint()) |
178 |
|
179 |
self.popup.setMethods(list) |
180 |
self.popup.show()
|
181 |
self.popup.list.setSelectedIndex(0) |
182 |
|
183 |
def inLastLine(self, include = 1): |
184 |
""" Determines whether the cursor is in the last line """
|
185 |
limits = self.__lastLine()
|
186 |
caret = self.output.caretPosition
|
187 |
if self.output.selectedText: |
188 |
caret = self.output.selectionStart
|
189 |
if include:
|
190 |
return (caret >= limits[0] and caret <= limits[1]) |
191 |
else:
|
192 |
return (caret > limits[0] and caret <= limits[1]) |
193 |
|
194 |
def enter(self, event): |
195 |
""" Triggered when enter is pressed """
|
196 |
offsets = self.__lastLine()
|
197 |
text = self.doc.getText(offsets[0], offsets[1]-offsets[0]) |
198 |
text = text[:-1] # chomp \n |
199 |
self.buffer.append(text)
|
200 |
source = "\n".join(self.buffer) |
201 |
more = self.interp.runsource(source)
|
202 |
if more:
|
203 |
self.printOnProcess()
|
204 |
else:
|
205 |
self.resetbuffer()
|
206 |
self.printPrompt()
|
207 |
self.history.append(text)
|
208 |
|
209 |
self.hide()
|
210 |
|
211 |
def resetbuffer(self): |
212 |
self.buffer = []
|
213 |
|
214 |
# home key stops after prompt
|
215 |
def home(self, event): |
216 |
""" Triggered when HOME is pressed """
|
217 |
if self.inLastLine(): |
218 |
self.output.caretPosition = self.__lastLine()[0] |
219 |
else:
|
220 |
lines = self.doc.rootElements[0].elementCount |
221 |
for i in xrange(0,lines-1): |
222 |
offsets = (self.doc.rootElements[0].getElement(i).startOffset, \ |
223 |
self.doc.rootElements[0].getElement(i).endOffset) |
224 |
line = self.doc.getText(offsets[0], offsets[1]-offsets[0]) |
225 |
if self.output.caretPosition >= offsets[0] and \ |
226 |
self.output.caretPosition <= offsets[1]: |
227 |
if line.startswith(Console.PROMPT) or line.startswith(Console.PROCESS): |
228 |
self.output.caretPosition = offsets[0] + len(Console.PROMPT) |
229 |
else:
|
230 |
self.output.caretPosition = offsets[0] |
231 |
|
232 |
def replaceRow(self, text): |
233 |
""" Replaces the last line of the textarea with text """
|
234 |
offset = self.__lastLine()
|
235 |
last = self.doc.getText(offset[0], offset[1]-offset[0]) |
236 |
if last != "\n": |
237 |
self.doc.remove(offset[0], offset[1]-offset[0]-1) |
238 |
self.__addOutput(self.infoColor, text) |
239 |
|
240 |
# don't allow prompt to be deleted
|
241 |
# this will cause problems when history can contain multiple lines
|
242 |
def delete(self, event): |
243 |
""" Intercepts delete events only allowing it to work in the last line """
|
244 |
if self.inLastLine(): |
245 |
if self.output.selectedText: |
246 |
self.doc.remove(self.output.selectionStart, self.output.selectionEnd - self.output.selectionStart) |
247 |
elif self.output.caretPosition < self.doc.length: |
248 |
self.doc.remove(self.output.caretPosition, 1) |
249 |
|
250 |
# why is there a keyTyped and a keyPressed?
|
251 |
def keyTyped(self, event): |
252 |
#print >> sys.stderr, "keyTyped", event.getKeyCode()
|
253 |
if not self.inLastLine(): |
254 |
event.consume() |
255 |
if self.bs: |
256 |
event.consume() |
257 |
self.bs=0 |
258 |
|
259 |
def keyPressed(self, event): |
260 |
if self.popup.visible: |
261 |
self.popup.key(event)
|
262 |
#print >> sys.stderr, "keyPressed", event.getKeyCode()
|
263 |
if event.keyCode == KeyEvent.VK_BACK_SPACE:
|
264 |
offsets = self.__lastLine()
|
265 |
if not self.inLastLine(include=0): |
266 |
self.bs = 1 |
267 |
else:
|
268 |
self.bs = 0 |
269 |
|
270 |
# TODO refactor me
|
271 |
def write(self, text): |
272 |
self.__addOutput(self.infoColor, text) |
273 |
|
274 |
def printResult(self, msg): |
275 |
""" Prints the results of an operation """
|
276 |
self.__addOutput(self.output.foreground, "\n" + str(msg)) |
277 |
|
278 |
def printError(self, msg): |
279 |
self.__addOutput(self.errorColor, "\n" + str(msg)) |
280 |
|
281 |
def printOnProcess(self): |
282 |
""" Prints the process symbol """
|
283 |
self.__addOutput(self.infoColor, "\n" + Console.PROCESS) |
284 |
|
285 |
def printPrompt(self): |
286 |
""" Prints the prompt """
|
287 |
self.__addOutput(self.infoColor, "\n" + Console.PROMPT) |
288 |
|
289 |
def __addOutput(self, color, msg): |
290 |
""" Adds the output to the text area using a given color """
|
291 |
from javax.swing.text import BadLocationException |
292 |
style = SimpleAttributeSet() |
293 |
|
294 |
if color:
|
295 |
style.addAttribute(StyleConstants.Foreground, color) |
296 |
|
297 |
self.doc.insertString(self.doc.length, msg, style) |
298 |
self.output.caretPosition = self.doc.length |
299 |
|
300 |
def __propertiesChanged(self): |
301 |
""" Detects when the properties have changed """
|
302 |
self.output.background = Color.white #jEdit.getColorProperty("jython.bgColor") |
303 |
self.output.foreground = Color.blue #jEdit.getColorProperty("jython.resultColor") |
304 |
self.infoColor = Color.black #jEdit.getColorProperty("jython.textColor") |
305 |
self.errorColor = Color.red # jEdit.getColorProperty("jython.errorColor") |
306 |
|
307 |
family = "Monospaced" # jEdit.getProperty("jython.font", "Monospaced") |
308 |
size = 14 #jEdit.getIntegerProperty("jython.fontsize", 14) |
309 |
style = Font.PLAIN #jEdit.getIntegerProperty("jython.fontstyle", Font.PLAIN)
|
310 |
self.output.setFont(Font(family,style,size))
|
311 |
|
312 |
def __inittext(self): |
313 |
""" Inserts the initial text with the jython banner """
|
314 |
self.doc.remove(0, self.doc.length) |
315 |
for line in "\n".join(Console.BANNER): |
316 |
self.__addOutput(self.infoColor, line) |
317 |
self.printPrompt()
|
318 |
self.output.requestFocus()
|
319 |
|
320 |
def __lastLine(self): |
321 |
""" Returns the char offests of the last line """
|
322 |
lines = self.doc.rootElements[0].elementCount |
323 |
offsets = (self.doc.rootElements[0].getElement(lines-1).startOffset, \ |
324 |
self.doc.rootElements[0].getElement(lines-1).endOffset) |
325 |
line = self.doc.getText(offsets[0], offsets[1]-offsets[0]) |
326 |
if len(line) >= 4 and (line[0:4]==Console.PROMPT or line[0:4]==Console.PROCESS): |
327 |
return (offsets[0] + len(Console.PROMPT), offsets[1]) |
328 |
return offsets
|
329 |
|
330 |
|
331 |
class ActionDelegator(TextAction): |
332 |
"""
|
333 |
Class action delegator encapsulates a TextAction delegating the action
|
334 |
event to a simple function
|
335 |
"""
|
336 |
def __init__(self, name, delegate): |
337 |
TextAction.__init__(self, name)
|
338 |
self.delegate = delegate
|
339 |
|
340 |
def actionPerformed(self, event): |
341 |
if isinstance(self.delegate, Action): |
342 |
self.delegate.actionPerformed(event)
|
343 |
else:
|
344 |
self.delegate(event)
|
345 |
|
346 |
class Interpreter(InteractiveInterpreter): |
347 |
def __init__(self, console, locals): |
348 |
InteractiveInterpreter.__init__(self, locals) |
349 |
self.console = console
|
350 |
|
351 |
|
352 |
def write(self, data): |
353 |
# send all output to the textpane
|
354 |
# KLUDGE remove trailing linefeed
|
355 |
self.console.printError(data[:-1]) |
356 |
|
357 |
|
358 |
# redirect stdout to the textpane
|
359 |
class StdOutRedirector: |
360 |
def __init__(self, console): |
361 |
self.console = console
|
362 |
|
363 |
def write(self, data): |
364 |
#print >> sys.stderr, ">>%s<<" % data
|
365 |
if data != '\n': |
366 |
# This is a sucky hack. Fix printResult
|
367 |
self.console.printResult(data)
|
368 |
|
369 |
class JythonFrame(JFrame): |
370 |
def __init__(self): |
371 |
self.title = "Jython" |
372 |
self.size = (600, 400) |
373 |
try:
|
374 |
#No queremos que se salga cuando cerremos la ventana
|
375 |
##self.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
|
376 |
self.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
|
377 |
except:
|
378 |
# assume jdk < 1.4
|
379 |
self.addWindowListener(KillListener())
|
380 |
self.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
|
381 |
|
382 |
class KillListener(WindowAdapter): |
383 |
"""
|
384 |
Handle EXIT_ON_CLOSE for jdk < 1.4
|
385 |
Thanks to James Richards for this method
|
386 |
"""
|
387 |
def windowClosed(self, evt): |
388 |
import java.lang.System as System |
389 |
System.exit(0)
|
390 |
|
391 |
def main(): |
392 |
frame = JythonFrame() |
393 |
console = Console(frame) |
394 |
frame.getContentPane().add(JScrollPane(console.output)) |
395 |
frame.show() |
396 |
|
397 |
if __name__ == "__main__": |
398 |
main() |