Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting.app / trunk / org.gvsig.scripting.app / org.gvsig.scripting.app.extension / src / main / resources / scripting / lib / console / console.py @ 359

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 = {"gvSIG":sys.gvSIG}
47

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