root / trunk / extensions / extScripting / scripts / jython / console / console.py @ 11445
History | View | Annotate | Download (13.9 KB)
1 | 5782 | jmvivo | """
|
---|---|---|---|
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 | 5910 | jmvivo | #from org.python.util import InteractiveConsole
|
21 | InteractiveConsole=sys.gvSIG.classForName("org.python.util.InteractiveConsole")
|
||
22 | 5782 | jmvivo | |
23 | __author__ = "Don Coleman <dcoleman@chariotsolutions.com>"
|
||
24 | __cvsid__ = "$Id$"
|
||
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 | 5910 | jmvivo | self.locals = {"gvSIG":sys.gvSIG} |
47 | 5782 | jmvivo | |
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 | 5910 | jmvivo | main() |