"""Shell is an interactive text control in which a user types in
commands to be sent to the interpreter.  This particular shell is
based on wxPython's wxStyledTextCtrl.

Sponsored by Orbtech - Your source for Python programming expertise."""

__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"

import wx
from wx import stc
from six import PY3

import keyword
import os
import sys
import time
from functools import cmp_to_key

from .buffer import Buffer
from . import dispatcher
from . import editwindow
from . import frame
from .pseudo import PseudoFileIn
from .pseudo import PseudoFileOut
from .pseudo import PseudoFileErr
from .version import VERSION
from .magic import magic
from .path import ls,cd,pwd,sx

sys.ps3 = '<-- '  # Input prompt.
USE_MAGIC=True
# Force updates from long-running commands after this many seconds
PRINT_UPDATE_MAX_TIME=2

NAVKEYS = (wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT,
           wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)


class ShellFrame(frame.Frame, frame.ShellFrameMixin):
    """Frame containing the shell component."""

    name = 'Shell Frame'

    def __init__(self, parent=None, id=-1, title='PyShell',
                 pos=wx.DefaultPosition, size=wx.DefaultSize,
                 style=wx.DEFAULT_FRAME_STYLE, locals=None,
                 InterpClass=None,
                 config=None, dataDir=None,
                 *args, **kwds):
        """Create ShellFrame instance."""
        frame.Frame.__init__(self, parent, id, title, pos, size, style)
        frame.ShellFrameMixin.__init__(self, config, dataDir)

        if size == wx.DefaultSize:
            self.SetSize((750, 525))

        intro = 'PyShell %s - The Flakiest Python Shell' % VERSION
        self.SetStatusText(intro.replace('\n', ', '))
        self.shell = Shell(parent=self, id=-1, introText=intro,
                           locals=locals, InterpClass=InterpClass,
                           startupScript=self.startupScript,
                           execStartupScript=self.execStartupScript,
                           *args, **kwds)

        # Override the shell so that status messages go to the status bar.
        self.shell.setStatusText = self.SetStatusText

        self.shell.SetFocus()
        self.LoadSettings()


    def OnClose(self, event):
        """Event handler for closing."""
        # This isn't working the way I want, but I'll leave it for now.
        if self.shell.waiting:
            if event.CanVeto():
                event.Veto(True)
        else:
            self.SaveSettings()
            self.shell.destroy()
            self.Destroy()

    def OnAbout(self, event):
        """Display an About window."""
        title = 'About PyShell'
        text = 'PyShell %s\n\n' % VERSION + \
               'Yet another Python shell, only flakier.\n\n' + \
               'Half-baked by Patrick K. O\'Brien,\n' + \
               'the other half is still in the oven.\n\n' + \
               'Platform: %s\n' % sys.platform + \
               'Python Version: %s\n' % sys.version.split()[0] + \
               'wxPython Version: %s\n' % wx.VERSION_STRING + \
               ('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:]))
        dialog = wx.MessageDialog(self, text, title,
                                  wx.OK | wx.ICON_INFORMATION)
        dialog.ShowModal()
        dialog.Destroy()


    def OnHelp(self, event):
        """Show a help dialog."""
        frame.ShellFrameMixin.OnHelp(self, event)


    def LoadSettings(self):
        if self.config is not None:
            frame.ShellFrameMixin.LoadSettings(self)
            frame.Frame.LoadSettings(self, self.config)
            self.shell.LoadSettings(self.config)

    def SaveSettings(self, force=False):
        if self.config is not None:
            frame.ShellFrameMixin.SaveSettings(self)
            if self.autoSaveSettings or force:
                frame.Frame.SaveSettings(self, self.config)
                self.shell.SaveSettings(self.config)

    def DoSaveSettings(self):
        if self.config is not None:
            self.SaveSettings(force=True)
            self.config.Flush()




HELP_TEXT = """\
* Key bindings:
Home              Go to the beginning of the command or line.
Shift+Home        Select to the beginning of the command or line.
Shift+End         Select to the end of the line.
End               Go to the end of the line.
Ctrl+C            Copy selected text, removing prompts.
Ctrl+Shift+C      Copy selected text, retaining prompts.
Alt+C             Copy to the clipboard, including prefixed prompts.
Ctrl+X            Cut selected text.
Ctrl+V            Paste from clipboard.
Ctrl+Shift+V      Paste and run multiple commands from clipboard.
Ctrl+Up Arrow     Retrieve Previous History item.
Alt+P             Retrieve Previous History item.
Ctrl+Down Arrow   Retrieve Next History item.
Alt+N             Retrieve Next History item.
Shift+Up Arrow    Insert Previous History item.
Shift+Down Arrow  Insert Next History item.
F8                Command-completion of History item.
                  (Type a few characters of a previous command and press F8.)
Ctrl+Enter        Insert new line into multiline command.
Ctrl+]            Increase font size.
Ctrl+[            Decrease font size.
Ctrl+=            Default font size.
Ctrl-Space        Show Auto Completion.
Ctrl-Alt-Space    Show Call Tip.
Shift+Enter       Complete Text from History.
Ctrl+F            Search
F3                Search next
Ctrl+H            "hide" lines containing selection / "unhide"
F12               on/off "free-edit" mode
"""

class ShellFacade:
    """Simplified interface to all shell-related functionality.

    This is a semi-transparent facade, in that all attributes of other
    are accessible, even though only some are visible to the user."""

    name = 'Shell Interface'

    def __init__(self, other):
        """Create a ShellFacade instance."""
        d = self.__dict__
        d['other'] = other
        d['helpText'] = HELP_TEXT

    def help(self):
        """Display some useful information about how to use the shell."""
        self.write(self.helpText)

    def __getattr__(self, name):
        if hasattr(self.other, name):
            return getattr(self.other, name)
        else:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        if name in self.__dict__:
            self.__dict__[name] = value
        elif hasattr(self.other, name):
            setattr(self.other, name, value)
        else:
            raise AttributeError(name)

    def _getAttributeNames(self):
        """Return list of magic attributes to extend introspection."""
        list = [
            'about',
            'ask',
            'autoCallTip',
            'autoComplete',
            'autoCompleteAutoHide',
            'autoCompleteCaseInsensitive',
            'autoCompleteIncludeDouble',
            'autoCompleteIncludeMagic',
            'autoCompleteIncludeSingle',
            'callTipInsert',
            'clear',
            'pause',
            'prompt',
            'quit',
            'redirectStderr',
            'redirectStdin',
            'redirectStdout',
            'run',
            'runfile',
            'wrap',
            'zoom',
            ]
        list.sort()
        return list


#DNM
DISPLAY_TEXT="""
Author: %r
Py Version: %s
Python Version: %s
wxPython Version: %s
wxPython PlatformInfo: %s
Platform: %s"""

class Shell(editwindow.EditWindow):
    """Shell based on StyledTextCtrl."""

    name = 'Shell'

    def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
                 introText='', locals=None, InterpClass=None,
                 startupScript=None, execStartupScript=True,
                 useStockId=True,
                 *args, **kwds):
        """Create Shell instance."""
        editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
        self.wrap()
        if locals is None:
            import __main__
            locals = __main__.__dict__

        # Grab these so they can be restored by self.redirect* methods.
        self.stdin = sys.stdin
        self.stdout = sys.stdout
        self.stderr = sys.stderr

        # Import a default interpreter class if one isn't provided.
        if InterpClass is None:
            from .interpreter import Interpreter
        else:
            Interpreter = InterpClass

        # Create a replacement for stdin.
        self.reader = PseudoFileIn(self.readline, self.readlines)
        self.reader.input = ''
        self.reader.isreading = False

        # Set up the interpreter.
        self.interp = Interpreter(locals=locals,
                                  rawin=self.raw_input,
                                  stdin=self.reader,
                                  stdout=PseudoFileOut(self.writeOut),
                                  stderr=PseudoFileErr(self.writeErr),
                                  *args, **kwds)

        # Set up the buffer.
        self.buffer = Buffer()

        # Find out for which keycodes the interpreter will autocomplete.
        self.autoCompleteKeys = self.interp.getAutoCompleteKeys()

        # Keep track of the last non-continuation prompt positions.
        self.promptPosStart = 0
        self.promptPosEnd = 0

        # Keep track of multi-line commands.
        self.more = False

        # For use with forced updates during long-running scripts
        self.lastUpdate=None

        # Create the command history.  Commands are added into the
        # front of the list (ie. at index 0) as they are entered.
        # self.historyIndex is the current position in the history; it
        # gets incremented as you retrieve the previous command,
        # decremented as you retrieve the next, and reset when you hit
        # Enter.  self.historyIndex == -1 means you're on the current
        # command, not in the history.
        self.history = []
        self.historyIndex = -1

        #seb add mode for "free edit"
        self.noteMode = 0
        self.MarkerDefine(0,stc.STC_MARK_ROUNDRECT)  # marker for hidden
        self.searchTxt = ""

        # Assign handlers for keyboard events.
        self.Bind(wx.EVT_CHAR, self.OnChar)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)

        # Assign handler for the context menu
        self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)

        # add the option to not use the stock IDs; otherwise the context menu
        # may not work on Mac without adding the proper IDs to the menu bar
        if useStockId:
            self.ID_CUT = wx.ID_CUT
            self.ID_COPY = wx.ID_COPY
            self.ID_PASTE = wx.ID_PASTE
            self.ID_SELECTALL = wx.ID_SELECTALL
            self.ID_CLEAR = wx.ID_CLEAR
            self.ID_UNDO = wx.ID_UNDO
            self.ID_REDO = wx.ID_REDO
        else:
            self.ID_CUT = wx.NewIdRef()
            self.ID_COPY = wx.NewIdRef()
            self.ID_PASTE = wx.NewIdRef()
            self.ID_SELECTALL = wx.NewIdRef()
            self.ID_CLEAR = wx.NewIdRef()
            self.ID_UNDO = wx.NewIdRef()
            self.ID_REDO = wx.NewIdRef()

        # Assign handlers for edit events
        self.Bind(wx.EVT_MENU, lambda evt: self.Cut(), id=self.ID_CUT)
        self.Bind(wx.EVT_MENU, lambda evt: self.Copy(), id=self.ID_COPY)
        self.Bind(wx.EVT_MENU, lambda evt: self.CopyWithPrompts(), id=frame.ID_COPY_PLUS)
        self.Bind(wx.EVT_MENU, lambda evt: self.Paste(), id=self.ID_PASTE)
        self.Bind(wx.EVT_MENU, lambda evt: self.PasteAndRun(), id=frame.ID_PASTE_PLUS)
        self.Bind(wx.EVT_MENU, lambda evt: self.SelectAll(), id=self.ID_SELECTALL)
        self.Bind(wx.EVT_MENU, lambda evt: self.Clear(), id=self.ID_CLEAR)
        self.Bind(wx.EVT_MENU, lambda evt: self.Undo(), id=self.ID_UNDO)
        self.Bind(wx.EVT_MENU, lambda evt: self.Redo(), id=self.ID_REDO)

        self.Bind(wx.EVT_UPDATE_UI, lambda evt: evt.Enable(self.CanCut()), id=self.ID_CUT)
        self.Bind(wx.EVT_UPDATE_UI, lambda evt: evt.Enable(self.CanCut()), id=self.ID_CLEAR)
        self.Bind(wx.EVT_UPDATE_UI, lambda evt: evt.Enable(self.CanCopy()), id=self.ID_COPY)
        self.Bind(wx.EVT_UPDATE_UI, lambda evt: evt.Enable(self.CanCopy()), id=frame.ID_COPY_PLUS)
        self.Bind(wx.EVT_UPDATE_UI, lambda evt: evt.Enable(self.CanPaste()), id=self.ID_PASTE)
        self.Bind(wx.EVT_UPDATE_UI, lambda evt: evt.Enable(self.CanPaste()), id=frame.ID_PASTE_PLUS)
        self.Bind(wx.EVT_UPDATE_UI, lambda evt: evt.Enable(self.CanUndo()), id=self.ID_UNDO)
        self.Bind(wx.EVT_UPDATE_UI, lambda evt: evt.Enable(self.CanRedo()), id=self.ID_REDO)

        # Assign handler for idle time.
        self.waiting = False
        self.Bind(wx.EVT_IDLE, self.OnIdle)

        # Display the introductory banner information.
        self.showIntro(introText)

        # Assign some pseudo keywords to the interpreter's namespace.
        self.setBuiltinKeywords()

        # Add 'shell' to the interpreter's local namespace.
        self.setLocalShell()

        ## NOTE:  See note at bottom of this file...
        ## #seb: File drag and drop
        ## self.SetDropTarget( FileDropTarget(self) )

        # Do this last so the user has complete control over their
        # environment.  They can override anything they want.
        if execStartupScript:
            if startupScript is None:
                startupScript = os.environ.get('PYTHONSTARTUP')
            self.execStartupScript(startupScript)
        else:
            self.prompt()

        wx.CallAfter(self.ScrollToLine, 0)


    def clearHistory(self):
        self.history = []
        self.historyIndex = -1
        dispatcher.send(signal="Shell.clearHistory")


    def destroy(self):
        del self.interp

    def setFocus(self):
        """Set focus to the shell."""
        self.SetFocus()

    def OnIdle(self, event):
        """Free the CPU to do other things."""
        if self.waiting:
            time.sleep(0.05)
        event.Skip()

    def showIntro(self, text=''):
        """Display introductory text in the shell."""
        if text:
            self.write(text)
        try:
            if self.interp.introText:
                if text and not text.endswith(os.linesep):
                    self.write(os.linesep)
                self.write(self.interp.introText)
        except AttributeError:
            pass

    def setBuiltinKeywords(self):
        """Create pseudo keywords as part of builtins.

        This sets "close", "exit" and "quit" to a helpful string.
        """
        from six.moves import builtins
        builtins.close = builtins.exit = builtins.quit = \
            'Click on the close button to leave the application.'
        builtins.cd = cd
        builtins.ls = ls
        builtins.pwd = pwd
        builtins.sx = sx


    def quit(self):
        """Quit the application."""
        # XXX Good enough for now but later we want to send a close event.
        # In the close event handler we can make sure they want to
        # quit.  Other applications, like PythonCard, may choose to
        # hide rather than quit so we should just post the event and
        # let the surrounding app decide what it wants to do.
        self.write('Click on the close button to leave the application.')


    def setLocalShell(self):
        """Add 'shell' to locals as reference to ShellFacade instance."""
        self.interp.locals['shell'] = ShellFacade(other=self)


    def execStartupScript(self, startupScript):
        """Execute the user's PYTHONSTARTUP script if they have one."""
        if startupScript and os.path.isfile(startupScript):
            text = 'Startup script executed: ' + startupScript
            if PY3:
                self.push('print(%r)' % text)
                self.push('with open(%r, "r") as f:\n'
                          '    exec(f.read())\n' % (startupScript))
            else:
                self.push('print(%r); execfile(%r)' % (text, startupScript))
            self.interp.startupScript = startupScript
        else:
            self.push('')


    def about(self):
        """Display information about Py."""
        #DNM
        text = DISPLAY_TEXT % \
        (__author__, VERSION,
         sys.version.split()[0], wx.VERSION_STRING, str(wx.PlatformInfo),
         sys.platform)
        self.write(text.strip())


    def OnChar(self, event):
        """Keypress event handler.

        Only receives an event if OnKeyDown calls event.Skip() for the
        corresponding event."""

        if self.noteMode:
            event.Skip()
            return

        # Prevent modification of previously submitted
        # commands/responses.
        if not self.CanEdit():
            return
        key = event.GetKeyCode()
        currpos = self.GetCurrentPos()
        stoppos = self.promptPosEnd
        # Return (Enter) needs to be ignored in this handler.
        if key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
            pass
        elif key in self.autoCompleteKeys:
            # Usually the dot (period) key activates auto completion.
            # Get the command between the prompt and the cursor.  Add
            # the autocomplete character to the end of the command.
            if self.AutoCompActive():
                self.AutoCompCancel()
            command = self.GetTextRange(stoppos, currpos) + chr(key)
            self.write(chr(key))
            if self.autoComplete:
                self.autoCompleteShow(command)
        elif key == ord('('):
            # The left paren activates a call tip and cancels an
            # active auto completion.
            if self.AutoCompActive():
                self.AutoCompCancel()
            # Get the command between the prompt and the cursor.  Add
            # the '(' to the end of the command.
            self.ReplaceSelection('')
            command = self.GetTextRange(stoppos, currpos) + '('
            self.write('(')
            self.autoCallTipShow(command, self.GetCurrentPos() == self.GetTextLength())
        else:
            # Allow the normal event handling to take place.
            event.Skip()


    def OnKeyDown(self, event):
        """Key down event handler."""

        key = event.GetKeyCode()
        # If the auto-complete window is up let it do its thing.
        if self.AutoCompActive():
            event.Skip()
            return

        # Prevent modification of previously submitted
        # commands/responses.
        controlDown = event.ControlDown()
        rawControlDown = event.RawControlDown()
        altDown = event.AltDown()
        shiftDown = event.ShiftDown()
        currpos = self.GetCurrentPos()
        endpos = self.GetTextLength()
        selecting = self.GetSelectionStart() != self.GetSelectionEnd()

        if (rawControlDown or controlDown) and shiftDown and key in (ord('F'), ord('f')):
            li = self.GetCurrentLine()
            m = self.MarkerGet(li)
            if m & 1<<0:
                startP = self.PositionFromLine(li)
                self.MarkerDelete(li, 0)
                maxli = self.GetLineCount()
                li += 1 # li stayed visible as header-line
                li0 = li
                while li<maxli and self.GetLineVisible(li) == 0:
                    li += 1
                endP = self.GetLineEndPosition(li-1)
                self.ShowLines(li0, li-1)
                # select reappearing text to allow "hide again"
                self.SetSelection( startP, endP )
                return
            startP,endP = self.GetSelection()
            endP-=1
            startL = self.LineFromPosition(startP)
            endL = self.LineFromPosition(endP)

            # never hide last prompt
            if endL == self.LineFromPosition(self.promptPosEnd):
                endL -= 1

            m = self.MarkerGet(startL)
            self.MarkerAdd(startL, 0)
            self.HideLines(startL+1,endL)
            self.SetCurrentPos( startP ) # to ensure caret stays visible !

        if key == wx.WXK_F12: #seb
            if self.noteMode:
                # self.promptPosStart not used anyway - or ?
                self.promptPosEnd = \
                   self.PositionFromLine( self.GetLineCount()-1 ) + \
                   len(str(sys.ps1))
                self.GotoLine(self.GetLineCount())
                self.GotoPos(self.promptPosEnd)
                self.prompt()  #make sure we have a prompt
                self.SetCaretForeground("black")
                self.SetCaretWidth(1)    #default
                self.SetCaretPeriod(500) #default
            else:
                self.SetCaretForeground("red")
                self.SetCaretWidth(4)
                self.SetCaretPeriod(0) #steady

            self.noteMode = not self.noteMode
            return
        if self.noteMode:
            event.Skip()
            return

        # Return (Enter) is used to submit a command to the
        # interpreter.
        if (not (rawControlDown or controlDown) and not shiftDown and not altDown) and \
           key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
            if self.CallTipActive():
                self.CallTipCancel()
            self.processLine()

        # Complete Text (from already typed words)
        elif shiftDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
            self.OnShowCompHistory()

        # Ctrl+Return (Ctrl+Enter) is used to insert a line break.
        elif (rawControlDown or controlDown) and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
            if self.CallTipActive():
                self.CallTipCancel()
            if currpos == endpos:
                self.processLine()
            else:
                self.insertLineBreak()

        # Let Ctrl-Alt-* get handled normally.
        elif (rawControlDown or controlDown) and altDown:
            event.Skip()

        # Clear the current, unexecuted command.
        elif key == wx.WXK_ESCAPE:
            if self.CallTipActive():
                event.Skip()
            else:
                self.clearCommand()

        # Clear the current command
        elif key == wx.WXK_BACK and (rawControlDown or controlDown) and shiftDown:
            self.clearCommand()

        # Increase font size.
        elif (rawControlDown or controlDown) and key in (ord(']'), wx.WXK_NUMPAD_ADD):
            dispatcher.send(signal='FontIncrease')

        # Decrease font size.
        elif (rawControlDown or controlDown) and key in (ord('['), wx.WXK_NUMPAD_SUBTRACT):
            dispatcher.send(signal='FontDecrease')

        # Default font size.
        elif (rawControlDown or controlDown) and key in (ord('='), wx.WXK_NUMPAD_DIVIDE):
            dispatcher.send(signal='FontDefault')

        # Cut to the clipboard.
        elif ((rawControlDown or controlDown) and key in (ord('X'), ord('x'))) \
                 or (shiftDown and key == wx.WXK_DELETE):
            self.Cut()

        # Copy to the clipboard.
        elif (rawControlDown or controlDown) and not shiftDown \
                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
            self.Copy()

        # Copy to the clipboard, including prompts.
        elif (rawControlDown or controlDown) and shiftDown \
                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
            self.CopyWithPrompts()

        # Copy to the clipboard, including prefixed prompts.
        elif altDown and not controlDown \
                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
            self.CopyWithPromptsPrefixed()

        # Home needs to be aware of the prompt.
        elif (rawControlDown or controlDown) and key == wx.WXK_HOME:
            home = self.promptPosEnd
            if currpos > home:
                self.SetCurrentPos(home)
                if not selecting and not shiftDown:
                    self.SetAnchor(home)
                    self.EnsureCaretVisible()
            else:
                event.Skip()

        # Home needs to be aware of the prompt.
        elif key == wx.WXK_HOME:
            home = self.promptPosEnd
            if currpos > home:
                [line_str,line_len] = self.GetCurLine()
                pos=self.GetCurrentPos()
                if line_str[:4] in [sys.ps1,sys.ps2,sys.ps3]:
                    self.SetCurrentPos(pos+4-line_len)
                    #self.SetCurrentPos(home)
                    if not selecting and not shiftDown:
                        self.SetAnchor(pos+4-line_len)
                        self.EnsureCaretVisible()
                else:
                    event.Skip()
            else:
                event.Skip()

        #
        # The following handlers modify text, so we need to see if
        # there is a selection that includes text prior to the prompt.
        #
        # Don't modify a selection with text prior to the prompt.
        elif selecting and key not in NAVKEYS and not self.CanEdit():
            pass

        # Paste from the clipboard.
        elif ((rawControlDown or controlDown) and not shiftDown and key in (ord('V'), ord('v'))) \
                 or (shiftDown and not controlDown and key == wx.WXK_INSERT):
            self.Paste()

        # manually invoke AutoComplete and Calltips
        elif (rawControlDown or controlDown) and key == wx.WXK_SPACE:
            self.OnCallTipAutoCompleteManually(shiftDown)

        # Paste from the clipboard, run commands.
        elif (rawControlDown or controlDown) and shiftDown and key in (ord('V'), ord('v')):
            self.PasteAndRun()

        # Replace with the previous command from the history buffer.
        elif ((rawControlDown or controlDown) and not shiftDown and key == wx.WXK_UP) \
                 or (altDown and key in (ord('P'), ord('p'))):
            self.OnHistoryReplace(step=+1)

        # Replace with the next command from the history buffer.
        elif ((rawControlDown or controlDown) and not shiftDown and key == wx.WXK_DOWN) \
                 or (altDown and key in (ord('N'), ord('n'))):
            self.OnHistoryReplace(step=-1)

        # Insert the previous command from the history buffer.
        elif ((rawControlDown or controlDown) and shiftDown and key == wx.WXK_UP) and self.CanEdit():
            self.OnHistoryInsert(step=+1)

        # Insert the next command from the history buffer.
        elif ((rawControlDown or controlDown) and shiftDown and key == wx.WXK_DOWN) and self.CanEdit():
            self.OnHistoryInsert(step=-1)

        # Search up the history for the text in front of the cursor.
        elif key == wx.WXK_F8:
            self.OnHistorySearch()

        # Don't backspace over the latest non-continuation prompt.
        elif key == wx.WXK_BACK:
            if selecting and self.CanEdit():
                event.Skip()
            elif currpos > self.promptPosEnd:
                event.Skip()

        # Only allow these keys after the latest prompt.
        elif key in (wx.WXK_TAB, wx.WXK_DELETE):
            if self.CanEdit():
                event.Skip()

        # Don't toggle between insert mode and overwrite mode.
        elif key == wx.WXK_INSERT:
            pass

        # Don't allow line deletion.
        elif controlDown and key in (ord('L'), ord('l')):
            # TODO : Allow line deletion eventually...
            #event.Skip()
            pass

        # Don't allow line transposition.
        elif controlDown and key in (ord('T'), ord('t')):
            # TODO : Allow line transposition eventually...
            # TODO : Will have to adjust markers accordingly and test if allowed...
            #event.Skip()
            pass

        # Basic navigation keys should work anywhere.
        elif key in NAVKEYS:
            event.Skip()

        # Protect the readonly portion of the shell.
        elif not self.CanEdit():
            pass

        else:
            event.Skip()


    def OnShowCompHistory(self):
        """Show possible autocompletion Words from already typed words."""

        #copy from history
        his = self.history[:]

        #put together in one string
        joined = " ".join (his)
        import re

        #sort out only "good" words
        newlist = re.split(r"[ \.\[\]=}(\)\,0-9\"]", joined)

        #length > 1 (mix out "trash")
        thlist = []
        for i in newlist:
            if len (i) > 1:
                thlist.append (i)

        #unique (no duplicate words
        #oneliner from german python forum => unique list
        unlist = [thlist[i] for i in range(len(thlist)) if thlist[i] not in thlist[:i]]

        #sort lowercase
        def _cmp(a,b):
            return  ((a > b) - (a < b))
        unlist.sort(key=cmp_to_key(lambda a, b: _cmp(a.lower(), b.lower())))

        #this is more convenient, isn't it?
        self.AutoCompSetIgnoreCase(True)

        #join again together in a string
        stringlist = " ".join(unlist)

        #pos von 0 noch ausrechnen

        #how big is the offset?
        cpos = self.GetCurrentPos() - 1
        while chr (self.GetCharAt (cpos)).isalnum():
            cpos -= 1

        #the most important part
        self.AutoCompShow(self.GetCurrentPos() - cpos -1, stringlist)


    def clearCommand(self):
        """Delete the current, unexecuted command."""
        startpos = self.promptPosEnd
        endpos = self.GetTextLength()
        self.SetSelection(startpos, endpos)
        self.ReplaceSelection('')
        self.more = False

    def OnHistoryReplace(self, step):
        """Replace with the previous/next command from the history buffer."""
        self.clearCommand()
        self.replaceFromHistory(step)

    def replaceFromHistory(self, step):
        """Replace selection with command from the history buffer."""
        ps2 = str(sys.ps2)
        self.ReplaceSelection('')
        newindex = self.historyIndex + step
        if -1 <= newindex <= len(self.history):
            self.historyIndex = newindex
        if 0 <= newindex <= len(self.history)-1:
            command = self.history[self.historyIndex]
            command = command.replace('\n', os.linesep + ps2)
            self.ReplaceSelection(command)

    def OnHistoryInsert(self, step):
        """Insert the previous/next command from the history buffer."""
        if not self.CanEdit():
            return
        startpos = self.GetCurrentPos()
        self.replaceFromHistory(step)
        endpos = self.GetCurrentPos()
        self.SetSelection(endpos, startpos)

    def OnHistorySearch(self):
        """Search up the history buffer for the text in front of the cursor."""
        if not self.CanEdit():
            return
        startpos = self.GetCurrentPos()
        # The text up to the cursor is what we search for.
        numCharsAfterCursor = self.GetTextLength() - startpos
        searchText = self.getCommand(rstrip=False)
        if numCharsAfterCursor > 0:
            searchText = searchText[:-numCharsAfterCursor]
        if not searchText:
            return
        # Search upwards from the current history position and loop
        # back to the beginning if we don't find anything.
        if (self.historyIndex <= -1) \
        or (self.historyIndex >= len(self.history)-2):
            searchOrder = range(len(self.history))
        else:
            ls = list(range(len(self.history)))
            searchOrder = ls[self.historyIndex+1:] + ls[:self.historyIndex]
        for i in searchOrder:
            command = self.history[i]
            if command[:len(searchText)] == searchText:
                # Replace the current selection with the one we found.
                self.ReplaceSelection(command[len(searchText):])
                endpos = self.GetCurrentPos()
                self.SetSelection(endpos, startpos)
                # We've now warped into middle of the history.
                self.historyIndex = i
                break

    def setStatusText(self, text):
        """Display status information."""

        # This method will likely be replaced by the enclosing app to
        # do something more interesting, like write to a status bar.
        print(text)

    def insertLineBreak(self):
        """Insert a new line break."""
        if self.CanEdit():
            self.write(os.linesep)
            self.more = True
            self.prompt()

    def processLine(self):
        """Process the line of text at which the user hit Enter."""

        # The user hit ENTER and we need to decide what to do. They
        # could be sitting on any line in the shell.

        thepos = self.GetCurrentPos()
        startpos = self.promptPosEnd
        endpos = self.GetTextLength()
        ps2 = str(sys.ps2)
        # If they hit RETURN inside the current command, execute the
        # command.
        if self.CanEdit():
            self.SetCurrentPos(endpos)
            self.interp.more = False
            command = self.GetTextRange(startpos, endpos)
            lines = command.split(os.linesep + ps2)
            lines = [line.rstrip() for line in lines]
            command = '\n'.join(lines)
            if self.reader.isreading:
                if not command:
                    # Match the behavior of the standard Python shell
                    # when the user hits return without entering a
                    # value.
                    command = '\n'
                self.reader.input = command
                self.write(os.linesep)
            else:
                self.push(command)
                wx.CallLater(1, self.EnsureCaretVisible)
        # Or replace the current command with the other command.
        else:
            # If the line contains a command (even an invalid one).
            if self.getCommand(rstrip=False):
                command = self.getMultilineCommand()
                self.clearCommand()
                self.write(command)
            # Otherwise, put the cursor back where we started.
            else:
                self.SetCurrentPos(thepos)
                self.SetAnchor(thepos)

    def getMultilineCommand(self, rstrip=True):
        """Extract a multi-line command from the editor.

        The command may not necessarily be valid Python syntax."""
        # XXX Need to extract real prompts here. Need to keep track of
        # the prompt every time a command is issued.
        ps1 = str(sys.ps1)
        ps1size = len(ps1)
        ps2 = str(sys.ps2)
        ps2size = len(ps2)
        # This is a total hack job, but it works.
        text = self.GetCurLine()[0]
        line = self.GetCurrentLine()
        while text[:ps2size] == ps2 and line > 0:
            line -= 1
            self.GotoLine(line)
            text = self.GetCurLine()[0]
        if text[:ps1size] == ps1:
            line = self.GetCurrentLine()
            self.GotoLine(line)
            startpos = self.GetCurrentPos() + ps1size
            line += 1
            self.GotoLine(line)
            while self.GetCurLine()[0][:ps2size] == ps2 and line < self.LineCount:
                line += 1
                self.GotoLine(line)
            stoppos = self.GetCurrentPos()
            command = self.GetTextRange(startpos, stoppos)
            command = command.replace(os.linesep + ps2, '\n')
            command = command.rstrip()
            command = command.replace('\n', os.linesep + ps2)
        else:
            command = ''
        if rstrip:
            command = command.rstrip()
        return command

    def getCommand(self, text=None, rstrip=True):
        """Extract a command from text which may include a shell prompt.

        The command may not necessarily be valid Python syntax."""
        if not text:
            text = self.GetCurLine()[0]
        # Strip the prompt off the front leaving just the command.
        command = self.lstripPrompt(text)
        if command == text:
            command = ''  # Real commands have prompts.
        if rstrip:
            command = command.rstrip()
        return command

    def lstripPrompt(self, text):
        """Return text without a leading prompt."""
        ps1 = str(sys.ps1)
        ps1size = len(ps1)
        ps2 = str(sys.ps2)
        ps2size = len(ps2)
        # Strip the prompt off the front of text.
        if text[:ps1size] == ps1:
            text = text[ps1size:]
        elif text[:ps2size] == ps2:
            text = text[ps2size:]
        return text

    def push(self, command, silent = False):
        """Send command to the interpreter for execution."""
        if not silent:
            self.write(os.linesep)

        #DNM
        if USE_MAGIC:
            command=magic(command)

        busy = wx.BusyCursor()
        self.waiting = True
        self.lastUpdate=None
        self.more = self.interp.push(command)
        self.lastUpdate=None
        self.waiting = False
        del busy
        if not self.more:
            self.addHistory(command.rstrip())
        if not silent:
            self.prompt()

    def addHistory(self, command):
        """Add command to the command history."""
        # Reset the history position.
        self.historyIndex = -1
        # Insert this command into the history, unless it's a blank
        # line or the same as the last command.
        if command != '' \
        and (len(self.history) == 0 or command != self.history[0]):
            self.history.insert(0, command)
            dispatcher.send(signal="Shell.addHistory", command=command)

    def write(self, text):
        """Display text in the shell.

        Replace line endings with OS-specific endings."""
        text = self.fixLineEndings(text)
        self.AddText(text)
        self.EnsureCaretVisible()

        if self.waiting:
            if self.lastUpdate==None:
                self.lastUpdate=time.time()
            if time.time()-self.lastUpdate > PRINT_UPDATE_MAX_TIME:
                self.Update()
                self.lastUpdate=time.time()

    def fixLineEndings(self, text):
        """Return text with line endings replaced by OS-specific endings."""
        lines = text.split('\r\n')
        for l in range(len(lines)):
            chunks = lines[l].split('\r')
            for c in range(len(chunks)):
                chunks[c] = os.linesep.join(chunks[c].split('\n'))
            lines[l] = os.linesep.join(chunks)
        text = os.linesep.join(lines)
        return text

    def prompt(self):
        """Display proper prompt for the context: ps1, ps2 or ps3.

        If this is a continuation line, autoindent as necessary."""
        isreading = self.reader.isreading
        skip = False
        if isreading:
            prompt = str(sys.ps3)
        elif self.more:
            prompt = str(sys.ps2)
        else:
            prompt = str(sys.ps1)
        pos = self.GetCurLine()[1]
        if pos > 0:
            if isreading:
                skip = True
            else:
                self.write(os.linesep)
        if not self.more:
            self.promptPosStart = self.GetCurrentPos()
        if not skip:
            self.write(prompt)
        if not self.more:
            self.promptPosEnd = self.GetCurrentPos()
            # Keep the undo feature from undoing previous responses.
            self.EmptyUndoBuffer()

        if self.more:
            line_num=self.GetCurrentLine()
            currentLine=self.GetLine(line_num)
            previousLine=self.GetLine(line_num-1)[len(prompt):]
            pstrip=previousLine.strip()
            lstrip=previousLine.lstrip()

            # Get the first alnum word:
            first_word=[]
            for i in pstrip:
                if i.isalnum():
                    first_word.append(i)
                else:
                    break
            first_word = ''.join(first_word)

            if pstrip == '':
                # because it is all whitespace!
                indent=previousLine.strip('\n').strip('\r')
            else:
                indent=previousLine[:(len(previousLine)-len(lstrip))]
                if pstrip[-1]==':' and \
                    first_word in ['if','else','elif','for','while','with',
                                   'def','class','try','except','finally']:
                    indent+=' '*4

            self.write(indent)
        self.EnsureCaretVisible()
        self.ScrollToColumn(0)

    def readline(self):
        """Replacement for stdin.readline()."""
        input = ''
        reader = self.reader
        reader.isreading = True
        self.prompt()
        try:
            while not reader.input:
                wx.GetApp().Yield(onlyIfNeeded=True)
            input = reader.input
        finally:
            reader.input = ''
            reader.isreading = False
        input = str(input)  # In case of Unicode.
        return input

    def readlines(self):
        """Replacement for stdin.readlines()."""
        lines = []
        while lines[-1:] != ['\n']:
            lines.append(self.readline())
        return lines

    def raw_input(self, prompt=''):
        """Return string based on user input."""
        if prompt:
            self.write(prompt)
        return self.readline()

    def ask(self, prompt='Please enter your response:'):
        """Get response from the user using a dialog box."""
        dialog = wx.TextEntryDialog(None, prompt,
                                    'Input Dialog (Raw)', '')
        try:
            if dialog.ShowModal() == wx.ID_OK:
                text = dialog.GetValue()
                return text
        finally:
            dialog.Destroy()
        return ''

    def pause(self):
        """Halt execution pending a response from the user."""
        self.ask('Press enter to continue:')

    def clear(self):
        """Delete all text from the shell."""
        self.ClearAll()

    def run(self, command, prompt=True, verbose=True):
        """Execute command as if it was typed in directly.
        >>> shell.run('print("this")')
        >>> print("this")
        this
        >>>
        """
        # Go to the very bottom of the text.
        endpos = self.GetTextLength()
        self.SetCurrentPos(endpos)
        command = command.rstrip()
        if prompt: self.prompt()
        if verbose: self.write(command)
        self.push(command)

    def runfile(self, filename):
        """Execute all commands in file as if they were typed into the
        shell."""
        self.prompt()
        with open(filename) as file_:
            for command in file_:
                if command[:6] == 'shell.':
                    # Run shell methods silently.
                    self.run(command, prompt=False, verbose=False)
                else:
                    self.run(command, prompt=False, verbose=True)

    def autoCompleteShow(self, command, offset = 0):
        """Display auto-completion popup list."""
        self.AutoCompSetAutoHide(self.autoCompleteAutoHide)
        self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
        list = self.interp.getAutoCompleteList(command,
                    includeMagic=self.autoCompleteIncludeMagic,
                    includeSingle=self.autoCompleteIncludeSingle,
                    includeDouble=self.autoCompleteIncludeDouble)
        if list:
            options = ' '.join(list)
            #offset = 0
            self.AutoCompShow(offset, options)

    def autoCallTipShow(self, command, insertcalltip = True, forceCallTip = False):
        """Display argument spec and docstring in a popup window."""
        if self.CallTipActive():
            self.CallTipCancel()
        (name, argspec, tip) = self.interp.getCallTip(command)
        if tip:
            dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
        if not self.autoCallTip and not forceCallTip:
            return
        startpos = self.GetCurrentPos()
        if argspec and insertcalltip and self.callTipInsert:
            self.write(argspec + ')')
            endpos = self.GetCurrentPos()
            self.SetSelection(startpos, endpos)
        if tip:
            tippos = startpos - (len(name) + 1)
            fallback = startpos - self.GetColumn(startpos)
            # In case there isn't enough room, only go back to the
            # fallback.
            tippos = max(tippos, fallback)
            self.CallTipShow(tippos, tip)

    def OnCallTipAutoCompleteManually (self, shiftDown):
        """AutoComplete and Calltips manually."""
        if self.AutoCompActive():
            self.AutoCompCancel()
        currpos = self.GetCurrentPos()
        stoppos = self.promptPosEnd

        cpos = currpos
        #go back until '.' is found
        pointavailpos = -1
        while cpos >= stoppos:
            if self.GetCharAt(cpos) == ord ('.'):
                pointavailpos = cpos
                break
            cpos -= 1

        #word from non whitespace until '.'
        if pointavailpos != -1:
            #look backward for first whitespace char
            textbehind = self.GetTextRange (pointavailpos + 1, currpos)
            pointavailpos += 1

            if not shiftDown:
                #call AutoComplete
                stoppos = self.promptPosEnd
                textbefore = self.GetTextRange(stoppos, pointavailpos)
                self.autoCompleteShow(textbefore, len (textbehind))
            else:
                #call CallTips
                cpos = pointavailpos
                begpos = -1
                while cpos > stoppos:
                    if chr(self.GetCharAt(cpos)).isspace():
                        begpos = cpos
                        break
                    cpos -= 1
                if begpos == -1:
                    begpos = cpos
                ctips = self.GetTextRange (begpos, currpos)
                ctindex = ctips.find ('(')
                if ctindex != -1 and not self.CallTipActive():
                    #insert calltip, if current pos is '(', otherwise show it only
                    self.autoCallTipShow(ctips[:ctindex + 1],
                        self.GetCharAt(currpos - 1) == ord('(') and \
                           self.GetCurrentPos() == self.GetTextLength(),
                        True)


    def writeOut(self, text):
        """Replacement for stdout."""
        self.write(text)

    def writeErr(self, text):
        """Replacement for stderr."""
        self.write(text)

    def redirectStdin(self, redirect=True):
        """If redirect is true then sys.stdin will come from the shell."""
        if redirect:
            sys.stdin = self.reader
        else:
            sys.stdin = self.stdin

    def redirectStdout(self, redirect=True):
        """If redirect is true then sys.stdout will go to the shell."""
        if redirect:
            sys.stdout = PseudoFileOut(self.writeOut)
        else:
            sys.stdout = self.stdout

    def redirectStderr(self, redirect=True):
        """If redirect is true then sys.stderr will go to the shell."""
        if redirect:
            sys.stderr = PseudoFileErr(self.writeErr)
        else:
            sys.stderr = self.stderr

    def CanCut(self):
        """Return true if text is selected and can be cut."""
        if self.GetSelectionStart() != self.GetSelectionEnd() \
               and self.GetSelectionStart() >= self.promptPosEnd \
               and self.GetSelectionEnd() >= self.promptPosEnd:
            return True
        else:
            return False

    def CanPaste(self):
        """Return true if a paste should succeed."""
        if self.CanEdit() and editwindow.EditWindow.CanPaste(self):
            return True
        else:
            return False

    def CanEdit(self):
        """Return true if editing should succeed."""
        if self.GetSelectionStart() != self.GetSelectionEnd():
            if self.GetSelectionStart() >= self.promptPosEnd \
                   and self.GetSelectionEnd() >= self.promptPosEnd:
                return True
            else:
                return False
        else:
            return self.GetCurrentPos() >= self.promptPosEnd

    def Cut(self):
        """Remove selection and place it on the clipboard."""
        if self.CanCut() and self.CanCopy():
            if self.AutoCompActive():
                self.AutoCompCancel()
            if self.CallTipActive():
                self.CallTipCancel()
            self.Copy()
            self.ReplaceSelection('')

    def Copy(self):
        """Copy selection and place it on the clipboard."""
        if self.CanCopy():
            ps1 = str(sys.ps1)
            ps2 = str(sys.ps2)
            command = self.GetSelectedText()
            command = command.replace(os.linesep + ps2, os.linesep)
            command = command.replace(os.linesep + ps1, os.linesep)
            command = self.lstripPrompt(text=command)
            data = wx.TextDataObject(command)
            self._clip(data)

    def CopyWithPrompts(self):
        """Copy selection, including prompts, and place it on the clipboard."""
        if self.CanCopy():
            command = self.GetSelectedText()
            data = wx.TextDataObject(command)
            self._clip(data)

    def CopyWithPromptsPrefixed(self):
        """Copy selection, including prompts prefixed with four
        spaces, and place it on the clipboard."""
        if self.CanCopy():
            command = self.GetSelectedText()
            spaces = ' ' * 4
            command = spaces + command.replace(os.linesep,
                                               os.linesep + spaces)
            data = wx.TextDataObject(command)
            self._clip(data)

    def _clip(self, data):
        if wx.TheClipboard.Open():
            wx.TheClipboard.UsePrimarySelection(False)
            wx.TheClipboard.SetData(data)
            wx.TheClipboard.Flush()
            wx.TheClipboard.Close()

    def Paste(self):
        """Replace selection with clipboard contents."""
        if self.CanPaste() and wx.TheClipboard.Open():
            ps2 = str(sys.ps2)
            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_UNICODETEXT)):
                data = wx.TextDataObject()
                if wx.TheClipboard.GetData(data):
                    self.ReplaceSelection('')
                    command = data.GetText()
                    command = command.rstrip()
                    command = self.fixLineEndings(command)
                    command = self.lstripPrompt(text=command)
                    command = command.replace(os.linesep + ps2, '\n')
                    command = command.replace(os.linesep, '\n')
                    command = command.replace('\n', os.linesep + ps2)
                    self.write(command)
            wx.TheClipboard.Close()


    def PasteAndRun(self):
        """Replace selection with clipboard contents, run commands."""
        text = ''
        if wx.TheClipboard.Open():
            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_UNICODETEXT)):
                data = wx.TextDataObject()
                if wx.TheClipboard.GetData(data):
                    text = data.GetText()
            wx.TheClipboard.Close()
        if text:
            self.Execute(text)


    def Execute(self, text):
        """Replace selection with text and run commands."""
        ps1 = str(sys.ps1)
        ps2 = str(sys.ps2)
        endpos = self.GetTextLength()
        self.SetCurrentPos(endpos)
        startpos = self.promptPosEnd
        self.SetSelection(startpos, endpos)
        self.ReplaceSelection('')
        text = text.lstrip()
        text = self.fixLineEndings(text)
        text = self.lstripPrompt(text)
        text = text.replace(os.linesep + ps1, '\n')
        text = text.replace(os.linesep + ps2, '\n')
        text = text.replace(os.linesep, '\n')
        lines = text.split('\n')
        commands = []
        command = ''
        for line in lines:
            if line.strip() == ps2.strip():
                # If we are pasting from something like a
                # web page that drops the trailing space
                # from the ps2 prompt of a blank line.
                line = ''
            lstrip = line.lstrip()
            if line.strip() != '' and lstrip == line and \
                    lstrip[:4] not in ['else','elif'] and \
                    lstrip[:6] != 'except' and \
                    lstrip[:7] != 'finally':
                # New command.
                if command:
                    # Add the previous command to the list.
                    commands.append(command)
                # Start a new command, which may be multiline.
                command = line
            else:
                # Multiline command. Add to the command.
                command += '\n'
                command += line
        commands.append(command)
        for command in commands:
            command = command.replace('\n', os.linesep + ps2)
            self.write(command)
            self.processLine()


    def wrap(self, wrap=True):
        """Sets whether text is word wrapped."""
        self.SetWrapMode(wrap)

    def zoom(self, points=0):
        """Set the zoom level.

        This number of points is added to the size of all fonts.  It
        may be positive to magnify or negative to reduce."""
        self.SetZoom(points)



    def LoadSettings(self, config):
        self.autoComplete              = \
            config.ReadBool('Options/AutoComplete', True)
        self.autoCompleteIncludeMagic  = \
            config.ReadBool('Options/AutoCompleteIncludeMagic', True)
        self.autoCompleteIncludeSingle = \
            config.ReadBool('Options/AutoCompleteIncludeSingle', True)
        self.autoCompleteIncludeDouble = \
            config.ReadBool('Options/AutoCompleteIncludeDouble', True)

        self.autoCallTip = config.ReadBool('Options/AutoCallTip', True)
        self.callTipInsert = config.ReadBool('Options/CallTipInsert', True)
        self.SetWrapMode(config.ReadBool('View/WrapMode', True))

        self.lineNumbers = config.ReadBool('View/ShowLineNumbers', True)
        self.setDisplayLineNumbers (self.lineNumbers)
        zoom = config.ReadInt('View/Zoom/Shell', -99)
        if zoom != -99:
            self.SetZoom(zoom)


    def SaveSettings(self, config):
        config.WriteBool('Options/AutoComplete', self.autoComplete)
        config.WriteBool('Options/AutoCompleteIncludeMagic',
                         self.autoCompleteIncludeMagic)
        config.WriteBool('Options/AutoCompleteIncludeSingle',
                         self.autoCompleteIncludeSingle)
        config.WriteBool('Options/AutoCompleteIncludeDouble',
                         self.autoCompleteIncludeDouble)
        config.WriteBool('Options/AutoCallTip', self.autoCallTip)
        config.WriteBool('Options/CallTipInsert', self.callTipInsert)
        config.WriteBool('View/WrapMode', self.GetWrapMode())
        config.WriteBool('View/ShowLineNumbers', self.lineNumbers)
        config.WriteInt('View/Zoom/Shell', self.GetZoom())

    def GetContextMenu(self):
        """
            Create and return a context menu for the shell.
            This is used instead of the scintilla default menu
            in order to correctly respect our immutable buffer.
        """
        menu = wx.Menu()
        menu.Append(self.ID_UNDO, "Undo")
        menu.Append(self.ID_REDO, "Redo")

        menu.AppendSeparator()

        menu.Append(self.ID_CUT, "Cut")
        menu.Append(self.ID_COPY, "Copy")
        menu.Append(frame.ID_COPY_PLUS, "Copy With Prompts")
        menu.Append(self.ID_PASTE, "Paste")
        menu.Append(frame.ID_PASTE_PLUS, "Paste And Run")
        menu.Append(self.ID_CLEAR, "Clear")

        menu.AppendSeparator()

        menu.Append(self.ID_SELECTALL, "Select All")
        return menu

    def OnContextMenu(self, evt):
        menu = self.GetContextMenu()
        self.PopupMenu(menu)




