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