• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /macosx-10.10/pyobjc-45/2.6/pyobjc/pyobjc-framework-WebKit/Examples/WebKitInterpreterPlugin/
1import sys
2import traceback
3import sets
4import keyword
5import time
6from code import InteractiveConsole, softspace
7from StringIO import StringIO
8import objc
9from objc import YES, NO, selector
10from Foundation import *
11from AppKit import *
12from PyObjCTools import AppHelper
13import os
14
15FLT_MAX = 3.40282347e+38
16
17try:
18    sys.ps1
19except AttributeError:
20    sys.ps1 = ">>> "
21try:
22    sys.ps2
23except AttributeError:
24    sys.ps2 = "... "
25
26class PseudoUTF8Output(object):
27    softspace = 0
28    def __init__(self, writemethod):
29        self._write = writemethod
30
31    def write(self, s):
32        if not isinstance(s, unicode):
33            s = s.decode('utf-8', 'replace')
34        self._write(s)
35
36    def writelines(self, lines):
37        for line in lines:
38            self.write(line)
39
40    def flush(self):
41        pass
42
43    def isatty(self):
44        return True
45
46class PseudoUTF8Input(object):
47    softspace = 0
48    def __init__(self, readlinemethod):
49        self._buffer = u''
50        self._readline = readlinemethod
51
52    def read(self, chars=None):
53        if chars is None:
54            if self._buffer:
55                rval = self._buffer
56                self._buffer = u''
57                if rval.endswith(u'\r'):
58                    rval = rval[:-1]+u'\n'
59                return rval.encode('utf-8')
60            else:
61                return self._readline(u'\x04')[:-1].encode('utf-8')
62        else:
63            while len(self._buffer) < chars:
64                self._buffer += self._readline(u'\x04\r')
65                if self._buffer.endswith('\x04'):
66                    self._buffer = self._buffer[:-1]
67                    break
68            rval, self._buffer = self._buffer[:chars], self._buffer[chars:]
69            return rval.encode('utf-8').replace('\r','\n')
70
71    def readline(self):
72        if u'\r' not in self._buffer:
73            self._buffer += self._readline(u'\x04\r')
74        if self._buffer.endswith('\x04'):
75            rval = self._buffer[:-1].encode('utf-8')
76        elif self._buffer.endswith('\r'):
77            rval = self._buffer[:-1].encode('utf-8')+'\n'
78        self._buffer = u''
79
80        return rval
81
82class AsyncInteractiveConsole(InteractiveConsole):
83    lock = False
84    buffer = None
85
86    def __init__(self, *args, **kwargs):
87        InteractiveConsole.__init__(self, *args, **kwargs)
88        self.locals['__interpreter__'] = self
89
90    def asyncinteract(self, write=None, banner=None):
91        if self.lock:
92            raise ValueError, "Can't nest"
93        self.lock = True
94        if write is None:
95            write = self.write
96        cprt = u'Type "help", "copyright", "credits" or "license" for more information.'
97        if banner is None:
98            write(u"Python %s in %s\n%s\n" % (
99                sys.version,
100                NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleName'),
101                cprt,
102            ))
103        else:
104            write(banner + '\n')
105        more = 0
106        _buff = []
107        try:
108            while True:
109                if more:
110                    prompt = sys.ps2
111                else:
112                    prompt = sys.ps1
113                write(prompt)
114                # yield the kind of prompt we have
115                yield more
116                # next input function
117                yield _buff.append
118                more = self.push(_buff.pop())
119        except:
120            self.lock = False
121            raise
122        self.lock = False
123
124    def resetbuffer(self):
125        self.lastbuffer = self.buffer
126        InteractiveConsole.resetbuffer(self)
127
128    def runcode(self, code):
129        try:
130            exec code in self.locals
131        except SystemExit:
132            raise
133        except:
134            self.showtraceback()
135        else:
136            if softspace(sys.stdout, 0):
137                print
138
139
140    def recommendCompletionsFor(self, word):
141        parts = word.split('.')
142        if len(parts) > 1:
143            # has a . so it must be a module or class or something
144            # using eval, which shouldn't normally have side effects
145            # unless there's descriptors/metaclasses doing some nasty
146            # get magic
147            objname = '.'.join(parts[:-1])
148            try:
149                obj = eval(objname, self.locals)
150            except:
151                return None, 0
152            wordlower = parts[-1].lower()
153            if wordlower == '':
154                # they just punched in a dot, so list all attributes
155                # that don't look private or special
156                prefix = '.'.join(parts[-2:])
157                check = [
158                    (prefix+_method)
159                    for _method
160                    in dir(obj)
161                    if _method[:1] != '_' and _method.lower().startswith(wordlower)
162                ]
163            else:
164                # they started typing the method name
165                check = filter(lambda s:s.lower().startswith(wordlower), dir(obj))
166        else:
167            # no dots, must be in the normal namespaces.. no eval necessary
168            check = sets.Set(dir(__builtins__))
169            check.update(keyword.kwlist)
170            check.update(self.locals)
171            wordlower = parts[-1].lower()
172            check = filter(lambda s:s.lower().startswith(wordlower), check)
173        check.sort()
174        return check, 0
175
176DEBUG_DELEGATE = 0
177PASSTHROUGH = (
178   'deleteBackward:',
179   'complete:',
180   'moveRight:',
181   'moveLeft:',
182)
183
184class PyInterpreter(NSObject):
185    """
186    PyInterpreter is a delegate/controller for a NSTextView,
187    turning it into a full featured interactive Python interpreter.
188    """
189
190    textView = objc.ivar('textView')
191
192    def initWithTextView_(self, textView):
193        self = super(PyInterpreter, self).init()
194        self.textView = textView
195        self.textView.setDelegate_(self)
196        self.awakeFromNib()
197        return self
198
199    def interpreterLocals(self):
200        return self._console.locals
201
202    #
203    #  NSApplicationDelegate methods
204    #
205
206    def applicationDidFinishLaunching_(self, aNotification):
207        self.textView.setFont_(self.font())
208        self.textView.setContinuousSpellCheckingEnabled_(False)
209        self.textView.setRichText_(False)
210        self._executeWithRedirectedIO(self._interp)
211
212    #
213    #  NIB loading protocol
214    #
215
216    def awakeFromNib(self):
217        self = super(PyInterpreter, self).init()
218        self._font = NSFont.userFixedPitchFontOfSize_(10)
219        self._stderrColor = NSColor.redColor()
220        self._stdoutColor = NSColor.blueColor()
221        self._codeColor = NSColor.blackColor()
222        self._historyLength = 50
223        self._history = [u'']
224        self._historyView = 0
225        self._characterIndexForInput = 0
226        self._stdin = PseudoUTF8Input(self._nestedRunLoopReaderUntilEOLchars_)
227        #self._stdin = PseudoUTF8Input(self.readStdin)
228        self._stderr = PseudoUTF8Output(self.writeStderr_)
229        self._stdout = PseudoUTF8Output(self.writeStdout_)
230        self._isInteracting = False
231        self._console = AsyncInteractiveConsole()
232        self._interp = self._console.asyncinteract(
233            write=self.writeCode_,
234        ).next
235        self._autoscroll = True
236        self.applicationDidFinishLaunching_(None)
237
238    #
239    #  Modal input dialog support
240    #
241
242    def _nestedRunLoopReaderUntilEOLchars_(self, eolchars):
243        """
244        This makes the baby jesus cry.
245
246        I want co-routines.
247        """
248        app = NSApplication.sharedApplication()
249        window = self.textView.window()
250        self.setCharacterIndexForInput_(self.lengthOfTextView())
251        # change the color.. eh
252        self.textView.setTypingAttributes_({
253            NSFontAttributeName:self.font(),
254            NSForegroundColorAttributeName:self.codeColor(),
255        })
256        while True:
257            event = app.nextEventMatchingMask_untilDate_inMode_dequeue_(
258                NSAnyEventMask,
259                NSDate.distantFuture(),
260                NSDefaultRunLoopMode,
261                True)
262            if (event.type() == NSKeyDown) and (event.window() == window):
263                eol = event.characters()
264                if eol in eolchars:
265                    break
266            app.sendEvent_(event)
267        cl = self.currentLine()
268        if eol == '\r':
269            self.writeCode_('\n')
270        return cl+eol
271
272    #
273    #  Interpreter functions
274    #
275
276    def _executeWithRedirectedIO(self, fn, *args, **kwargs):
277        old = sys.stdin, sys.stdout, sys.stderr
278        if self._stdin is not None:
279            sys.stdin = self._stdin
280        sys.stdout, sys.stderr = self._stdout, self._stderr
281        try:
282            rval = fn(*args, **kwargs)
283        finally:
284            sys.stdin, sys.stdout, sys.stderr = old
285            self.setCharacterIndexForInput_(self.lengthOfTextView())
286        return rval
287
288    def executeLine_(self, line):
289        self.addHistoryLine_(line)
290        self._executeWithRedirectedIO(self._executeLine_, line)
291        self._history = filter(None, self._history)
292        self._history.append(u'')
293        self._historyView = len(self._history) - 1
294
295    def _executeLine_(self, line):
296        self._interp()(line)
297        self._more = self._interp()
298
299    def executeInteractiveLine_(self, line):
300        self.setIsInteracting(True)
301        try:
302            self.executeLine_(line)
303        finally:
304            self.setIsInteracting(False)
305
306    def replaceLineWithCode_(self, s):
307        idx = self.characterIndexForInput()
308        ts = self.textView.textStorage()
309        ts.replaceCharactersInRange_withAttributedString_(
310            (idx, len(ts.mutableString())-idx), self.codeString_(s))
311
312    #
313    #  History functions
314    #
315
316    def historyLength(self):
317        return self._historyLength
318
319    def setHistoryLength_(self, length):
320        self._historyLength = length
321
322    def addHistoryLine_(self, line):
323        line = line.rstrip('\n')
324        if self._history[-1] == line:
325            return False
326        if not line:
327            return False
328        self._history.append(line)
329        if len(self._history) > self.historyLength():
330            self._history.pop(0)
331        return True
332
333    def historyDown_(self, sender):
334        if self._historyView == (len(self._history) - 1):
335            return
336        self._history[self._historyView] = self.currentLine()
337        self._historyView += 1
338        self.replaceLineWithCode_(self._history[self._historyView])
339        self.moveToEndOfLine_(self)
340
341    def historyUp_(self, sender):
342        if self._historyView == 0:
343            return
344        self._history[self._historyView] = self.currentLine()
345        self._historyView -= 1
346        self.replaceLineWithCode_(self._history[self._historyView])
347        self.moveToEndOfLine_(self)
348
349    #
350    #  Convenience methods to create/write decorated text
351    #
352
353    def _formatString_forOutput_(self, s, name):
354        return NSAttributedString.alloc().initWithString_attributes_(
355            s,
356            {
357                NSFontAttributeName:self.font(),
358                NSForegroundColorAttributeName:getattr(self, name+'Color')(),
359            },
360        )
361
362    def _writeString_forOutput_(self, s, name):
363        self.textView.textStorage().appendAttributedString_(getattr(self, name+'String_')(s))
364
365        window = self.textView.window()
366        app = NSApplication.sharedApplication()
367        st = time.time()
368        now = time.time
369
370        if self._autoscroll:
371            self.textView.scrollRangeToVisible_((self.lengthOfTextView(), 0))
372
373        while app.isRunning() and now() - st < 0.01:
374            event = app.nextEventMatchingMask_untilDate_inMode_dequeue_(
375                NSAnyEventMask,
376                NSDate.dateWithTimeIntervalSinceNow_(0.01),
377                NSDefaultRunLoopMode,
378                True)
379
380            if event is None:
381                continue
382
383            if (event.type() == NSKeyDown) and (event.window() == window):
384                chr = event.charactersIgnoringModifiers()
385                if chr == 'c' and (event.modifierFlags() & NSControlKeyMask):
386                    raise KeyboardInterrupt
387
388            app.sendEvent_(event)
389
390
391    codeString_   = lambda self, s: self._formatString_forOutput_(s, 'code')
392    stderrString_ = lambda self, s: self._formatString_forOutput_(s, 'stderr')
393    stdoutString_ = lambda self, s: self._formatString_forOutput_(s, 'stdout')
394    writeCode_    = lambda self, s: self._writeString_forOutput_(s, 'code')
395    writeStderr_  = lambda self, s: self._writeString_forOutput_(s, 'stderr')
396    writeStdout_  = lambda self, s: self._writeString_forOutput_(s, 'stdout')
397
398    #
399    #  Accessors
400    #
401
402    def more(self):
403        return self._more
404
405    def font(self):
406        return self._font
407
408    def setFont_(self, font):
409        self._font = font
410
411    def stderrColor(self):
412        return self._stderrColor
413
414    def setStderrColor_(self, color):
415        self._stderrColor = color
416
417    def stdoutColor(self):
418        return self._stdoutColor
419
420    def setStdoutColor_(self, color):
421        self._stdoutColor = color
422
423    def codeColor(self):
424        return self._codeColor
425
426    def setStdoutColor_(self, color):
427        self._codeColor = color
428
429    def isInteracting(self):
430        return self._isInteracting
431
432    def setIsInteracting(self, v):
433        self._isInteracting = v
434
435    def isAutoScroll(self):
436        return self._autoScroll
437
438    def setAutoScroll(self, v):
439        self._autoScroll = v
440
441
442    #
443    #  Convenience methods for manipulating the NSTextView
444    #
445
446    def currentLine(self):
447        return self.textView.textStorage().mutableString()[self.characterIndexForInput():]
448
449    def moveAndScrollToIndex_(self, idx):
450        self.textView.scrollRangeToVisible_((idx, 0))
451        self.textView.setSelectedRange_((idx, 0))
452
453    def characterIndexForInput(self):
454        return self._characterIndexForInput
455
456    def lengthOfTextView(self):
457        return len(self.textView.textStorage().mutableString())
458
459    def setCharacterIndexForInput_(self, idx):
460        self._characterIndexForInput = idx
461        self.moveAndScrollToIndex_(idx)
462
463    #
464    #  NSTextViewDelegate methods
465    #
466
467    def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, aTextView, completions, (begin, length), index):
468        txt = self.textView.textStorage().mutableString()
469        end = begin+length
470        while (begin>0) and (txt[begin].isalnum() or txt[begin] in '._'):
471            begin -= 1
472        while not txt[begin].isalnum():
473            begin += 1
474        return self._console.recommendCompletionsFor(txt[begin:end])
475
476    def textView_shouldChangeTextInRange_replacementString_(self, aTextView, aRange, newString):
477        begin, length = aRange
478        lastLocation = self.characterIndexForInput()
479        if begin < lastLocation:
480            # no editing anywhere but the interactive line
481            return NO
482        newString = newString.replace('\r', '\n')
483        if '\n' in newString:
484            if begin != lastLocation:
485                # no pasting multiline unless you're at the end
486                # of the interactive line
487                return NO
488            # multiline paste support
489            #self.clearLine()
490            newString = self.currentLine() + newString
491            for s in newString.strip().split('\n'):
492                self.writeCode_(s+'\n')
493                self.executeLine_(s)
494            return NO
495        return YES
496
497    def textView_willChangeSelectionFromCharacterRange_toCharacterRange_(self, aTextView, fromRange, toRange):
498        return toRange
499        begin, length = toRange
500        if length == 0 and begin < self.characterIndexForInput():
501            # no cursor movement off the interactive line
502            return fromRange
503        return toRange
504
505    def textView_doCommandBySelector_(self, aTextView, aSelector):
506        # deleteForward: is ctrl-d
507        if self.isInteracting():
508            if aSelector == 'insertNewline:':
509                self.writeCode_('\n')
510            return NO
511        responder = getattr(self, aSelector.replace(':','_'), None)
512        if responder is not None:
513            responder(aTextView)
514            return YES
515        else:
516            if DEBUG_DELEGATE and aSelector not in PASSTHROUGH:
517                print aSelector
518            return NO
519
520    #
521    #  doCommandBySelector "posers" on the textView
522    #
523
524    def insertTabIgnoringFieldEditor_(self, sender):
525        # this isn't terribly necessary, b/c F5 and opt-esc do completion
526        # but why not
527        sender.complete_(self)
528
529    def moveToBeginningOfLine_(self, sender):
530        self.moveAndScrollToIndex_(self.characterIndexForInput())
531
532    def moveToEndOfLine_(self, sender):
533        self.moveAndScrollToIndex_(self.lengthOfTextView())
534
535    def moveToBeginningOfLineAndModifySelection_(self, sender):
536        begin, length = self.textView.selectedRange()
537        pos = self.characterIndexForInput()
538        if begin+length > pos:
539            self.textView.setSelectedRange_((pos, begin+length-pos))
540        else:
541            self.moveToBeginningOfLine_(sender)
542
543    def moveToEndOfLineAndModifySelection_(self, sender):
544        begin, length = self.textView.selectedRange()
545        pos = max(self.characterIndexForInput(), begin)
546        self.textView.setSelectedRange_((pos, self.lengthOfTextView()))
547
548    def insertNewline_(self, sender):
549        line = self.currentLine()
550        self.writeCode_('\n')
551        self.executeInteractiveLine_(line)
552
553    moveToBeginningOfParagraph_ = moveToBeginningOfLine_
554    moveToEndOfParagraph_ = moveToEndOfLine_
555    insertNewlineIgnoringFieldEditor_ = insertNewline_
556    moveDown_ = historyDown_
557    moveUp_ = historyUp_
558
559class WebKitInterpreter(NSView):
560
561    arguments = objc.ivar('arguments')
562    pyInterpreter = objc.ivar('pyInterpreter')
563    scrollView = objc.ivar('scrollView')
564    textView = objc.ivar('textView')
565
566    def container(self):
567        return self.arguments.get(u'WebPluginContainer')
568
569    def pluginViewWithArguments_(cls, arguments):
570        self = super(WebKitInterpreter, cls).alloc().initWithFrame_(NSZeroRect)
571        NSLog('pluginViewWithArguments:')
572        NSLog(arguments)
573        self.arguments = arguments
574        return self
575    pluginViewWithArguments_ = classmethod(pluginViewWithArguments_)
576
577    def pluginStart(self):
578        NSLog('pluginStart')
579        try:
580            self.doPluginStart()
581        except:
582            import traceback
583            traceback.print_exc()
584
585    def doPluginStart(self):
586        dct = self.arguments[u'WebPluginAttributes']
587        w, h = [float(dct.get(k, 0)) for k in ('width', 'height')]
588
589        self.setFrame_(((0.0, 0.0), (w, h)))
590        scrollView = NSScrollView.alloc().initWithFrame_(self.frame())
591        scrollView.setHasVerticalScroller_(True)
592        scrollView.setHasHorizontalScroller_(False)
593        scrollView.setAutoresizingMask_(
594            NSViewWidthSizable | NSViewHeightSizable)
595        contentSize = scrollView.contentSize()
596        textView = NSTextView.alloc().initWithFrame_(
597            ((0, 0), scrollView.contentSize()))
598        textView.setMinSize_(
599            (0, contentSize.height))
600        textView.setMaxSize_(
601            (FLT_MAX, FLT_MAX))
602        textView.setVerticallyResizable_(True)
603        textView.setHorizontallyResizable_(False)
604        textView.setAutoresizingMask_(NSViewWidthSizable)
605
606        textView.textContainer().setContainerSize_(
607            (contentSize.width, FLT_MAX))
608        textView.textContainer().setWidthTracksTextView_(True)
609
610        scrollView.setDocumentView_(textView)
611        self.addSubview_(scrollView)
612
613        self.pyInterpreter = PyInterpreter.alloc().initWithTextView_(
614            textView)
615
616        self.pyInterpreter.interpreterLocals()[u'container'] = self.container()
617
618    def objectForWebScript(self):
619        return self
620
621NSLog('loaded WebKitInterpreter')
622
623objc.removeAutoreleasePool()
624