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