console.py

Cesar Martinez Izquierdo, 09/06/2016 05:04 PM

Download (14.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.promptPosition = 0
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_BACK_SPACE, 0, "jython.backspace", self.backspace),
63
            (KeyEvent.VK_HOME, 0, "jython.home", self.home),
64
            (KeyEvent.VK_UP, 0, "jython.up", self.history.historyUp),
65
            (KeyEvent.VK_DOWN, 0, "jython.down", self.history.historyDown),
66
            (KeyEvent.VK_PERIOD, 0, "jython.showPopup", self.showPopup),
67
            (KeyEvent.VK_ESCAPE, 0, "jython.hide", self.hide),
68
            
69
            ('(', 0, "jython.showTip", self.showTip),
70
            (')', 0, "jython.hideTip", self.hideTip),
71
            
72
            #(KeyEvent.VK_UP, InputEvent.CTRL_MASK, DefaultEditorKit.upAction, self.output.keymap.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0))),
73
            #(KeyEvent.VK_DOWN, InputEvent.CTRL_MASK, DefaultEditorKit.downAction, self.output.keymap.getAction(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)))
74
            ]
75
        # TODO rename newmap to keymap
76
        newmap = JTextComponent.addKeymap("jython", self.output.keymap)
77
        for (key, modifier, name, function) in keyBindings:
78
            newmap.addActionForKeyStroke(KeyStroke.getKeyStroke(key, modifier), ActionDelegator(name, function))
79

    
80
        self.output.keymap = newmap
81
                
82
        self.doc = self.output.document
83
        #self.panel.add(BorderLayout.CENTER, JScrollPane(self.output))
84
        self.__propertiesChanged()
85
        self.__inittext()
86
        self.initialLocation = self.doc.createPosition(self.doc.length-1)
87

    
88
        # Don't pass frame to popups. JWindows with null owners are not focusable
89
        # this fixes the focus problem on Win32, but make the mouse problem worse
90
        self.popup = Popup(None, self.output)
91
        self.tip = Tip(None)
92

    
93
        # get fontmetrics info so we can position the popup
94
        metrics = self.output.getFontMetrics(self.output.getFont())
95
        self.dotWidth = metrics.charWidth('.')
96
        self.textHeight = metrics.getHeight()
97

    
98
        # add some handles to our objects
99
        self.locals['console'] = self
100

    
101
        self.caret = self.output.getCaret()
102

    
103
    # TODO refactor me
104
    def getinput(self):
105
        offsets = self.__lastLine()
106
        text = self.doc.getText(offsets[0], offsets[1]-offsets[0])
107
        return text
108

    
109
    def getDisplayPoint(self):
110
        """Get the point where the popup window should be displayed"""
111
        screenPoint = self.output.getLocationOnScreen()
112
        caretPoint = self.output.caret.getMagicCaretPosition()
113

    
114
        # TODO use SwingUtils to do this translation
115
        x = screenPoint.getX() + caretPoint.getX() + self.dotWidth 
116
        y = screenPoint.getY() + caretPoint.getY() + self.textHeight
117
        return Point(int(x),int(y))
118

    
119
    def hide(self, event=None):
120
        """Hide the popup or tip window if visible"""
121
        if self.popup.visible:
122
            self.popup.hide()
123
        if self.tip.visible:
124
            self.tip.hide()
125

    
126
    def hideTip(self, event=None):
127
        self.tip.hide()
128
        # TODO this needs to insert ')' at caret!
129
        self.write(')')
130

    
131
    def showTip(self, event=None):
132
        # get the display point before writing text
133
        # otherwise magicCaretPosition is None
134
        displayPoint = self.getDisplayPoint()
135

    
136
        if self.popup.visible:
137
            self.popup.hide()
138
        
139
        line = self.getinput()
140
        #debug("line", line)
141
        # Hack 'o rama
142
        line = line[:-1] # remove \n
143
        line += '('
144
        #debug("line", line)
145

    
146
        # TODO this needs to insert '(' at caret!
147
        self.write('(')
148
        
149
        (name, argspec, tip) = jintrospect.getCallTipJava(line, self.locals)
150
        #debug("name", name)
151
        #debug("argspec", argspec)
152
        #debug("tip", tip)
153

    
154
        if tip:
155
            self.tip.setLocation(displayPoint)
156
            self.tip.setText(tip)
157
            self.tip.show()
158
            
159

    
160
    def showPopup(self, event=None):
161

    
162
        line = self.getinput()
163
        # this is silly, I have to add the '.' and the other code removes it.
164
        line = line[:-1] # remove \n
165
        line = line + '.'
166
        #print >> sys.stderr, "line:",line
167
        
168
        # TODO get this code into Popup
169
        # TODO handle errors gracefully
170
        try:
