• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /macosx-10.10.1/pyobjc-45/pyobjc/pyobjc-core-2.5.1/Examples/NonFunctional/RemotePyInterpreter/
1import sys
2import traceback
3import sets
4import keyword
5import time
6from Foundation import *
7from AppKit import *
8from PyObjCTools import NibClassBuilder, AppHelper
9
10NibClassBuilder.extractClasses("RemotePyInterpreterDocument.nib")
11
12from AsyncPythonInterpreter import *
13from ConsoleReactor import *
14from netrepr import RemoteObjectReference
15
16def ensure_unicode(s):
17    if not isinstance(s, unicode):
18        s = unicode(s, 'utf-8', 'replace')
19    return s
20
21class RemotePyInterpreterReactor(NibClassBuilder.AutoBaseClass):
22    def handleExpectCommand_(self, command):
23        print command
24        seq = command[0]
25        name = command[1]
26        args = command[2:]
27        netrepr = self.netReprCenter.netrepr
28        rval = None
29        code = None
30        if name == 'RemoteConsole.raw_input':
31            prompt = ensure_unicode(args[0])
32            def input_received(line):
33                self.sendResult_sequence_(line, seq)
34            self.delegate.expectCodeInput_withPrompt_(input_received, prompt)
35        elif name == 'RemoteConsole.write':
36            args = [ensure_unicode(args[0]), u'code']
37            self.doCallback_sequence_args_(self.delegate.writeString_forOutput_, seq, args)
38        elif name == 'RemoteConsole.displayhook':
39            obj = args[0]
40            def displayhook_respond(reprobject):
41                self.delegate.writeString_forOutput_(ensure_unicode(reprobject) + u'\n', u'code')
42            def displayhook_local(obj):
43                if obj is not None:
44                    displayhook_respond(repr(obj))
45            if isinstance(obj, RemoteObjectReference):
46                self.deferCallback_sequence_value_(displayhook_respond, seq, 'repr(%s)' % (netrepr(obj),))
47            else:
48                self.doCallback_sequence_args_(displayhook_local, seq, args)
49        elif name.startswith('RemoteFileLike.'):
50            method = name[len('RemoteFileLike.'):]
51            if method == 'write':
52                style, msg = map(ensure_unicode, args)
53                args = [msg, style]
54                self.doCallback_sequence_args_(self.delegate.writeString_forOutput_, seq, args)
55
56            elif method == 'readline':
57                def input_received(line):
58                    self.sendResult_sequence_(line, seq)
59                self.delegate.expectCodeInput_withPrompt_(input_received, '')
60
61            else:
62                self.doCallback_sequence_args_(NSLog, seq, [u'%s does not respond to expect %s', self, command])
63        elif name == 'RemoteConsole.initialize':
64            def gotTitle(repr_versioninfo, executable, pid):
65                self.delegate.setVersion_executable_pid_(
66                    u'.'.join(map(unicode, self.netEval_(repr_versioninfo)[:3])),
67                    ensure_unicode(executable),
68                    pid,
69                )
70            self.doCallback_sequence_args_(gotTitle, seq, args)
71        #    fh = getattr(sys, args[0])
72        #    meth = getattr(fh, name[len('RemoteFileLike.'):])
73        #    self.doCallback_sequence_args_(meth, seq, args[1:])
74        else:
75            self.doCallback_sequence_args_(NSLog, seq, [u'%s does not respond to expect %s', self, command])
76
77    def close(self):
78        super(RemotePyInterpreterReactor, self).close()
79        self.delegate = None
80
81
82class PseudoUTF8Input(object):
83    softspace = 0
84    def __init__(self, readlinemethod):
85        self._buffer = u''
86        self._readline = readlinemethod
87
88    def read(self, chars=None):
89        if chars is None:
90            if self._buffer:
91                rval = self._buffer
92                self._buffer = u''
93                if rval.endswith(u'\r'):
94                    rval = rval[:-1]+u'\n'
95                return rval.encode('utf-8')
96            else:
97                return self._readline(u'\x04')[:-1].encode('utf-8')
98        else:
99            while len(self._buffer) < chars:
100                self._buffer += self._readline(u'\x04\r')
101                if self._buffer.endswith('\x04'):
102                    self._buffer = self._buffer[:-1]
103                    break
104            rval, self._buffer = self._buffer[:chars], self._buffer[chars:]
105            return rval.encode('utf-8').replace('\r','\n')
106
107    def readline(self):
108        if u'\r' not in self._buffer:
109            self._buffer += self._readline(u'\x04\r')
110        if self._buffer.endswith('\x04'):
111            rval = self._buffer[:-1].encode('utf-8')
112        elif self._buffer.endswith('\r'):
113            rval = self._buffer[:-1].encode('utf-8')+'\n'
114        self._buffer = u''
115
116        return rval
117
118
119DEBUG_DELEGATE = 0
120PASSTHROUGH = (
121   'deleteBackward:',
122   'complete:',
123   'moveRight:',
124   'moveLeft:',
125)
126
127class RemotePyInterpreterDocument(NibClassBuilder.AutoBaseClass):
128    """
129    PyInterpreter is a delegate/controller for a NSTextView,
130    turning it into a full featured interactive Python interpreter.
131    """
132
133    def expectCodeInput_withPrompt_(self, callback, prompt):
134        self.writeString_forOutput_(prompt, u'code')
135        self.setCharacterIndexForInput_(self.lengthOfTextView())
136        self.p_input_callbacks.append(callback)
137        self.flushCallbacks()
138
139    def flushCallbacks(self):
140        while self.p_input_lines and self.p_input_callbacks:
141            self.p_input_callbacks.pop(0)(self.p_input_lines.pop(0))
142
143    def setupTextView(self):
144        self.textView.setFont_(self.font())
145        self.textView.setContinuousSpellCheckingEnabled_(False)
146        self.textView.setRichText_(False)
147        self.setCharacterIndexForInput_(0)
148
149    def setVersion_executable_pid_(self, version, executable, pid):
150        self.version = version
151        self.pid = pid
152        self.executable = executable
153        self.setFileName_(executable)
154
155    def displayName(self):
156        if not hasattr(self, 'version'):
157            return u'Starting...'
158        return u'Python %s - %s - %s' % (self.version, self.executable, self.pid)
159
160    def updateChangeCount_(self, val):
161        return
162
163    def windowWillClose_(self, window):
164        if self.commandReactor is not None:
165            self.commandReactor.close()
166            self.commandReactor = None
167        if self.interpreter is not None:
168            self.interpreter.close()
169            self.interpreter = None
170
171    def windowNibName(self):
172        return u'RemotePyInterpreterDocument'
173
174    def isDocumentEdited(self):
175        return False
176
177    def awakeFromNib(self):
178        # XXX - should this be done later?
179        self.setFont_(NSFont.userFixedPitchFontOfSize_(10))
180        self.p_colors = {
181            u'stderr': NSColor.redColor(),
182            u'stdout': NSColor.blueColor(),
183            u'code': NSColor.blackColor(),
184        }
185        self.setHistoryLength_(50)
186        self.setHistoryView_(0)
187        self.setInteracting_(False)
188        self.setAutoScroll_(True)
189        self.setSingleLineInteraction_(False)
190        self.p_history = [u'']
191        self.p_input_callbacks = []
192        self.p_input_lines = []
193        self.setupTextView()
194        self.interpreter.connect()
195
196    #
197    #  Modal input dialog support
198    #
199
200    #def p_nestedRunLoopReaderUntilEOLchars_(self, eolchars):
201    #    """
202    #    This makes the baby jesus cry.
203
204    #    I want co-routines.
205    #    """
206    #    app = NSApplication.sharedApplication()
207    #    window = self.textView.window()
208    #    self.setCharacterIndexForInput_(self.lengthOfTextView())
209    #    # change the color.. eh
210    #    self.textView.setTypingAttributes_({
211    #        NSFontAttributeName: self.font(),
212    #        NSForegroundColorAttributeName: self.colorForName_(u'code'),
213    #    })
214    #    while True:
215    #        event = app.nextEventMatchingMask_untilDate_inMode_dequeue_(
216    #            NSAnyEventMask,
217    #            NSDate.distantFuture(),
218    #            NSDefaultRunLoopMode,
219    #            True)
220    #        if (event.type() == NSKeyDown) and (event.window() is window):
221    #            eol = event.characters()
222    #            if eol in eolchars:
223    #                break
224    #        app.sendEvent_(event)
225    #    cl = self.currentLine()
226    #    if eol == u'\r':
227    #        self.writeNewLine()
228    #    return cl + eol
229
230    def executeLine_(self, line):
231        self.addHistoryLine_(line)
232        self.p_input_lines.append(line)
233        self.flushCallbacks()
234        self.p_history = filter(None, self.p_history)
235        self.p_history.append(u'')
236        self.setHistoryView_(len(self.p_history) - 1)
237
238    def executeInteractiveLine_(self, line):
239        self.setInteracting_(True)
240        try:
241            self.executeLine_(line)
242        finally:
243            self.setInteracting_(False)
244
245    def replaceLineWithCode_(self, s):
246        idx = self.characterIndexForInput()
247        ts = self.textView.textStorage()
248        s = self.formatString_forOutput_(s, u'code')
249        ts.replaceCharactersInRange_withAttributedString_(
250            (idx, len(ts.mutableString())-idx),
251            s,
252        )
253
254    #
255    #  History functions
256    #
257
258    def addHistoryLine_(self, line):
259        line = line.rstrip(u'\n')
260        if self.p_history[-1] == line:
261            return False
262        if not line:
263            return False
264        self.p_history.append(line)
265        if len(self.p_history) > self.historyLength():
266            self.p_history.pop(0)
267        return True
268
269    def historyDown_(self, sender):
270        if self.p_historyView == (len(self.p_history) - 1):
271            return
272        self.p_history[self.p_historyView] = self.currentLine()
273        self.p_historyView += 1
274        self.replaceLineWithCode_(self.p_history[self.p_historyView])
275        self.moveToEndOfLine_(self)
276
277    def historyUp_(self, sender):
278        if self.p_historyView == 0:
279            return
280        self.p_history[self.p_historyView] = self.currentLine()
281        self.p_historyView -= 1
282        self.replaceLineWithCode_(self.p_history[self.p_historyView])
283        self.moveToEndOfLine_(self)
284
285    #
286    #  Convenience methods to create/write decorated text
287    #
288
289    def formatString_forOutput_(self, s, name):
290        return NSAttributedString.alloc().initWithString_attributes_(
291            s,
292            {
293                NSFontAttributeName: self.font(),
294                NSForegroundColorAttributeName: self.colorForName_(name),
295            },
296        )
297
298    def writeString_forOutput_(self, s, name):
299        s = self.formatString_forOutput_(s, name)
300        self.textView.textStorage().appendAttributedString_(s)
301        if self.isAutoScroll():
302            self.textView.scrollRangeToVisible_((self.lengthOfTextView(), 0))
303
304    def writeNewLine(self):
305        self.writeString_forOutput_(u'\n', u'code')
306
307    def colorForName_(self, name):
308        return self.p_colors[name]
309
310    def setColor_forName_(self, color, name):
311        self.p_colors[name] = color
312
313    #
314    #  Convenience methods for manipulating the NSTextView
315    #
316
317    def currentLine(self):
318        return self.textView.textStorage().mutableString()[self.characterIndexForInput():]
319
320    def moveAndScrollToIndex_(self, idx):
321        self.textView.scrollRangeToVisible_((idx, 0))
322        self.textView.setSelectedRange_((idx, 0))
323
324    def lengthOfTextView(self):
325        return len(self.textView.textStorage().mutableString())
326
327    #
328    #  NSTextViewDelegate methods
329    #
330
331    def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, aTextView, completions, (begin, length), index):
332        # XXX
333        # this will probably have to be tricky in order to be asynchronous..
334        # either by:
335        #     nesting a run loop (bleh)
336        #     polling the subprocess (bleh)
337        #     returning nothing and calling self.textView.complete_ later
338        return None, 0
339
340        if False:
341            txt = self.textView.textStorage().mutableString()
342            end = begin+length
343            while (begin>0) and (txt[begin].isalnum() or txt[begin] in u'._'):
344                begin -= 1
345            while not txt[begin].isalnum():
346                begin += 1
347            return self.p_console.recommendCompletionsFor(txt[begin:end])
348
349    def textView_shouldChangeTextInRange_replacementString_(self, aTextView, aRange, newString):
350        begin, length = aRange
351        lastLocation = self.characterIndexForInput()
352        if begin < lastLocation:
353            # no editing anywhere but the interactive line
354            return False
355        newString = newString.replace(u'\r', u'\n')
356        if u'\n' in newString:
357            if begin != lastLocation:
358                # no pasting multiline unless you're at the end
359                # of the interactive line
360                return False
361            # multiline paste support
362            #self.clearLine()
363            newString = self.currentLine() + newString
364            for s in newString.strip().split(u'\n'):
365                self.writeString_forOutput_(s + u'\n', u'code')
366                self.executeLine_(s)
367            return False
368        return True
369
370    def textView_willChangeSelectionFromCharacterRange_toCharacterRange_(self, aTextView, fromRange, toRange):
371        begin, length = toRange
372        if self.singleLineInteraction() and length == 0 and begin < self.characterIndexForInput():
373            # no cursor movement off the interactive line
374            return fromRange
375        else:
376            return toRange
377
378    def textView_doCommandBySelector_(self, aTextView, aSelector):
379        # deleteForward: is ctrl-d
380        if self.isInteracting():
381            if aSelector == 'insertNewline:':
382                self.writeNewLine()
383            return False
384        # XXX - this is ugly
385        responder = getattr(self, aSelector.replace(':','_'), None)
386        if responder is not None:
387            responder(aTextView)
388            return True
389        else:
390            if DEBUG_DELEGATE and aSelector not in PASSTHROUGH:
391                print aSelector
392            return False
393
394    #
395    #  doCommandBySelector "posers" on the textView
396    #
397
398    def insertTabIgnoringFieldEditor_(self, sender):
399        # this isn't terribly necessary, b/c F5 and opt-esc do completion
400        # but why not
401        sender.complete_(self)
402
403    def moveToBeginningOfLine_(self, sender):
404        self.moveAndScrollToIndex_(self.characterIndexForInput())
405
406    def moveToEndOfLine_(self, sender):
407        self.moveAndScrollToIndex_(self.lengthOfTextView())
408
409    def moveToBeginningOfLineAndModifySelection_(self, sender):
410        begin, length = self.textView.selectedRange()
411        pos = self.characterIndexForInput()
412        if begin + length > pos:
413            self.textView.setSelectedRange_((pos, begin + length - pos))
414        else:
415            self.moveToBeginningOfLine_(sender)
416
417    def moveToEndOfLineAndModifySelection_(self, sender):
418        begin, length = self.textView.selectedRange()
419        pos = max(self.characterIndexForInput(), begin)
420        self.textView.setSelectedRange_((pos, self.lengthOfTextView()))
421
422    def insertNewline_(self, sender):
423        line = self.currentLine()
424        self.writeNewLine()
425        self.executeInteractiveLine_(line)
426
427    moveToBeginningOfParagraph_ = moveToBeginningOfLine_
428    moveToEndOfParagraph_ = moveToEndOfLine_
429    insertNewlineIgnoringFieldEditor_ = insertNewline_
430    moveDown_ = historyDown_
431    moveUp_ = historyUp_
432
433    #
434    #  Accessors
435    #
436
437    def historyLength(self):
438        return self.p_historyLength
439
440    def setHistoryLength_(self, length):
441        self.p_historyLength = length
442
443    def font(self):
444        return self.p_font
445
446    def setFont_(self, font):
447        self.p_font = font
448
449    def isInteracting(self):
450        return self.p_interacting
451
452    def setInteracting_(self, v):
453        self.p_interacting = v
454
455    def isAutoScroll(self):
456        return self.p_autoScroll
457
458    def setAutoScroll_(self, v):
459        self.p_autoScroll = v
460
461    def characterIndexForInput(self):
462        return self.p_characterIndexForInput
463
464    def setCharacterIndexForInput_(self, idx):
465        self.p_characterIndexForInput = idx
466        self.moveAndScrollToIndex_(idx)
467
468    def historyView(self):
469        return self.p_historyView
470
471    def setHistoryView_(self, v):
472        self.p_historyView = v
473
474    def singleLineInteraction(self):
475        return self.p_singleLineInteraction
476
477    def setSingleLineInteraction_(self, v):
478        self.p_singleLineInteraction = v
479
480
481
482if __name__ == '__main__':
483    AppHelper.runEventLoop(installInterrupt=True)
484