gvsig-scripting / org.gvsig.scripting.app / trunk / org.gvsig.scripting.app / org.gvsig.scripting.app.extension / src / main / resources / scripting / lib / console / console.py @ 423
History | View | Annotate | Download (14 KB)
1 | 359 | jjdelcerro | """
|
---|---|---|---|
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 | 362 | vacevedo | from org.python.util import InteractiveConsole |
21 | #InteractiveConsole=sys.gvSIG.classForName("org.python.util.InteractiveConsole")
|
||
22 | 359 | jjdelcerro | |
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 | 362 | vacevedo | self.locals = {}
|
47 | #self.locals = {"gvSIG":sys.gvSIG}
|
||
48 | 359 | jjdelcerro | |
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() |