171
            list = jintrospect.getAutoCompleteList(line, self.locals)
172
        except Exception, e:
173
            # TODO handle this gracefully
174
            print >> sys.stderr, e
175
            return
176

    
177
        if len(list) == 0:
178
            #print >> sys.stderr, "list was empty"
179
            return
180

    
181
        self.popup.setLocation(self.getDisplayPoint())
182

    
183
        self.popup.setMethods(list)
184
        self.popup.show()
185
        self.popup.list.setSelectedIndex(0)
186

    
187
    def beyondPrompt(self, considerCurrent=True, considerSelection=False):
188
        """Determines wheter the cursor is in the editable area
189
           (i.e. beyond the last prompt position)"""
190
        caret = self.output.caretPosition
191
        caret0 = caret
192
        if considerCurrent:
193
            caret = caret + 1
194
        if considerSelection:
195
            if self.output.selectedText:
196
                caret = self.output.selectionEnd
197
        if caret > self.promptPosition:
198
            return True
199
        return False
200

    
201
    def inLastLine(self, include = 1):
202
        """ Determines whether the cursor is in the last line """
203
        limits = self.__lastLine()
204
        caret = self.output.caretPosition
205
        if self.output.selectedText:
206
            caret = self.output.selectionStart
207
        if include:
208
            return (caret >= limits[0] and caret <= limits[1])
209
        else:
210
            return (caret > limits[0] and caret <= limits[1])
211

    
212
    def enter(self, event):
213
        """ Triggered when enter is pressed """
214
        offsets = self.__lastLine()
215
        text = self.doc.getText(offsets[0], offsets[1]-offsets[0])
216
        text = text[:-1] # chomp \n
217
        self.buffer.append(text)
218
        source = "\n".join(self.buffer)
219
        more = self.interp.runsource(source)
220
        if more:
221
            self.printOnProcess()
222
        else:
223
            self.resetbuffer()
224
            self.printPrompt()
225
        self.history.append(text)
226

    
227
        self.hide()
228

    
229
    def resetbuffer(self):
230
        self.buffer = []
231

    
232
    # home key stops after prompt
233
    def home(self, event):
234
                """ Triggered when HOME is pressed """
235
                if self.inLastLine():
236
                        self.output.caretPosition = self.__lastLine()[0]
237
                else:
238
                        lines = self.doc.rootElements[0].elementCount
239
                        for i in xrange(0,lines-1):
240
                                offsets = (self.doc.rootElements[0].getElement(i).startOffset, \
241
                                        self.doc.rootElements[0].getElement(i).endOffset)
242
                                line = self.doc.getText(offsets[0], offsets[1]-offsets[0])
243
                                if self.output.caretPosition >= offsets[0] and \
244
                                        self.output.caretPosition <= offsets[1]:
245
                                        if line.startswith(Console.PROMPT) or line.startswith(Console.PROCESS):
246
                                                self.output.caretPosition = offsets[0] + len(Console.PROMPT)
247
                                        else:
248
                                                self.output.caretPosition = offsets[0]
249

    
250
    def replaceRow(self, text):
251
        """ Replaces the last line of the textarea with text """
252
        offset = self.__lastLine()
253
        last = self.doc.getText(offset[0], offset[1]-offset[0])
254
        if last != "\n":
255
            self.doc.remove(offset[0], offset[1]-offset[0]-1)
256
        self.__addOutput(self.infoColor, text)
257

    
258
    def __do_delete(self, event, pos=0):
259
        if self.output.selectedText:
260
            start = max(self.output.selectionStart, self.promptPosition)
261
            self.doc.remove(start, self.output.selectionEnd - start)
262
        else:
263
            self.doc.remove(self.output.caretPosition + pos, 1)
264

    
265
    # don't allow prompt to be deleted
266
    def delete(self, event):
267
        """ Intercepts backspace events only allowing it to work in the last line """
268
        if self.beyondPrompt(considerCurrent=True, considerSelection=True):
269
            self.__do_delete(event)
270

    
271

    
272
    # don't allow prompt to be deleted
273
    def backspace(self, event):
274
        """ Intercepts backspace events only allowing it to work in the last line """
275
        if self.beyondPrompt(considerCurrent=False, considerSelection=True):
276
            self.__do_delete(event, -1)
277

    
278
    # why is there a keyTyped and a keyPressed?
279
    def keyTyped(self, event):
280
        if not self.beyondPrompt():
281
            self.output.setCaretPosition(self.doc.length)
282

    
283
    def keyPressed(self, event):
284
        # skip Shift + delete and shift + backspace as the are incorrectly managed
285
        if event.keyCode == KeyEvent.VK_BACK_SPACE or event.keyCode == KeyEvent.VK_DELETE:
286
            if event.modifiers > 0:
287
                event.consume()
