1"""Shell is an interactive text control in which a user types in
2commands to be sent to the interpreter.  This particular shell is
3based on wxPython's wxStyledTextCtrl.
4
5Sponsored by Orbtech - Your source for Python programming expertise."""
6
7__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
8
9import wx
10from wx import stc
11from six import PY3
12
13import keyword
14import os
15import sys
16import time
17from functools import cmp_to_key
18
19from .buffer import Buffer
20from . import dispatcher
21from . import editwindow
22from . import frame
23from .pseudo import PseudoFileIn
24from .pseudo import PseudoFileOut
25from .pseudo import PseudoFileErr
26from .version import VERSION
27from .magic import magic
28from .path import ls,cd,pwd,sx
29
30sys.ps3 = '<-- '  # Input prompt.
31USE_MAGIC=True
32# Force updates from long-running commands after this many seconds
33PRINT_UPDATE_MAX_TIME=2
34
35NAVKEYS = (wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT,
36           wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)
37
38
39class ShellFrame(frame.Frame, frame.ShellFrameMixin):
40    """Frame containing the shell component."""
41
42    name = 'Shell Frame'
43
44    def __init__(self, parent=None, id=-1, title='PyShell',
45                 pos=wx.DefaultPosition, size=wx.DefaultSize,
46                 style=wx.DEFAULT_FRAME_STYLE, locals=None,
47                 InterpClass=None,
48                 config=None, dataDir=None,
49                 *args, **kwds):
50        """Create ShellFrame instance."""
51        frame.Frame.__init__(self, parent, id, title, pos, size, style)
52        frame.ShellFrameMixin.__init__(self, config, dataDir)
53
54        if size == wx.DefaultSize:
55            self.SetSize((750, 525))
56
57        intro = 'PyShell %s - The Flakiest Python Shell' % VERSION
58        self.SetStatusText(intro.replace('\n', ', '))
59        self.shell = Shell(parent=self, id=-1, introText=intro,
60                           locals=locals, InterpClass=InterpClass,
61                           startupScript=self.startupScript,
62                           execStartupScript=self.execStartupScript,
63                           *args, **kwds)
64
65        # Override the shell so that status messages go to the status bar.
66        self.shell.setStatusText = self.SetStatusText
67
68        self.shell.SetFocus()
69        self.LoadSettings()
70
71
72    def OnClose(self, event):
73        """Event handler for closing."""
74        # This isn't working the way I want, but I'll leave it for now.
75        if self.shell.waiting:
76            if event.CanVeto():
77                event.Veto(True)
78        else:
79            self.SaveSettings()
80            self.shell.destroy()
81            self.Destroy()
82
83    def OnAbout(self, event):
84        """Display an About window."""
85        title = 'About PyShell'
86        text = 'PyShell %s\n\n' % VERSION + \
87               'Yet another Python shell, only flakier.\n\n' + \
88               'Half-baked by Patrick K. O\'Brien,\n' + \
89               'the other half is still in the oven.\n\n' + \
90               'Platform: %s\n' % sys.platform + \
91               'Python Version: %s\n' % sys.version.split()[0] + \
92               'wxPython Version: %s\n' % wx.VERSION_STRING + \
93               ('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:]))
94        dialog = wx.MessageDialog(self, text, title,
95                                  wx.OK | wx.ICON_INFORMATION)
96        dialog.ShowModal()
97        dialog.Destroy()
98
99
100    def OnHelp(self, event):
101        """Show a help dialog."""
102        frame.ShellFrameMixin.OnHelp(self, event)
103
104
105    def LoadSettings(self):
106        if self.config is not None:
107            frame.ShellFrameMixin.LoadSettings(self)
108            frame.Frame.LoadSettings(self, self.config)
109            self.shell.LoadSettings(self.config)
110
111    def SaveSettings(self, force=False):
112        if self.config is not None:
113            frame.ShellFrameMixin.SaveSettings(self)
114            if self.autoSaveSettings or force:
115                frame.Frame.SaveSettings(self, self.config)
116                self.shell.SaveSettings(self.config)
117
118    def DoSaveSettings(self):
119        if self.config is not None:
120            self.SaveSettings(force=True)
121            self.config.Flush()
122
123
124
125
126HELP_TEXT = """\
127* Key bindings:
128Home              Go to the beginning of the command or line.
129Shift+Home        Select to the beginning of the command or line.
130Shift+End         Select to the end of the line.
131End               Go to the end of the line.
132Ctrl+C            Copy selected text, removing prompts.
133Ctrl+Shift+C      Copy selected text, retaining prompts.
134Alt+C             Copy to the clipboard, including prefixed prompts.
135Ctrl+X            Cut selected text.
136Ctrl+V            Paste from clipboard.
137Ctrl+Shift+V      Paste and run multiple commands from clipboard.
138Ctrl+Up Arrow     Retrieve Previous History item.
139Alt+P             Retrieve Previous History item.
140Ctrl+Down Arrow   Retrieve Next History item.
141Alt+N             Retrieve Next History item.
142Shift+Up Arrow    Insert Previous History item.
143Shift+Down Arrow  Insert Next History item.
144F8                Command-completion of History item.
145                  (Type a few characters of a previous command and press F8.)
146Ctrl+Enter        Insert new line into multiline command.
147Ctrl+]            Increase font size.
148Ctrl+[            Decrease font size.
149Ctrl+=            Default font size.
150Ctrl-Space        Show Auto Completion.
151Ctrl-Alt-Space    Show Call Tip.
152Shift+Enter       Complete Text from History.
153Ctrl+F            Search
154F3                Search next
155Ctrl+H            "hide" lines containing selection / "unhide"
156F12               on/off "free-edit" mode
157"""
158
159class ShellFacade:
160    """Simplified interface to all shell-related functionality.
161
162    This is a semi-transparent facade, in that all attributes of other
163    are accessible, even though only some are visible to the user."""
164
165    name = 'Shell Interface'
166
167    def __init__(self, other):
168        """Create a ShellFacade instance."""
169        d = self.__dict__
170        d['other'] = other
171        d['helpText'] = HELP_TEXT
172
173    def help(self):
174        """Display some useful information about how to use the shell."""
175        self.write(self.helpText)
176
177    def __getattr__(self, name):
178        if hasattr(self.other, name):
179            return getattr(self.other, name)
180        else:
181            raise AttributeError(name)
182
183    def __setattr__(self, name, value):
184        if name in self.__dict__:
185            self.__dict__[name] = value
186        elif hasattr(self.other, name):
187            setattr(self.other, name, value)
188        else:
189            raise AttributeError(name)
190
191    def _getAttributeNames(self):
192        """Return list of magic attributes to extend introspection."""
193        list = [
194            'about',
195            'ask',
196            'autoCallTip',
197            'autoComplete',
198            'autoCompleteAutoHide',
199            'autoCompleteCaseInsensitive',
200            'autoCompleteIncludeDouble',
201            'autoCompleteIncludeMagic',
202            'autoCompleteIncludeSingle',
203            'callTipInsert',
204            'clear',
205            'pause',
206            'prompt',
207            'quit',
208            'redirectStderr',
209            'redirectStdin',
210            'redirectStdout',
211            'run',
212            'runfile',
213            'wrap',
214            'zoom',
215            ]
216        list.sort()
217        return list
218
219
220#DNM
221DISPLAY_TEXT="""
222Author: %r
223Py Version: %s
224Python Version: %s
225wxPython Version: %s
226wxPython PlatformInfo: %s
227Platform: %s"""
228
229class Shell(editwindow.EditWindow):
230    """Shell based on StyledTextCtrl."""
231
232    name = 'Shell'
233
234    def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
235                 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
236                 introText='', locals=None, InterpClass=None,
237                 startupScript=None, execStartupScript=True,
238                 useStockId=True,
239                 *args, **kwds):
240        """Create Shell instance."""
241        editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
242        self.wrap()
243        if locals is None:
244            import __main__
245            locals = __main__.__dict__
246
247        # Grab these so they can be restored by self.redirect* methods.
248        self.stdin = sys.stdin
249        self.stdout = sys.stdout
250        self.stderr = sys.stderr
251
252        # Import a default interpreter class if one isn't provided.
253        if InterpClass == None:
254            from .interpreter import Interpreter
255        else:
256            Interpreter = InterpClass
257
258        # Create a replacement for stdin.
259        self.reader = PseudoFileIn(self.readline, self.readlines)
260        self.reader.input = ''
261        self.reader.isreading = False
262
263        # Set up the interpreter.
264        self.interp = Interpreter(locals=locals,
265                                  rawin=self.raw_input,
266                                  stdin=self.reader,
267                                  stdout=PseudoFileOut(self.writeOut),
268                                  stderr=PseudoFileErr(self.writeErr),
269                                  *args, **kwds)
270
271        # Set up the buffer.
272        self.buffer = Buffer()
273
274        # Find out for which keycodes the interpreter will autocomplete.
275        self.autoCompleteKeys = self.interp.getAutoCompleteKeys()
276
277        # Keep track of the last non-continuation prompt positions.
278        self.promptPosStart = 0
279        self.promptPosEnd = 0
280
281        # Keep track of multi-line commands.
282        self.more = False
283
284        # For use with forced updates during long-running scripts
285        self.lastUpdate=None
286
287        # Create the command history.  Commands are added into the
288        # front of the list (ie. at index 0) as they are entered.
289        # self.historyIndex is the current position in the history; it
290        # gets incremented as you retrieve the previous command,
291        # decremented as you retrieve the next, and reset when you hit
292        # Enter.  self.historyIndex == -1 means you're on the current
293        # command, not in the history.
294        self.history = []
295        self.historyIndex = -1
296
297        #seb add mode for "free edit"
298        self.noteMode = 0
299        self.MarkerDefine(0,stc.STC_MARK_ROUNDRECT)  # marker for hidden
300        self.searchTxt = ""
301
302        # Assign handlers for keyboard events.
303        self.Bind(wx.EVT_CHAR, self.OnChar)
304        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
305
306        # Assign handler for the context menu
307        self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
308        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI)
309
310        # add the option to not use the stock IDs; otherwise the context menu
311        # may not work on Mac without adding the proper IDs to the menu bar
312        if useStockId:
313            self.ID_CUT = wx.ID_CUT
314            self.ID_COPY = wx.ID_COPY
315            self.ID_PASTE = wx.ID_PASTE
316            self.ID_SELECTALL = wx.ID_SELECTALL
317            self.ID_CLEAR = wx.ID_CLEAR
318            self.ID_UNDO = wx.ID_UNDO
319            self.ID_REDO = wx.ID_REDO
320        else:
321            self.ID_CUT = wx.NewIdRef()
322            self.ID_COPY = wx.NewIdRef()
323            self.ID_PASTE = wx.NewIdRef()
324            self.ID_SELECTALL = wx.NewIdRef()
325            self.ID_CLEAR = wx.NewIdRef()
326            self.ID_UNDO = wx.NewIdRef()
327            self.ID_REDO = wx.NewIdRef()
328
329        # Assign handlers for edit events
330        self.Bind(wx.EVT_MENU, lambda evt: self.Cut(), id=self.ID_CUT)
331        self.Bind(wx.EVT_MENU, lambda evt: self.Copy(), id=self.ID_COPY)
332        self.Bind(wx.EVT_MENU, lambda evt: self.CopyWithPrompts(), id=frame.ID_COPY_PLUS)
333        self.Bind(wx.EVT_MENU, lambda evt: self.Paste(), id=self.ID_PASTE)
334        self.Bind(wx.EVT_MENU, lambda evt: self.PasteAndRun(), id=frame.ID_PASTE_PLUS)
335        self.Bind(wx.EVT_MENU, lambda evt: self.SelectAll(), id=self.ID_SELECTALL)
336        self.Bind(wx.EVT_MENU, lambda evt: self.Clear(), id=self.ID_CLEAR)
337        self.Bind(wx.EVT_MENU, lambda evt: self.Undo(), id=self.ID_UNDO)
338        self.Bind(wx.EVT_MENU, lambda evt: self.Redo(), id=self.ID_REDO)
339
340
341        # Assign handler for idle time.
342        self.waiting = False
343        self.Bind(wx.EVT_IDLE, self.OnIdle)
344
345        # Display the introductory banner information.
346        self.showIntro(introText)
347
348        # Assign some pseudo keywords to the interpreter's namespace.
349        self.setBuiltinKeywords()
350
351        # Add 'shell' to the interpreter's local namespace.
352        self.setLocalShell()
353
354        ## NOTE:  See note at bottom of this file...
355        ## #seb: File drag and drop
356        ## self.SetDropTarget( FileDropTarget(self) )
357
358        # Do this last so the user has complete control over their
359        # environment.  They can override anything they want.
360        if execStartupScript:
361            if startupScript is None:
362                startupScript = os.environ.get('PYTHONSTARTUP')
363            self.execStartupScript(startupScript)
364        else:
365            self.prompt()
366
367        wx.CallAfter(self.ScrollToLine, 0)
368
369
370    def clearHistory(self):
371        self.history = []
372        self.historyIndex = -1
373        dispatcher.send(signal="Shell.clearHistory")
374
375
376    def destroy(self):
377        del self.interp
378
379    def setFocus(self):
380        """Set focus to the shell."""
381        self.SetFocus()
382
383    def OnIdle(self, event):
384        """Free the CPU to do other things."""
385        if self.waiting:
386            time.sleep(0.05)
387        event.Skip()
388
389    def showIntro(self, text=''):
390        """Display introductory text in the shell."""
391        if text:
392            self.write(text)
393        try:
394            if self.interp.introText:
395                if text and not text.endswith(os.linesep):
396                    self.write(os.linesep)
397                self.write(self.interp.introText)
398        except AttributeError:
399            pass
400
401    def setBuiltinKeywords(self):
402        """Create pseudo keywords as part of builtins.
403
404        This sets "close", "exit" and "quit" to a helpful string.
405        """
406        from six.moves import builtins
407        builtins.close = builtins.exit = builtins.quit = \
408            'Click on the close button to leave the application.'
409        builtins.cd = cd
410        builtins.ls = ls
411        builtins.pwd = pwd
412        builtins.sx = sx
413
414
415    def quit(self):
416        """Quit the application."""
417        # XXX Good enough for now but later we want to send a close event.
418        # In the close event handler we can make sure they want to
419        # quit.  Other applications, like PythonCard, may choose to
420        # hide rather than quit so we should just post the event and
421        # let the surrounding app decide what it wants to do.
422        self.write('Click on the close button to leave the application.')
423
424
425    def setLocalShell(self):
426        """Add 'shell' to locals as reference to ShellFacade instance."""
427        self.interp.locals['shell'] = ShellFacade(other=self)
428
429
430    def execStartupScript(self, startupScript):
431        """Execute the user's PYTHONSTARTUP script if they have one."""
432        if startupScript and os.path.isfile(startupScript):
433            text = 'Startup script executed: ' + startupScript
434            if PY3:
435                self.push('print(%r)' % text)
436                self.push('with open(%r, "r") as f:\n'
437                          '    exec(f.read())\n' % (startupScript))
438            else:
439                self.push('print(%r); execfile(%r)' % (text, startupScript))
440            self.interp.startupScript = startupScript
441        else:
442            self.push('')
443
444
445    def about(self):
446        """Display information about Py."""
447        #DNM
448        text = DISPLAY_TEXT % \
449        (__author__, VERSION,
450         sys.version.split()[0], wx.VERSION_STRING, str(wx.PlatformInfo),
451         sys.platform)
452        self.write(text.strip())
453
454
455    def OnChar(self, event):
456        """Keypress event handler.
457
458        Only receives an event if OnKeyDown calls event.Skip() for the
459        corresponding event."""
460
461        if self.noteMode:
462            event.Skip()
463            return
464
465        # Prevent modification of previously submitted
466        # commands/responses.
467        if not self.CanEdit():
468            return
469        key = event.GetKeyCode()
470        currpos = self.GetCurrentPos()
471        stoppos = self.promptPosEnd
472        # Return (Enter) needs to be ignored in this handler.
473        if key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
474            pass
475        elif key in self.autoCompleteKeys:
476            # Usually the dot (period) key activates auto completion.
477            # Get the command between the prompt and the cursor.  Add
478            # the autocomplete character to the end of the command.
479            if self.AutoCompActive():
480                self.AutoCompCancel()
481            command = self.GetTextRange(stoppos, currpos) + chr(key)
482            self.write(chr(key))
483            if self.autoComplete:
484                self.autoCompleteShow(command)
485        elif key == ord('('):
486            # The left paren activates a call tip and cancels an
487            # active auto completion.
488            if self.AutoCompActive():
489                self.AutoCompCancel()
490            # Get the command between the prompt and the cursor.  Add
491            # the '(' to the end of the command.
492            self.ReplaceSelection('')
493            command = self.GetTextRange(stoppos, currpos) + '('
494            self.write('(')
495            self.autoCallTipShow(command, self.GetCurrentPos() == self.GetTextLength())
496        else:
497            # Allow the normal event handling to take place.
498            event.Skip()
499
500
501    def OnKeyDown(self, event):
502        """Key down event handler."""
503
504        key = event.GetKeyCode()
505        # If the auto-complete window is up let it do its thing.
506        if self.AutoCompActive():
507            event.Skip()
508            return
509
510        # Prevent modification of previously submitted
511        # commands/responses.
512        controlDown = event.ControlDown()
513        rawControlDown = event.RawControlDown()
514        altDown = event.AltDown()
515        shiftDown = event.ShiftDown()
516        currpos = self.GetCurrentPos()
517        endpos = self.GetTextLength()
518        selecting = self.GetSelectionStart() != self.GetSelectionEnd()
519
520        if (rawControlDown or controlDown) and shiftDown and key in (ord('F'), ord('f')):
521            li = self.GetCurrentLine()
522            m = self.MarkerGet(li)
523            if m & 1<<0:
524                startP = self.PositionFromLine(li)
525                self.MarkerDelete(li, 0)
526                maxli = self.GetLineCount()
527                li += 1 # li stayed visible as header-line
528                li0 = li
529                while li<maxli and self.GetLineVisible(li) == 0:
530                    li += 1
531                endP = self.GetLineEndPosition(li-1)
532                self.ShowLines(li0, li-1)
533                # select reappearing text to allow "hide again"
534                self.SetSelection( startP, endP )
535                return
536            startP,endP = self.GetSelection()
537            endP-=1
538            startL = self.LineFromPosition(startP)
539            endL = self.LineFromPosition(endP)
540
541            # never hide last prompt
542            if endL == self.LineFromPosition(self.promptPosEnd):
543                endL -= 1
544
545            m = self.MarkerGet(startL)
546            self.MarkerAdd(startL, 0)
547            self.HideLines(startL+1,endL)
548            self.SetCurrentPos( startP ) # to ensure caret stays visible !
549
550        if key == wx.WXK_F12: #seb
551            if self.noteMode:
552                # self.promptPosStart not used anyway - or ?
553                self.promptPosEnd = \
554                   self.PositionFromLine( self.GetLineCount()-1 ) + \
555                   len(str(sys.ps1))
556                self.GotoLine(self.GetLineCount())
557                self.GotoPos(self.promptPosEnd)
558                self.prompt()  #make sure we have a prompt
559                self.SetCaretForeground("black")
560                self.SetCaretWidth(1)    #default
561                self.SetCaretPeriod(500) #default
562            else:
563                self.SetCaretForeground("red")
564                self.SetCaretWidth(4)
565                self.SetCaretPeriod(0) #steady
566
567            self.noteMode = not self.noteMode
568            return
569        if self.noteMode:
570            event.Skip()
571            return
572
573        # Return (Enter) is used to submit a command to the
574        # interpreter.
575        if (not (rawControlDown or controlDown) and not shiftDown and not altDown) and \
576           key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
577            if self.CallTipActive():
578                self.CallTipCancel()
579            self.processLine()
580
581        # Complete Text (from already typed words)
582        elif shiftDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
583            self.OnShowCompHistory()
584
585        # Ctrl+Return (Ctrl+Enter) is used to insert a line break.
586        elif (rawControlDown or controlDown) and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
587            if self.CallTipActive():
588                self.CallTipCancel()
589            if currpos == endpos:
590                self.processLine()
591            else:
592                self.insertLineBreak()
593
594        # Let Ctrl-Alt-* get handled normally.
595        elif (rawControlDown or controlDown) and altDown:
596            event.Skip()
597
598        # Clear the current, unexecuted command.
599        elif key == wx.WXK_ESCAPE:
600            if self.CallTipActive():
601                event.Skip()
602            else:
603                self.clearCommand()
604
605        # Clear the current command
606        elif key == wx.WXK_BACK and (rawControlDown or controlDown) and shiftDown:
607            self.clearCommand()
608
609        # Increase font size.
610        elif (rawControlDown or controlDown) and key in (ord(']'), wx.WXK_NUMPAD_ADD):
611            dispatcher.send(signal='FontIncrease')
612
613        # Decrease font size.
614        elif (rawControlDown or controlDown) and key in (ord('['), wx.WXK_NUMPAD_SUBTRACT):
615            dispatcher.send(signal='FontDecrease')
616
617        # Default font size.
618        elif (rawControlDown or controlDown) and key in (ord('='), wx.WXK_NUMPAD_DIVIDE):
619            dispatcher.send(signal='FontDefault')
620
621        # Cut to the clipboard.
622        elif ((rawControlDown or controlDown) and key in (ord('X'), ord('x'))) \
623                 or (shiftDown and key == wx.WXK_DELETE):
624            self.Cut()
625
626        # Copy to the clipboard.
627        elif (rawControlDown or controlDown) and not shiftDown \
628                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
629            self.Copy()
630
631        # Copy to the clipboard, including prompts.
632        elif (rawControlDown or controlDown) and shiftDown \
633                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
634            self.CopyWithPrompts()
635
636        # Copy to the clipboard, including prefixed prompts.
637        elif altDown and not controlDown \
638                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
639            self.CopyWithPromptsPrefixed()
640
641        # Home needs to be aware of the prompt.
642        elif (rawControlDown or controlDown) and key == wx.WXK_HOME:
643            home = self.promptPosEnd
644            if currpos > home:
645                self.SetCurrentPos(home)
646                if not selecting and not shiftDown:
647                    self.SetAnchor(home)
648                    self.EnsureCaretVisible()
649            else:
650                event.Skip()
651
652        # Home needs to be aware of the prompt.
653        elif key == wx.WXK_HOME:
654            home = self.promptPosEnd
655            if currpos > home:
656                [line_str,line_len] = self.GetCurLine()
657                pos=self.GetCurrentPos()
658                if line_str[:4] in [sys.ps1,sys.ps2,sys.ps3]:
659                    self.SetCurrentPos(pos+4-line_len)
660                    #self.SetCurrentPos(home)
661                    if not selecting and not shiftDown:
662                        self.SetAnchor(pos+4-line_len)
663                        self.EnsureCaretVisible()
664                else:
665                    event.Skip()
666            else:
667                event.Skip()
668
669        #
670        # The following handlers modify text, so we need to see if
671        # there is a selection that includes text prior to the prompt.
672        #
673        # Don't modify a selection with text prior to the prompt.
674        elif selecting and key not in NAVKEYS and not self.CanEdit():
675            pass
676
677        # Paste from the clipboard.
678        elif ((rawControlDown or controlDown) and not shiftDown and key in (ord('V'), ord('v'))) \
679                 or (shiftDown and not controlDown and key == wx.WXK_INSERT):
680            self.Paste()
681
682        # manually invoke AutoComplete and Calltips
683        elif (rawControlDown or controlDown) and key == wx.WXK_SPACE:
684            self.OnCallTipAutoCompleteManually(shiftDown)
685
686        # Paste from the clipboard, run commands.
687        elif (rawControlDown or controlDown) and shiftDown and key in (ord('V'), ord('v')):
688            self.PasteAndRun()
689
690        # Replace with the previous command from the history buffer.
691        elif ((rawControlDown or controlDown) and not shiftDown and key == wx.WXK_UP) \
692                 or (altDown and key in (ord('P'), ord('p'))):
693            self.OnHistoryReplace(step=+1)
694
695        # Replace with the next command from the history buffer.
696        elif ((rawControlDown or controlDown) and not shiftDown and key == wx.WXK_DOWN) \
697                 or (altDown and key in (ord('N'), ord('n'))):
698            self.OnHistoryReplace(step=-1)
699
700        # Insert the previous command from the history buffer.
701        elif ((rawControlDown or controlDown) and shiftDown and key == wx.WXK_UP) and self.CanEdit():
702            self.OnHistoryInsert(step=+1)
703
704        # Insert the next command from the history buffer.
705        elif ((rawControlDown or controlDown) and shiftDown and key == wx.WXK_DOWN) and self.CanEdit():
706            self.OnHistoryInsert(step=-1)
707
708        # Search up the history for the text in front of the cursor.
709        elif key == wx.WXK_F8:
710            self.OnHistorySearch()
711
712        # Don't backspace over the latest non-continuation prompt.
713        elif key == wx.WXK_BACK:
714            if selecting and self.CanEdit():
715                event.Skip()
716            elif currpos > self.promptPosEnd:
717                event.Skip()
718
719        # Only allow these keys after the latest prompt.
720        elif key in (wx.WXK_TAB, wx.WXK_DELETE):
721            if self.CanEdit():
722                event.Skip()
723
724        # Don't toggle between insert mode and overwrite mode.
725        elif key == wx.WXK_INSERT:
726            pass
727
728        # Don't allow line deletion.
729        elif controlDown and key in (ord('L'), ord('l')):
730            # TODO : Allow line deletion eventually...
731            #event.Skip()
732            pass
733
734        # Don't allow line transposition.
735        elif controlDown and key in (ord('T'), ord('t')):
736            # TODO : Allow line transposition eventually...
737            # TODO : Will have to adjust markers accordingly and test if allowed...
738            #event.Skip()
739            pass
740
741        # Basic navigation keys should work anywhere.
742        elif key in NAVKEYS:
743            event.Skip()
744
745        # Protect the readonly portion of the shell.
746        elif not self.CanEdit():
747            pass
748
749        else:
750            event.Skip()
751
752
753    def OnShowCompHistory(self):
754        """Show possible autocompletion Words from already typed words."""
755
756        #copy from history
757        his = self.history[:]
758
759        #put together in one string
760        joined = " ".join (his)
761        import re
762
763        #sort out only "good" words
764        newlist = re.split("[ \.\[\]=}(\)\,0-9\"]", joined)
765
766        #length > 1 (mix out "trash")
767        thlist = []
768        for i in newlist:
769            if len (i) > 1:
770                thlist.append (i)
771
772        #unique (no duplicate words
773        #oneliner from german python forum => unique list
774        unlist = [thlist[i] for i in range(len(thlist)) if thlist[i] not in thlist[:i]]
775
776        #sort lowercase
777        def _cmp(a,b):
778            return  ((a > b) - (a < b))
779        unlist.sort(key=cmp_to_key(lambda a, b: _cmp(a.lower(), b.lower())))
780
781        #this is more convenient, isn't it?
782        self.AutoCompSetIgnoreCase(True)
783
784        #join again together in a string
785        stringlist = " ".join(unlist)
786
787        #pos von 0 noch ausrechnen
788
789        #how big is the offset?
790        cpos = self.GetCurrentPos() - 1
791        while chr (self.GetCharAt (cpos)).isalnum():
792            cpos -= 1
793
794        #the most important part
795        self.AutoCompShow(self.GetCurrentPos() - cpos -1, stringlist)
796
797
798    def clearCommand(self):
799        """Delete the current, unexecuted command."""
800        startpos = self.promptPosEnd
801        endpos = self.GetTextLength()
802        self.SetSelection(startpos, endpos)
803        self.ReplaceSelection('')
804        self.more = False
805
806    def OnHistoryReplace(self, step):
807        """Replace with the previous/next command from the history buffer."""
808        self.clearCommand()
809        self.replaceFromHistory(step)
810
811    def replaceFromHistory(self, step):
812        """Replace selection with command from the history buffer."""
813        ps2 = str(sys.ps2)
814        self.ReplaceSelection('')
815        newindex = self.historyIndex + step
816        if -1 <= newindex <= len(self.history):
817            self.historyIndex = newindex
818        if 0 <= newindex <= len(self.history)-1:
819            command = self.history[self.historyIndex]
820            command = command.replace('\n', os.linesep + ps2)
821            self.ReplaceSelection(command)
822
823    def OnHistoryInsert(self, step):
824        """Insert the previous/next command from the history buffer."""
825        if not self.CanEdit():
826            return
827        startpos = self.GetCurrentPos()
828        self.replaceFromHistory(step)
829        endpos = self.GetCurrentPos()
830        self.SetSelection(endpos, startpos)
831
832    def OnHistorySearch(self):
833        """Search up the history buffer for the text in front of the cursor."""
834        if not self.CanEdit():
835            return
836        startpos = self.GetCurrentPos()
837        # The text up to the cursor is what we search for.
838        numCharsAfterCursor = self.GetTextLength() - startpos
839        searchText = self.getCommand(rstrip=False)
840        if numCharsAfterCursor > 0:
841            searchText = searchText[:-numCharsAfterCursor]
842        if not searchText:
843            return
844        # Search upwards from the current history position and loop
845        # back to the beginning if we don't find anything.
846        if (self.historyIndex <= -1) \
847        or (self.historyIndex >= len(self.history)-2):
848            searchOrder = range(len(self.history))
849        else:
850            searchOrder = range(self.historyIndex+1, len(self.history)) + \
851                          range(self.historyIndex)
852        for i in searchOrder:
853            command = self.history[i]
854            if command[:len(searchText)] == searchText:
855                # Replace the current selection with the one we found.
856                self.ReplaceSelection(command[len(searchText):])
857                endpos = self.GetCurrentPos()
858                self.SetSelection(endpos, startpos)
859                # We've now warped into middle of the history.
860                self.historyIndex = i
861                break
862
863    def setStatusText(self, text):
864        """Display status information."""
865
866        # This method will likely be replaced by the enclosing app to
867        # do something more interesting, like write to a status bar.
868        print(text)
869
870    def insertLineBreak(self):
871        """Insert a new line break."""
872        if self.CanEdit():
873            self.write(os.linesep)
874            self.more = True
875            self.prompt()
876
877    def processLine(self):
878        """Process the line of text at which the user hit Enter."""
879
880        # The user hit ENTER and we need to decide what to do. They
881        # could be sitting on any line in the shell.
882
883        thepos = self.GetCurrentPos()
884        startpos = self.promptPosEnd
885        endpos = self.GetTextLength()
886        ps2 = str(sys.ps2)
887        # If they hit RETURN inside the current command, execute the
888        # command.
889        if self.CanEdit():
890            self.SetCurrentPos(endpos)
891            self.interp.more = False
892            command = self.GetTextRange(startpos, endpos)
893            lines = command.split(os.linesep + ps2)
894            lines = [line.rstrip() for line in lines]
895            command = '\n'.join(lines)
896            if self.reader.isreading:
897                if not command:
898                    # Match the behavior of the standard Python shell
899                    # when the user hits return without entering a
900                    # value.
901                    command = '\n'
902                self.reader.input = command
903                self.write(os.linesep)
904            else:
905                self.push(command)
906                wx.CallLater(1, self.EnsureCaretVisible)
907        # Or replace the current command with the other command.
908        else:
909            # If the line contains a command (even an invalid one).
910            if self.getCommand(rstrip=False):
911                command = self.getMultilineCommand()
912                self.clearCommand()
913                self.write(command)
914            # Otherwise, put the cursor back where we started.
915            else:
916                self.SetCurrentPos(thepos)
917                self.SetAnchor(thepos)
918
919    def getMultilineCommand(self, rstrip=True):
920        """Extract a multi-line command from the editor.
921
922        The command may not necessarily be valid Python syntax."""
923        # XXX Need to extract real prompts here. Need to keep track of
924        # the prompt every time a command is issued.
925        ps1 = str(sys.ps1)
926        ps1size = len(ps1)
927        ps2 = str(sys.ps2)
928        ps2size = len(ps2)
929        # This is a total hack job, but it works.
930        text = self.GetCurLine()[0]
931        line = self.GetCurrentLine()
932        while text[:ps2size] == ps2 and line > 0:
933            line -= 1
934            self.GotoLine(line)
935            text = self.GetCurLine()[0]
936        if text[:ps1size] == ps1:
937            line = self.GetCurrentLine()
938            self.GotoLine(line)
939            startpos = self.GetCurrentPos() + ps1size
940            line += 1
941            self.GotoLine(line)
942            while self.GetCurLine()[0][:ps2size] == ps2:
943                line += 1
944                self.GotoLine(line)
945            stoppos = self.GetCurrentPos()
946            command = self.GetTextRange(startpos, stoppos)
947            command = command.replace(os.linesep + ps2, '\n')
948            command = command.rstrip()
949            command = command.replace('\n', os.linesep + ps2)
950        else:
951            command = ''
952        if rstrip:
953            command = command.rstrip()
954        return command
955
956    def getCommand(self, text=None, rstrip=True):
957        """Extract a command from text which may include a shell prompt.
958
959        The command may not necessarily be valid Python syntax."""
960        if not text:
961            text = self.GetCurLine()[0]
962        # Strip the prompt off the front leaving just the command.
963        command = self.lstripPrompt(text)
964        if command == text:
965            command = ''  # Real commands have prompts.
966        if rstrip:
967            command = command.rstrip()
968        return command
969
970    def lstripPrompt(self, text):
971        """Return text without a leading prompt."""
972        ps1 = str(sys.ps1)
973        ps1size = len(ps1)
974        ps2 = str(sys.ps2)
975        ps2size = len(ps2)
976        # Strip the prompt off the front of text.
977        if text[:ps1size] == ps1:
978            text = text[ps1size:]
979        elif text[:ps2size] == ps2:
980            text = text[ps2size:]
981        return text
982
983    def push(self, command, silent = False):
984        """Send command to the interpreter for execution."""
985        if not silent:
986            self.write(os.linesep)
987
988        #DNM
989        if USE_MAGIC:
990            command=magic(command)
991
992        busy = wx.BusyCursor()
993        self.waiting = True
994        self.lastUpdate=None
995        self.more = self.interp.push(command)
996        self.lastUpdate=None
997        self.waiting = False
998        del busy
999        if not self.more:
1000            self.addHistory(command.rstrip())
1001        if not silent:
1002            self.prompt()
1003
1004    def addHistory(self, command):
1005        """Add command to the command history."""
1006        # Reset the history position.
1007        self.historyIndex = -1
1008        # Insert this command into the history, unless it's a blank
1009        # line or the same as the last command.
1010        if command != '' \
1011        and (len(self.history) == 0 or command != self.history[0]):
1012            self.history.insert(0, command)
1013            dispatcher.send(signal="Shell.addHistory", command=command)
1014
1015    def write(self, text):
1016        """Display text in the shell.
1017
1018        Replace line endings with OS-specific endings."""
1019        text = self.fixLineEndings(text)
1020        self.AddText(text)
1021        self.EnsureCaretVisible()
1022
1023        if self.waiting:
1024            if self.lastUpdate==None:
1025                self.lastUpdate=time.time()
1026            if time.time()-self.lastUpdate > PRINT_UPDATE_MAX_TIME:
1027                self.Update()
1028                self.lastUpdate=time.time()
1029
1030    def fixLineEndings(self, text):
1031        """Return text with line endings replaced by OS-specific endings."""
1032        lines = text.split('\r\n')
1033        for l in range(len(lines)):
1034            chunks = lines[l].split('\r')
1035            for c in range(len(chunks)):
1036                chunks[c] = os.linesep.join(chunks[c].split('\n'))
1037            lines[l] = os.linesep.join(chunks)
1038        text = os.linesep.join(lines)
1039        return text
1040
1041    def prompt(self):
1042        """Display proper prompt for the context: ps1, ps2 or ps3.
1043
1044        If this is a continuation line, autoindent as necessary."""
1045        isreading = self.reader.isreading
1046        skip = False
1047        if isreading:
1048            prompt = str(sys.ps3)
1049        elif self.more:
1050            prompt = str(sys.ps2)
1051        else:
1052            prompt = str(sys.ps1)
1053        pos = self.GetCurLine()[1]
1054        if pos > 0:
1055            if isreading:
1056                skip = True
1057            else:
1058                self.write(os.linesep)
1059        if not self.more:
1060            self.promptPosStart = self.GetCurrentPos()
1061        if not skip:
1062            self.write(prompt)
1063        if not self.more:
1064            self.promptPosEnd = self.GetCurrentPos()
1065            # Keep the undo feature from undoing previous responses.
1066            self.EmptyUndoBuffer()
1067
1068        if self.more:
1069            line_num=self.GetCurrentLine()
1070            currentLine=self.GetLine(line_num)
1071            previousLine=self.GetLine(line_num-1)[len(prompt):]
1072            pstrip=previousLine.strip()
1073            lstrip=previousLine.lstrip()
1074
1075            # Get the first alnum word:
1076            first_word=[]
1077            for i in pstrip:
1078                if i.isalnum():
1079                    first_word.append(i)
1080                else:
1081                    break
1082            first_word = ''.join(first_word)
1083
1084            if pstrip == '':
1085                # because it is all whitespace!
1086                indent=previousLine.strip('\n').strip('\r')
1087            else:
1088                indent=previousLine[:(len(previousLine)-len(lstrip))]
1089                if pstrip[-1]==':' and \
1090                    first_word in ['if','else','elif','for','while',
1091                                   'def','class','try','except','finally']:
1092                    indent+=' '*4
1093
1094            self.write(indent)
1095        self.EnsureCaretVisible()
1096        self.ScrollToColumn(0)
1097
1098    def readline(self):
1099        """Replacement for stdin.readline()."""
1100        input = ''
1101        reader = self.reader
1102        reader.isreading = True
1103        self.prompt()
1104        try:
1105            while not reader.input:
1106                wx.GetApp().Yield(onlyIfNeeded=True)
1107            input = reader.input
1108        finally:
1109            reader.input = ''
1110            reader.isreading = False
1111        input = str(input)  # In case of Unicode.
1112        return input
1113
1114    def readlines(self):
1115        """Replacement for stdin.readlines()."""
1116        lines = []
1117        while lines[-1:] != ['\n']:
1118            lines.append(self.readline())
1119        return lines
1120
1121    def raw_input(self, prompt=''):
1122        """Return string based on user input."""
1123        if prompt:
1124            self.write(prompt)
1125        return self.readline()
1126
1127    def ask(self, prompt='Please enter your response:'):
1128        """Get response from the user using a dialog box."""
1129        dialog = wx.TextEntryDialog(None, prompt,
1130                                    'Input Dialog (Raw)', '')
1131        try:
1132            if dialog.ShowModal() == wx.ID_OK:
1133                text = dialog.GetValue()
1134                return text
1135        finally:
1136            dialog.Destroy()
1137        return ''
1138
1139    def pause(self):
1140        """Halt execution pending a response from the user."""
1141        self.ask('Press enter to continue:')
1142
1143    def clear(self):
1144        """Delete all text from the shell."""
1145        self.ClearAll()
1146
1147    def run(self, command, prompt=True, verbose=True):
1148        """Execute command as if it was typed in directly.
1149        >>> shell.run('print("this")')
1150        >>> print("this")
1151        this
1152        >>>
1153        """
1154        # Go to the very bottom of the text.
1155        endpos = self.GetTextLength()
1156        self.SetCurrentPos(endpos)
1157        command = command.rstrip()
1158        if prompt: self.prompt()
1159        if verbose: self.write(command)
1160        self.push(command)
1161
1162    def runfile(self, filename):
1163        """Execute all commands in file as if they were typed into the
1164        shell."""
1165        file = open(filename)
1166        try:
1167            self.prompt()
1168            for command in file.readlines():
1169                if command[:6] == 'shell.':
1170                    # Run shell methods silently.
1171                    self.run(command, prompt=False, verbose=False)
1172                else:
1173                    self.run(command, prompt=False, verbose=True)
1174        finally:
1175            file.close()
1176
1177    def autoCompleteShow(self, command, offset = 0):
1178        """Display auto-completion popup list."""
1179        self.AutoCompSetAutoHide(self.autoCompleteAutoHide)
1180        self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
1181        list = self.interp.getAutoCompleteList(command,
1182                    includeMagic=self.autoCompleteIncludeMagic,
1183                    includeSingle=self.autoCompleteIncludeSingle,
1184                    includeDouble=self.autoCompleteIncludeDouble)
1185        if list:
1186            options = ' '.join(list)
1187            #offset = 0
1188            self.AutoCompShow(offset, options)
1189
1190    def autoCallTipShow(self, command, insertcalltip = True, forceCallTip = False):
1191        """Display argument spec and docstring in a popup window."""
1192        if self.CallTipActive():
1193            self.CallTipCancel()
1194        (name, argspec, tip) = self.interp.getCallTip(command)
1195        if tip:
1196            dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
1197        if not self.autoCallTip and not forceCallTip:
1198            return
1199        startpos = self.GetCurrentPos()
1200        if argspec and insertcalltip and self.callTipInsert:
1201            self.write(argspec + ')')
1202            endpos = self.GetCurrentPos()
1203            self.SetSelection(startpos, endpos)
1204        if tip:
1205            tippos = startpos - (len(name) + 1)
1206            fallback = startpos - self.GetColumn(startpos)
1207            # In case there isn't enough room, only go back to the
1208            # fallback.
1209            tippos = max(tippos, fallback)
1210            self.CallTipShow(tippos, tip)
1211
1212    def OnCallTipAutoCompleteManually (self, shiftDown):
1213        """AutoComplete and Calltips manually."""
1214        if self.AutoCompActive():
1215            self.AutoCompCancel()
1216        currpos = self.GetCurrentPos()
1217        stoppos = self.promptPosEnd
1218
1219        cpos = currpos
1220        #go back until '.' is found
1221        pointavailpos = -1
1222        while cpos >= stoppos:
1223            if self.GetCharAt(cpos) == ord ('.'):
1224                pointavailpos = cpos
1225                break
1226            cpos -= 1
1227
1228        #word from non whitespace until '.'
1229        if pointavailpos != -1:
1230            #look backward for first whitespace char
1231            textbehind = self.GetTextRange (pointavailpos + 1, currpos)
1232            pointavailpos += 1
1233
1234            if not shiftDown:
1235                #call AutoComplete
1236                stoppos = self.promptPosEnd
1237                textbefore = self.GetTextRange(stoppos, pointavailpos)
1238                self.autoCompleteShow(textbefore, len (textbehind))
1239            else:
1240                #call CallTips
1241                cpos = pointavailpos
1242                begpos = -1
1243                while cpos > stoppos:
1244                    if chr(self.GetCharAt(cpos)).isspace():
1245                        begpos = cpos
1246                        break
1247                    cpos -= 1
1248                if begpos == -1:
1249                    begpos = cpos
1250                ctips = self.GetTextRange (begpos, currpos)
1251                ctindex = ctips.find ('(')
1252                if ctindex != -1 and not self.CallTipActive():
1253                    #insert calltip, if current pos is '(', otherwise show it only
1254                    self.autoCallTipShow(ctips[:ctindex + 1],
1255                        self.GetCharAt(currpos - 1) == ord('(') and \
1256                           self.GetCurrentPos() == self.GetTextLength(),
1257                        True)
1258
1259
1260    def writeOut(self, text):
1261        """Replacement for stdout."""
1262        self.write(text)
1263
1264    def writeErr(self, text):
1265        """Replacement for stderr."""
1266        self.write(text)
1267
1268    def redirectStdin(self, redirect=True):
1269        """If redirect is true then sys.stdin will come from the shell."""
1270        if redirect:
1271            sys.stdin = self.reader
1272        else:
1273            sys.stdin = self.stdin
1274
1275    def redirectStdout(self, redirect=True):
1276        """If redirect is true then sys.stdout will go to the shell."""
1277        if redirect:
1278            sys.stdout = PseudoFileOut(self.writeOut)
1279        else:
1280            sys.stdout = self.stdout
1281
1282    def redirectStderr(self, redirect=True):
1283        """If redirect is true then sys.stderr will go to the shell."""
1284        if redirect:
1285            sys.stderr = PseudoFileErr(self.writeErr)
1286        else:
1287            sys.stderr = self.stderr
1288
1289    def CanCut(self):
1290        """Return true if text is selected and can be cut."""
1291        if self.GetSelectionStart() != self.GetSelectionEnd() \
1292               and self.GetSelectionStart() >= self.promptPosEnd \
1293               and self.GetSelectionEnd() >= self.promptPosEnd:
1294            return True
1295        else:
1296            return False
1297
1298    def CanPaste(self):
1299        """Return true if a paste should succeed."""
1300        if self.CanEdit() and editwindow.EditWindow.CanPaste(self):
1301            return True
1302        else:
1303            return False
1304
1305    def CanEdit(self):
1306        """Return true if editing should succeed."""
1307        if self.GetSelectionStart() != self.GetSelectionEnd():
1308            if self.GetSelectionStart() >= self.promptPosEnd \
1309                   and self.GetSelectionEnd() >= self.promptPosEnd:
1310                return True
1311            else:
1312                return False
1313        else:
1314            return self.GetCurrentPos() >= self.promptPosEnd
1315
1316    def Cut(self):
1317        """Remove selection and place it on the clipboard."""
1318        if self.CanCut() and self.CanCopy():
1319            if self.AutoCompActive():
1320                self.AutoCompCancel()
1321            if self.CallTipActive():
1322                self.CallTipCancel()
1323            self.Copy()
1324            self.ReplaceSelection('')
1325
1326    def Copy(self):
1327        """Copy selection and place it on the clipboard."""
1328        if self.CanCopy():
1329            ps1 = str(sys.ps1)
1330            ps2 = str(sys.ps2)
1331            command = self.GetSelectedText()
1332            command = command.replace(os.linesep + ps2, os.linesep)
1333            command = command.replace(os.linesep + ps1, os.linesep)
1334            command = self.lstripPrompt(text=command)
1335            data = wx.TextDataObject(command)
1336            self._clip(data)
1337
1338    def CopyWithPrompts(self):
1339        """Copy selection, including prompts, and place it on the clipboard."""
1340        if self.CanCopy():
1341            command = self.GetSelectedText()
1342            data = wx.TextDataObject(command)
1343            self._clip(data)
1344
1345    def CopyWithPromptsPrefixed(self):
1346        """Copy selection, including prompts prefixed with four
1347        spaces, and place it on the clipboard."""
1348        if self.CanCopy():
1349            command = self.GetSelectedText()
1350            spaces = ' ' * 4
1351            command = spaces + command.replace(os.linesep,
1352                                               os.linesep + spaces)
1353            data = wx.TextDataObject(command)
1354            self._clip(data)
1355
1356    def _clip(self, data):
1357        if wx.TheClipboard.Open():
1358            wx.TheClipboard.UsePrimarySelection(False)
1359            wx.TheClipboard.SetData(data)
1360            wx.TheClipboard.Flush()
1361            wx.TheClipboard.Close()
1362
1363    def Paste(self):
1364        """Replace selection with clipboard contents."""
1365        if self.CanPaste() and wx.TheClipboard.Open():
1366            ps2 = str(sys.ps2)
1367            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
1368                data = wx.TextDataObject()
1369                if wx.TheClipboard.GetData(data):
1370                    self.ReplaceSelection('')
1371                    command = data.GetText()
1372                    command = command.rstrip()
1373                    command = self.fixLineEndings(command)
1374                    command = self.lstripPrompt(text=command)
1375                    command = command.replace(os.linesep + ps2, '\n')
1376                    command = command.replace(os.linesep, '\n')
1377                    command = command.replace('\n', os.linesep + ps2)
1378                    self.write(command)
1379            wx.TheClipboard.Close()
1380
1381
1382    def PasteAndRun(self):
1383        """Replace selection with clipboard contents, run commands."""
1384        text = ''
1385        if wx.TheClipboard.Open():
1386            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
1387                data = wx.TextDataObject()
1388                if wx.TheClipboard.GetData(data):
1389                    text = data.GetText()
1390            wx.TheClipboard.Close()
1391        if text:
1392            self.Execute(text)
1393
1394
1395    def Execute(self, text):
1396        """Replace selection with text and run commands."""
1397        ps1 = str(sys.ps1)
1398        ps2 = str(sys.ps2)
1399        endpos = self.GetTextLength()
1400        self.SetCurrentPos(endpos)
1401        startpos = self.promptPosEnd
1402        self.SetSelection(startpos, endpos)
1403        self.ReplaceSelection('')
1404        text = text.lstrip()
1405        text = self.fixLineEndings(text)
1406        text = self.lstripPrompt(text)
1407        text = text.replace(os.linesep + ps1, '\n')
1408        text = text.replace(os.linesep + ps2, '\n')
1409        text = text.replace(os.linesep, '\n')
1410        lines = text.split('\n')
1411        commands = []
1412        command = ''
1413        for line in lines:
1414            if line.strip() == ps2.strip():
1415                # If we are pasting from something like a
1416                # web page that drops the trailing space
1417                # from the ps2 prompt of a blank line.
1418                line = ''
1419            lstrip = line.lstrip()
1420            if line.strip() != '' and lstrip == line and \
1421                    lstrip[:4] not in ['else','elif'] and \
1422                    lstrip[:6] != 'except':
1423                # New command.
1424                if command:
1425                    # Add the previous command to the list.
1426                    commands.append(command)
1427                # Start a new command, which may be multiline.
1428                command = line
1429            else:
1430                # Multiline command. Add to the command.
1431                command += '\n'
1432                command += line
1433        commands.append(command)
1434        for command in commands:
1435            command = command.replace('\n', os.linesep + ps2)
1436            self.write(command)
1437            self.processLine()
1438
1439
1440    def wrap(self, wrap=True):
1441        """Sets whether text is word wrapped."""
1442        try:
1443            self.SetWrapMode(wrap)
1444        except AttributeError:
1445            return 'Wrapping is not available in this version.'
1446
1447    def zoom(self, points=0):
1448        """Set the zoom level.
1449
1450        This number of points is added to the size of all fonts.  It
1451        may be positive to magnify or negative to reduce."""
1452        self.SetZoom(points)
1453
1454
1455
1456    def LoadSettings(self, config):
1457        self.autoComplete              = \
1458            config.ReadBool('Options/AutoComplete', True)
1459        self.autoCompleteIncludeMagic  = \
1460            config.ReadBool('Options/AutoCompleteIncludeMagic', True)
1461        self.autoCompleteIncludeSingle = \
1462            config.ReadBool('Options/AutoCompleteIncludeSingle', True)
1463        self.autoCompleteIncludeDouble = \
1464            config.ReadBool('Options/AutoCompleteIncludeDouble', True)
1465
1466        self.autoCallTip = config.ReadBool('Options/AutoCallTip', True)
1467        self.callTipInsert = config.ReadBool('Options/CallTipInsert', True)
1468        self.SetWrapMode(config.ReadBool('View/WrapMode', True))
1469
1470        self.lineNumbers = config.ReadBool('View/ShowLineNumbers', True)
1471        self.setDisplayLineNumbers (self.lineNumbers)
1472        zoom = config.ReadInt('View/Zoom/Shell', -99)
1473        if zoom != -99:
1474            self.SetZoom(zoom)
1475
1476
1477    def SaveSettings(self, config):
1478        config.WriteBool('Options/AutoComplete', self.autoComplete)
1479        config.WriteBool('Options/AutoCompleteIncludeMagic',
1480                         self.autoCompleteIncludeMagic)
1481        config.WriteBool('Options/AutoCompleteIncludeSingle',
1482                         self.autoCompleteIncludeSingle)
1483        config.WriteBool('Options/AutoCompleteIncludeDouble',
1484                         self.autoCompleteIncludeDouble)
1485        config.WriteBool('Options/AutoCallTip', self.autoCallTip)
1486        config.WriteBool('Options/CallTipInsert', self.callTipInsert)
1487        config.WriteBool('View/WrapMode', self.GetWrapMode())
1488        config.WriteBool('View/ShowLineNumbers', self.lineNumbers)
1489        config.WriteInt('View/Zoom/Shell', self.GetZoom())
1490
1491    def GetContextMenu(self):
1492        """
1493            Create and return a context menu for the shell.
1494            This is used instead of the scintilla default menu
1495            in order to correctly respect our immutable buffer.
1496        """
1497        menu = wx.Menu()
1498        menu.Append(self.ID_UNDO, "Undo")
1499        menu.Append(self.ID_REDO, "Redo")
1500
1501        menu.AppendSeparator()
1502
1503        menu.Append(self.ID_CUT, "Cut")
1504        menu.Append(self.ID_COPY, "Copy")
1505        menu.Append(frame.ID_COPY_PLUS, "Copy With Prompts")
1506        menu.Append(self.ID_PASTE, "Paste")
1507        menu.Append(frame.ID_PASTE_PLUS, "Paste And Run")
1508        menu.Append(self.ID_CLEAR, "Clear")
1509
1510        menu.AppendSeparator()
1511
1512        menu.Append(self.ID_SELECTALL, "Select All")
1513        return menu
1514
1515    def OnContextMenu(self, evt):
1516        menu = self.GetContextMenu()
1517        self.PopupMenu(menu)
1518
1519    def OnUpdateUI(self, evt):
1520        id = evt.Id
1521        if id in (self.ID_CUT, self.ID_CLEAR):
1522            evt.Enable(self.CanCut())
1523        elif id in (self.ID_COPY, frame.ID_COPY_PLUS):
1524            evt.Enable(self.CanCopy())
1525        elif id in (self.ID_PASTE, frame.ID_PASTE_PLUS):
1526            evt.Enable(self.CanPaste())
1527        elif id == self.ID_UNDO:
1528            evt.Enable(self.CanUndo())
1529        elif id == self.ID_REDO:
1530            evt.Enable(self.CanRedo())
1531
1532
1533
1534
1535## NOTE: The DnD of file names is disabled until we can figure out how
1536## best to still allow DnD of text.
1537
1538
1539## #seb : File drag and drop
1540## class FileDropTarget(wx.FileDropTarget):
1541##     def __init__(self, obj):
1542##         wx.FileDropTarget.__init__(self)
1543##         self.obj = obj
1544##     def OnDropFiles(self, x, y, filenames):
1545##         if len(filenames) == 1:
1546##             txt = 'r\"%s\"' % filenames[0]
1547##         else:
1548##             txt = '( '
1549##             for f in filenames:
1550##                 txt += 'r\"%s\" , ' % f
1551##             txt += ')'
1552##         self.obj.AppendText(txt)
1553##         pos = self.obj.GetCurrentPos()
1554##         self.obj.SetCurrentPos( pos )
1555##         self.obj.SetSelection( pos, pos )
1556
1557
1558
1559## class TextAndFileDropTarget(wx.DropTarget):
1560##     def __init__(self, shell):
1561##         wx.DropTarget.__init__(self)
1562##         self.shell = shell
1563##         self.compdo = wx.DataObjectComposite()
1564##         self.textdo = wx.TextDataObject()
1565##         self.filedo = wx.FileDataObject()
1566##         self.compdo.Add(self.textdo)
1567##         self.compdo.Add(self.filedo, True)
1568
1569##         self.SetDataObject(self.compdo)
1570
1571##     def OnDrop(self, x, y):
1572##         return True
1573
1574##     def OnData(self, x, y, result):
1575##         self.GetData()
1576##         if self.textdo.GetTextLength() > 1:
1577##             text = self.textdo.GetText()
1578##             # *** Do somethign with the dragged text here...
1579##             self.textdo.SetText('')
1580##         else:
1581##             filenames = str(self.filename.GetFilenames())
1582##             if len(filenames) == 1:
1583##                 txt = 'r\"%s\"' % filenames[0]
1584##             else:
1585##                 txt = '( '
1586##                 for f in filenames:
1587##                     txt += 'r\"%s\" , ' % f
1588##                 txt += ')'
1589##             self.shell.AppendText(txt)
1590##             pos = self.shell.GetCurrentPos()
1591##             self.shell.SetCurrentPos( pos )
1592##             self.shell.SetSelection( pos, pos )
1593
1594##         return result
1595