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