288

    
289
        if self.popup.visible:
290
            self.popup.key(event)
291

    
292

    
293
    # TODO refactor me
294
    def write(self, text):
295
        self.__addOutput(self.infoColor, text)
296

    
297
    def printResult(self, msg):
298
        """ Prints the results of an operation """
299
        self.__addOutput(self.output.foreground, msg, True)
300

    
301
    def printError(self, msg): 
302
        self.__addOutput(self.errorColor, msg, True)
303

    
304
    def printOnProcess(self):
305
        """ Prints the process symbol """
306
        self.__addOutput(self.infoColor, Console.PROCESS, True)
307

    
308
    def printPrompt(self):
309
        """ Prints the prompt """
310
        self.__addOutput(self.infoColor, Console.PROMPT, True)
311
        self.promptPosition = self.doc.length
312
                
313
    def __addOutput(self, color, msg, new_line=False):
314
        """ Adds the output to the text area using a given color """
315
        if new_line:
316
            if isinstance(msg, unicode):
317
                msg =  u"\n" + msg
318
            else:
319
                msg = "\n" + str(msg)
320
        from javax.swing.text import BadLocationException
321
        style = SimpleAttributeSet()
322

    
323
        if color:
324
            style.addAttribute(StyleConstants.Foreground, color)
325

    
326
        self.doc.insertString(self.doc.length, msg, style)
327
        self.output.caretPosition = self.doc.length
328

    
329
    def __propertiesChanged(self):
330
        """ Detects when the properties have changed """
331
        self.output.background = Color.white #jEdit.getColorProperty("jython.bgColor")
332
        self.output.foreground = Color.blue #jEdit.getColorProperty("jython.resultColor")
333
        self.infoColor = Color.black #jEdit.getColorProperty("jython.textColor")
334
        self.errorColor = Color.red # jEdit.getColorProperty("jython.errorColor")
335

    
336
        family = "Monospaced" # jEdit.getProperty("jython.font", "Monospaced")
337
        size = 14 #jEdit.getIntegerProperty("jython.fontsize", 14)
338
        style = Font.PLAIN #jEdit.getIntegerProperty("jython.fontstyle", Font.PLAIN)
339
        self.output.setFont(Font(family,style,size))
340

    
341
    def __inittext(self):
342
        """ Inserts the initial text with the jython banner """
343
        self.doc.remove(0, self.doc.length)
344
        for line in "\n".join(Console.BANNER):
345
            self.__addOutput(self.infoColor, line)
346
        self.printPrompt()
347
        self.output.requestFocus()
348

    
349
    def __lastLine(self):
350
        """ Returns the char offests of the last line """
351
        lines = self.doc.rootElements[0].elementCount
352
        offsets = (self.doc.rootElements[0].getElement(lines-1).startOffset, \
353
                   self.doc.rootElements[0].getElement(lines-1).endOffset)
354
        line = self.doc.getText(offsets[0], offsets[1]-offsets[0])
355
        if len(line) >= 4 and (line[0:4]==Console.PROMPT or line[0:4]==Console.PROCESS):
356
            return (offsets[0] + len(Console.PROMPT), offsets[1])
357
        return offsets
358

    
359

    
360
class ActionDelegator(TextAction):
361
        """
362
                Class action delegator encapsulates a TextAction delegating the action
363
                event to a simple function
364
        """
365
        def __init__(self, name, delegate):
366
                TextAction.__init__(self, name)
367
                self.delegate = delegate
368

    
369
        def actionPerformed(self, event):
370
                if isinstance(self.delegate, Action):
371
                        self.delegate.actionPerformed(event)
372
                else:
373
                        self.delegate(event)
374

    
375
class Interpreter(InteractiveInterpreter):
376
    def __init__(self, console, locals):
377
        InteractiveInterpreter.__init__(self, locals)
378
        self.console = console
379
        
380
        
381
    def write(self, data):
382
        # send all output to the textpane
383
        # KLUDGE remove trailing linefeed
384
        self.console.printError(data[:-1])
385
        
386

    
387
# redirect stdout to the textpane
388
class StdOutRedirector:
389
    def __init__(self, console):
390
        self.console = console
391
        
392
    def write(self, data):
393
        #print >> sys.stderr, ">>%s<<" % data
394
        if data != '\n':
395
            # This is a sucky hack.  Fix printResult
396
            self.console.printResult(data)
397

    
398
class JythonFrame(JFrame):
399
    def __init__(self):
400
        self.title = "Jython"
401
        self.size = (600, 400)
402
        self.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
403

    
404

    
405
def main():
406
    frame = JythonFrame()
407
    console = Console(frame)
408
    frame.getContentPane().add(JScrollPane(console.output))
409
    frame.show()
410
    
411
if __name__ == "__main__":
412
    main()