Statistics
| Revision:

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()