## NOTE: The DnD of file names is disabled until we can figure out how
## best to still allow DnD of text.


## #seb : File drag and drop
## class FileDropTarget(wx.FileDropTarget):
##     def __init__(self, obj):
##         wx.FileDropTarget.__init__(self)
##         self.obj = obj
##     def OnDropFiles(self, x, y, filenames):
##         if len(filenames) == 1:
##             txt = 'r\"%s\"' % filenames[0]
##         else:
##             txt = '( '
##             for f in filenames:
##                 txt += 'r\"%s\" , ' % f
##             txt += ')'
##         self.obj.AppendText(txt)
##         pos = self.obj.GetCurrentPos()
##         self.obj.SetCurrentPos( pos )
##         self.obj.SetSelection( pos, pos )



## class TextAndFileDropTarget(wx.DropTarget):
##     def __init__(self, shell):
##         wx.DropTarget.__init__(self)
##         self.shell = shell
##         self.compdo = wx.DataObjectComposite()
##         self.textdo = wx.TextDataObject()
##         self.filedo = wx.FileDataObject()
##         self.compdo.Add(self.textdo)
##         self.compdo.Add(self.filedo, True)

##         self.SetDataObject(self.compdo)

##     def OnDrop(self, x, y):
##         return True

##     def OnData(self, x, y, result):
##         self.GetData()
##         if self.textdo.GetTextLength() > 1:
##             text = self.textdo.GetText()
##             # *** Do something with the dragged text here...
##             self.textdo.SetText('')
##         else:
##             filenames = str(self.filename.GetFilenames())
##             if len(filenames) == 1:
##                 txt = 'r\"%s\"' % filenames[0]
##             else:
##                 txt = '( '
##                 for f in filenames:
##                     txt += 'r\"%s\" , ' % f
##                 txt += ')'
##             self.shell.AppendText(txt)
##             pos = self.shell.GetCurrentPos()
##             self.shell.SetCurrentPos( pos )
##             self.shell.SetSelection( pos, pos )

##         return result
