# -*- coding: utf-8 -*-

# Copyright (c) 2002 - 2006 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a graphical Python shell.
"""

import sys

from qt import *
from qtext import QextScintilla, QextScintillaLexerPython

from KdeQt import KQMessageBox, KQInputDialog

from QextScintillaCompat import QextScintillaCompat, QSCINTILLA_VERSION
from Editor import Editor

import Preferences
import UI.PixmapCache
from Utilities import toUnicode

from DebugClients.Python.DebugClientCapabilities import HasShell, HasCompleter

class Shell(QextScintillaCompat):
    """
    Module implementing a graphical Python shell.
    
    A user can enter commands that are executed in the remote 
    Python interpreter.  
    """
    def __init__(self,dbs,vm,parent=None):
        """
        Constructor
        
        @param dbs reference to the debug server object
        @param vm reference to the viewmanager object
        @param parent parent widget (QWidget)
        """
        QextScintillaCompat.__init__(self,parent)
        self.setUtf8(1)
        
        self.vm = vm
        
        self.linesepRegExp = QRegExp(r"\r\n|\n|\r")

        self.passive = Preferences.getDebugger("PassiveDbgEnabled")
        if self.passive:
            self.setCaption(self.trUtf8('Shell - Passive'))
        else:
            self.setCaption(self.trUtf8('Shell'))

        QWhatsThis.add(self,self.trUtf8(
"""<b>The Shell Window</b>"""
"""<p>This is simply an interpreter running in a window. The"""
""" interpreter is the one that is used to run the program being debugged."""
""" This means that you can execute any command while the program"""
""" being debugged is running.</p>"""
"""<p>You can use the cursor keys while entering commands. There is also a"""
""" history of commands that can be recalled using the up and down cursor"""
""" keys. Pressing the up or down key after some text has been entered will"""
""" start an incremental search.</p>"""
"""<p>The shell has some special commands. 'reset' kills the shell"""
""" and starts a new one. 'clear' clears the display of the shell window."""
""" 'start' is used to switch the shell language and must be followed by"""
""" either 'Python' or 'Ruby'."""
""" These commands are available through the context menu as well.</p>"""
"""<p>Pressing the Tab key after some text has been entered will show"""
""" a list of possible commandline completions. The relevant entry may"""
""" be selected from this list. If only one entry is available, this will"""
""" inserted automatically.</p>"""
"""<p>In passive debugging mode the shell is only available after the"""
""" program to be debugged has connected to the IDE until it has finished."""
""" This is indicated by a different prompt and by an indication in the"""
""" window caption.</p>"""
                      ))

        self.connect(self, SIGNAL('SCN_USERLISTSELECTION(const char *,int)'),
                     self.handleCompletionList)
        
        self.connect(dbs,PYSIGNAL('clientStatement'),self.handleClientStatement)
        self.connect(dbs,PYSIGNAL('clientOutput'),self.write)
        self.connect(dbs,PYSIGNAL('clientGone'),self.initialise)
        self.connect(dbs,PYSIGNAL('clientRawInput'),self.raw_input)
        self.connect(dbs,PYSIGNAL('clientBanner'),self.writeBanner)
        self.connect(dbs,PYSIGNAL('clientCompletionList'),self.showCompletions)
        self.connect(dbs,PYSIGNAL('clientCapabilities'),self.handleClientCapabilities)
        self.connect(dbs,PYSIGNAL('clientException'), self.handleClientError)
        self.connect(dbs,PYSIGNAL('clientSyntaxError'), self.handleClientError)
        self.dbs = dbs

        # Initialise instance variables.
        self.initialise()
        self.prline = 0
        self.prcol = 0
        self.inDragDrop = 0
        self.lexer = None
        self.completionText = ""

        # Initialize history
        self.historyLists = {}
        self.maxHistoryEntries = Preferences.getShell("MaxHistoryEntries")
        self.history = None
        self.histidx = -1

        self.clientType = ''
        
        # Make sure we have prompts.
        if self.passive:
            sys.ps1 = unicode(self.trUtf8("Passive >>> "))
        else:
            try:
                sys.ps1
            except AttributeError:
                sys.ps1 = ">>> "
        try:
            sys.ps2
        except AttributeError:
            sys.ps2 = "... "
            
        if self.passive:
            self.getBanner()

        # Create a little language context menu
        self.lmenu = QPopupMenu()
        self.lmenu.insertItem(self.trUtf8('Python'), self.handleStartPython)
        self.lmenu.insertItem(self.trUtf8('Ruby'), self.handleStartRuby)
        
        # Create a little context menu
        self.menu = QPopupMenu(self)
        self.menu.insertItem(self.trUtf8('Copy'), self.copy)
        self.menu.insertItem(self.trUtf8('Paste'), self.paste)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Clear'), self.handleClear)
        self.menu.insertItem(self.trUtf8('Reset'), self.handleReset)
        self.menu.insertItem(self.trUtf8('Reset and Clear'),
            self.handleResetAndClear)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Start'), self.lmenu)
        
        self.setMargin0()
        self.setTextDisplay()
        self.bindLexer()
        
        # set the autocompletion and calltips function
        self.setAutoCompletion()
        self.setCallTips()
        
        self.setIcon(UI.PixmapCache.getPixmap("eric.png"))
        
        self.incrementalSearchString = ""
        self.incrementalSearchActive = False
        
        self.supportedEditorCommands = {
            QextScintilla.SCI_LINEDELETE : self.clearCurrentLine,
            QextScintilla.SCI_TAB        : self.handleQScintillaTab,
            QextScintilla.SCI_NEWLINE    : self.handleQScintillaNewline,
            
            QextScintilla.SCI_DELETEBACK : self.handleQScintillaDeleteBack,
            QextScintilla.SCI_CLEAR      : self.handleQScintillaDelete,
            QextScintilla.SCI_DELWORDLEFT  : self.handleQScintillaDeleteWordLeft,
            QextScintilla.SCI_DELWORDRIGHT : self.deleteWordRight,
            QextScintilla.SCI_DELLINELEFT  : self.handleQScintillaDeleteLineLeft,
            QextScintilla.SCI_DELLINERIGHT : self.deleteLineRight,
            
            QextScintilla.SCI_CHARLEFT   : self.handleQScintillaCharLeft,
            QextScintilla.SCI_CHARRIGHT  : self.moveCursorRight,
            QextScintilla.SCI_WORDLEFT   : self.handleQScintillaWordLeft,
            QextScintilla.SCI_WORDRIGHT  : self.moveCursorWordRight,
            QextScintilla.SCI_VCHOME     : self.handleQScintillaVCHome,
            QextScintilla.SCI_LINEEND    : self.handleQScintillaLineEnd,
            QextScintilla.SCI_LINEUP     : self.handleQScintillaLineUp,
            QextScintilla.SCI_LINEDOWN   : self.handleQScintillaLineDown,
            
            QextScintilla.SCI_PAGEUP     : self.handleQScintillaAutoCompletionCommand,
            QextScintilla.SCI_PAGEDOWN   : self.handleQScintillaAutoCompletionCommand,
            QextScintilla.SCI_CANCEL     : self.handleQScintillaAutoCompletionCommand,
            
            QextScintilla.SCI_CHARLEFTEXTEND  : self.handleQScintillaCharLeftExtend,
            QextScintilla.SCI_CHARRIGHTEXTEND : self.extendSelectionRight,
            QextScintilla.SCI_WORDLEFTEXTEND  : self.handleQScintillaWordLeftExtend,
            QextScintilla.SCI_WORDRIGHTEXTEND : self.extendSelectionWordRight,
            QextScintilla.SCI_VCHOMEEXTEND    : self.handleQScintillaVCHomeExtend,
            QextScintilla.SCI_LINEENDEXTEND   : self.extendSelectionToEOL,
        }
        
    def closeShell(self):
        """
        Public method to shutdown the shell. 
        """
        for key in self.historyLists.keys():
            ok = Preferences.Prefs.settings.writeEntry(\
                "/eric3/Shell/Histories/" + key, self.historyLists[key])
        
    def bindLexer(self, language = 'Python'):
        """
        Private slot to set the lexer.
        
        @param language lexer language to set
        """
        if language == 'Python':
            from LexerPython import LexerPython
            self.lexer = LexerPython(self)
        elif language == 'Ruby':
            try:
                from LexerRuby import LexerRuby
                self.lexer = LexerRuby(self)
            except ImportError:
                self.lexer = None
        
        if self.lexer is None:
            self.setLexer(None)
            return
        
        # get the font for style 0 and set it as the default font
        key = '/eric3/Scintilla/%s/style0/font' % unicode(self.lexer.language())
        fontstr, ok = Preferences.Prefs.settings.readEntry(key)
        if ok:
            fdesc = QStringList.split(',', fontstr)
            font = QFont(fdesc[0], int(str(fdesc[1])))
            self.lexer.setDefaultFont(font)
        self.setLexer(self.lexer)
        self.lexer.readSettings(Preferences.Prefs.settings, "/eric3/Scintilla")
        
        # now set the lexer properties
        if language == "Python":
            from qtext import QextScintillaLexerPython
            # Python lexer stuff
            if Preferences.getEditor("PythonBadIndentation"):
                self.lexer.setIndentationWarning(QextScintillaLexerPython.Inconsistent)
            else:
                self.lexer.setIndentationWarning(QextScintillaLexerPython.NoWarning)
        elif language == "Ruby":
            # Ruby lexer stuff
            from qtext import QextScintillaLexerRuby
            try:
                if Preferences.getEditor("RubyBadIndentation"):
                    self.lexer.setIndentationWarning(QextScintillaLexerRuby.Inconsistent)
                else:
                    self.lexer.setIndentationWarning(QextScintillaLexerRuby.NoWarning)
            except AttributeError:
                pass
        
    def setMargin0(self):
        """
        Private method to configure margins 0.
        """
        # set the font for all margins
        self.setMarginsFont(Preferences.getEditorOtherFonts("MarginsFont"))
        
        # set margin 0 settings
        linenoMargin = Preferences.getShell("LinenoMargin")
        self.setMarginLineNumbers(0, linenoMargin)
        if linenoMargin:
            self.setMarginWidth(0, ' ' + '8' * Preferences.getShell("LinenoWidth"))
        else:
            self.setMarginWidth(0, 0)
        
        # disable margins 1 and 2
        self.setMarginWidth(1, 0)
        self.setMarginWidth(2, 0)
        
    def setTextDisplay(self):
        """
        Private method to configure the text display.
        """
        self.setTabWidth(Preferences.getEditor("TabWidth"))
        if Preferences.getEditor("ShowWhitespace"):
            self.setWhitespaceVisibility(QextScintilla.WsVisible)
        else:
            self.setWhitespaceVisibility(QextScintilla.WsInvisible)
        self.setEolVisibility(Preferences.getEditor("ShowEOL"))
        if Preferences.getEditor("BraceHighlighting"):
            self.setBraceMatching(QextScintilla.SloppyBraceMatch)
        else:
            self.setBraceMatching(QextScintilla.NoBraceMatch)
        self.setMatchedBraceForegroundColor(
            Preferences.getEditorColour("MatchingBrace"))
        self.setMatchedBraceBackgroundColor(
            Preferences.getEditorColour("MatchingBraceBack"))
        self.setUnmatchedBraceForegroundColor(
            Preferences.getEditorColour("NonmatchingBrace"))
        self.setUnmatchedBraceBackgroundColor(
            Preferences.getEditorColour("NonmatchingBraceBack"))
        self.setSelectionBackgroundColor(qApp.palette().active().highlight())
        if Preferences.getEditor("ColourizeSelText"):
            self.resetSelectionForegroundColor()
        else:
            self.setSelectionForegroundColor(\
                qApp.palette().active().highlightedText())
        self.setCaretForegroundColor(
            Preferences.getEditorColour("CaretForeground"))
        self.setCaretLineBackgroundColor(
            Preferences.getEditorColour("CaretLineBackground"))
        self.setCaretLineVisible(Preferences.getEditor("CaretLineVisible"))
        self.caretWidth = Preferences.getEditor("CaretWidth")
        self.setCaretWidth(self.caretWidth)
        if Preferences.getShell("WrapEnabled"):
            self.setWrapMode(QextScintilla.WrapWord)
        else:
            self.setWrapMode(QextScintilla.WrapNone)
        
    def setAutoCompletion(self, language = 'Python'):
        """
        Private method to configure the autocompletion function.
        
        @param language of the autocompletion set to set
        """
        self.setAutoCompletionCaseSensitivity(
            Preferences.getEditor("AutoCompletionCaseSensitivity"))
        self.setAutoCompletionThreshold(-1)
        
        self.racEnabled = Preferences.getShell("AutoCompletionEnabled")
        
    def setCallTips(self, language = 'Python'):
        """
        Private method to configure the calltips function.
        
        @param language of the calltips set to set
        """
        if Preferences.getShell("CallTipsEnabled"):
            api = self.vm.getAPIs(language)
            self.setCallTipsAPIs(api)
            self.setCallTipsBackgroundColor(
                Preferences.getEditorColour("CallTipsBackground"))
            self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible"))
        else:
            self.setCallTipsAPIs(None)

    def initialise(self):
        """
        Private method to get ready for a new remote interpreter.
        """
        self.buff = QString()
        self.inContinue = 0
        self.inRawMode = 0
        self.echoInput = 1
        self.clientCapabilities = 0

    def handleClientCapabilities(self, cap, clType):
        """
        Private slot to handle the reporting of the clients capabilities.
        
        @param cap client capabilities (integer)
        @param clType type of the debug client (string)
        """
        self.clientCapabilities = cap
        if clType != self.clientType:
            self.clientType = clType
            self.bindLexer(clType)
            self.setAutoCompletion(clType)
            self.setCallTips(clType)
            self.racEnabled = Preferences.getShell("AutoCompletionEnabled") and \
                              (cap & HasCompleter) > 0
            
            if not self.historyLists.has_key(clType):
                # load history list
                hl, ok = Preferences.Prefs.settings\
                            .readListEntry("/eric3/Shell/Histories/" + clType)
                if ok:
                    self.historyLists[clType] = hl[-self.maxHistoryEntries:]
                else:
                    self.historyLists[clType] = QStringList()
            self.history = self.historyLists[clType]
            self.histidx = -1
        
    def getClientType(self):
        """
        Public slot to get the clients type.
        
        @return client type (string)
        """
        return unicode(self.clientType)
        
    def getBanner(self):
        """
        Private method to get the banner for the remote interpreter.
        
        It requests the Python version and platform running on the
        debug client side.
        """
        if self.passive:
            self.writeBanner('','','')
        else:
            self.dbs.remoteBanner()
            
    def writeBanner(self, version, platform, dbgclient):
        """
        Private method to write a banner with info from the debug client.
        
        @param version Python version string (string)
        @param platform platform of the remote interpreter (string)
        @param dbgclient debug client variant used (string)
        """
        self.clear()
        if self.passive:
            self.write('Passive Debug Mode')
        else:
            version = version.replace("#", unicode(self.trUtf8("No.")))
            self.write(self.trUtf8('%1 on %2, %3')
                        .arg(version)
                        .arg(platform)
                        .arg(dbgclient))
        self.write('\n')

        self.write(sys.ps1)
        
    def handleClientStatement(self,more):
        """
        Private method to handle the response from the debugger client.
        
        @param more flag indicating that more user input is required
        """
        if not self.inRawMode:
            self.inContinue = more
    
            if more:
                prompt = sys.ps2
            else:
                prompt = sys.ps1
    
            self.write(prompt)
        self.inCommandExecution = False

    def handleClientError(self):
        """
        Private method to handle an error in the client.
        """
        self.inCommandExecution = False
        self.interruptCommandExecution = True

    def getEndPos(self):
        """
        Private method to return the line and column of the last character.
        
        @return tuple of two values (int, int) giving the line and column
        """
        line = self.lines() - 1
        return (line, self.lineLength(line))

    def write(self,s):
        """
        Private method to display some text.
        
        @param s text to be displayed (string or QString)
        """
        line, col = self.getEndPos()
        self.setCursorPosition(line, col)
        self.insert(toUnicode(s))
        self.prline, self.prcol = self.getCursorPosition()
        self.ensureCursorVisible()
        self.ensureLineVisible(line)

    def raw_input(self,s,echo):
        """
        Private method to handle raw input.
        
        @param s prompt to be displayed (string or QString)
        @param echo Flag indicating echoing of the input (boolean)
        """
        self.setFocus()
        self.inRawMode = 1
        self.echoInput = echo
        self.write(s)
        line, col = self.getEndPos()
        self.setCursorPosition(line,col)
        self.prompt = unicode(self.text(line))\
                      .replace(sys.ps1, "").replace(sys.ps2, "")
        # move cursor to end of line
        self.moveCursorToEOL()
        
    def paste(self):
        """
        Reimplemented slot to handle the paste action.
        """
        lines = unicode(qApp.clipboard().text())
        self.executeLines(lines)

    def handleMiddleButton(self):
        """
        Private method to handle the middle mouse button press.
        """
        lines = unicode(qApp.clipboard().text(QClipboard.Selection))
        self.executeLines(lines)
    
    def executeLines(self, lines):
        """
        Private method to execute a set of lines as multiple commands.
        
        @param lines multiple lines of text to be executed as single
            commands (string)
        """
        for line in lines.splitlines(True):
            if line.endswith("\r\n"):
                fullline = True
                cmd = line[:-2]
            elif line.endswith("\r") or line.endswith("\n"):
                fullline = True
                cmd = line[:-1]
            else:
                fullline = False
            
            self.insertTextAtEnd(line)
            if fullline:
                self.executeCommand(cmd)
                if self.interruptCommandExecution: 
                    self.executeCommand("")
                    break
        
    def clearCurrentLine(self):
        """
        Private method to clear the line containing the cursor.
        """
        line, col = self.getCursorPosition()
        if self.text(line).startsWith(sys.ps1):
            col = len(sys.ps1)
        elif self.text(line).startsWith(sys.ps2):
            col = len(sys.ps2)
        else:
            col = 0
        self.setCursorPosition(line, col)
        self.deleteLineRight()
        
    def insertText(self,s):
        """
        Private method to insert some text at the current cursor position.
        
        @param s text to be inserted (string or QString)
        """
        line, col = self.getCursorPosition()
        self.insertAt(s,line,col)
        self.setCursorPosition(line, col + len(str(s)))
        
    def insertTextAtEnd(self,s):
        """
        Private method to insert some text at the end of the command line.
        
        @param s text to be inserted (string or QString)
        """
        line, col = self.getEndPos()
        self.setCursorPosition(line, col)
        self.insert(s)
        self.prline, self.prcol = self.getCursorPosition()
        
    def insertTextNoEcho(self,s):
        """
        Private method to insert some text at the end of the buffer without echoing it.
        
        @param s text to be inserted (string or QString)
        """
        self.buff.append(s)
        self.prline, self.prcol = self.getCursorPosition()
        
    def mousePress(self, ev):
        """
        Re-implemented to handle the press of the left and middle button specially.
        
        @param ev key event (QMouseEvent)
        """
        self.setFocus()
        if ev.button() == Qt.MidButton:
            self.handleMiddleButton()
            return True
        else:
            return False
        
    def editorCommand(self, cmd):
        """
        Public method to perform an editor command.
        
        @param cmd the scintilla command to be performed
        """
        try:
            self.supportedEditorCommands[cmd]()
        except TypeError:
            self.supportedEditorCommands[cmd](cmd)
        except KeyError:
            pass
        
    def keyPressEvent(self, ev):
        """
        Re-implemented to handle the user input a key at a time.
        
        @param ev key event (QKeyPressEvent)
        """
        txt = ev.text()
        key = ev.key()
        asc = ev.ascii()
        buf = unicode('')

        # See it is text to insert.
        if txt.length():
            if self.echoInput:
                ac = self.isListActive()
                QextScintillaCompat.keyPressEvent(self, ev)
                self.incrementalSearchActive = True
                if ac and \
                   self.racEnabled:
                    self.dbs.remoteCompletion(self.completionText+unicode(txt))
            else:
                self.insertTextNoEcho(txt)
            return
        else:
            ev.ignore()

    def handleQScintillaTab(self, cmd):
        """
        Private method to handle the Tab key.
        
        @param cmd QScintilla command
        """
        if self.isListActive():
            self.SendScintilla(cmd)
        else:
            line, index = self.getCursorPosition()
            buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
            if self.inContinue and not buf[:index-len(sys.ps2)].strip():
                self.SendScintilla(cmd)
            elif self.racEnabled:
                self.dbs.remoteCompletion(buf)
        return line

    def handleQScintillaLeftDeleteCommand(self, method):
        """
        Private method to handle a QScintilla delete command working to the left.
        
        @param method shell method to execute
        """
        line, col = self.getCursorPosition()
        db = 0
        ac = self.isListActive()
        oldLength = self.text(line).length()
        
        if self.text(line).startsWith(sys.ps1):
            if col > len(sys.ps1):
                method()
                db = 1
        elif self.text(line).startsWith(sys.ps2):
            if col > len(sys.ps2):
                method()
                db = 1
        elif col > 0:
            method()
            db = 1
        if db and ac and self.racEnabled and self.completionText:
            delta = self.text(line).length() - oldLength
            self.dbs.remoteCompletion(self.completionText[:delta])
        
    def handleQScintillaDeleteBack(self):
        """
        Private method to handle the Backspace key.
        """
        self.handleQScintillaLeftDeleteCommand(self.deleteBack)

    def handleQScintillaDeleteWordLeft(self):
        """
        Private method to handle the Delete Word Left command.
        """
        self.handleQScintillaLeftDeleteCommand(self.deleteWordLeft)

    def handleQScintillaDelete(self):
        """
        Private method to handle the delete command.
        """
        if self.hasSelectedText():
            lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
            if self.text(lineFrom).startsWith(sys.ps1):
                if indexFrom >= len(sys.ps1):
                    self.delete()
            elif self.text(lineFrom).startsWith(sys.ps2):
                if indexFrom >= len(sys.ps2):
                    self.delete()
            elif indexFrom >= 0:
                self.delete()
            self.setSelection(lineTo, indexTo, lineTo, indexTo)
        else:
            self.delete()

    def handleQScintillaDeleteLineLeft(self):
        """
        Private method to handle the Delete Line Left command.
        """
        if self.isListActive():
            self.cancelList()
            
        line, col = self.getCursorPosition()
        if self.text(line).startsWith(sys.ps1):
            prompt = sys.ps1
        elif self.text(line).startsWith(sys.ps2):
            prompt = sys.ps2
        else:
            prompt = ""
        
        self.deleteLineLeft()
        self.insertAt(prompt, line, 0)
        self.setCursorPosition(line, len(prompt))

    def handleQScintillaNewline(self, cmd):
        """
        Private method to handle the Return key.
        
        @param cmd QScintilla command
        """
        if self.isListActive():
            self.SendScintilla(cmd)
        else:
            self.incrementalSearchString = ""
            self.incrementalSearchActive = False
            line, col = self.getEndPos()
            self.setCursorPosition(line,col)
            buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
            self.insert('\n')
            self.executeCommand(buf)

    def handleQScintillaLeftCommand(self, method):
        """
        Private method to handle a QScintilla command working to the left.
        
        @param method shell method to execute
        """
        line, col = self.getCursorPosition()
        if self.text(line).startsWith(sys.ps1):
            if col > len(sys.ps1):
                method()
        elif self.text(line).startsWith(sys.ps2):
            if col > len(sys.ps2):
                method()
        elif col > 0:
            method()

    def handleQScintillaCharLeft(self):
        """
        Private method to handle the Cursor Left command.
        """
        self.handleQScintillaLeftCommand(self.moveCursorLeft)

    def handleQScintillaWordLeft(self):
        """
        Private method to handle the Cursor Word Left command.
        """
        self.handleQScintillaLeftCommand(self.moveCursorWordLeft)

    def handleQScintillaVCHome(self, cmd):
        """
        Private method to handle the Home key.
        
        @param cmd QScintilla command
        """
        if self.isListActive():
            self.SendScintilla(cmd)
        else:
            line, col = self.getCursorPosition()
            if self.text(line).startsWith(sys.ps1):
                col = len(sys.ps1)
            elif self.text(line).startsWith(sys.ps2):
                col = len(sys.ps2)
            else:
                col = 0
            self.setCursorPosition(line, col)

    def handleQScintillaLineEnd(self, cmd):
        """
        Private method to handle the End key.
        
        @param cmd QScintilla command
        """
        if self.isListActive():
            self.SendScintilla(cmd)
        else:
            self.moveCursorToEOL()

    def handleQScintillaLineUp(self, cmd):
        """
        Private method to handle the Up key.
        
        @param cmd QScintilla command
        """
        if self.isListActive():
            self.SendScintilla(cmd)
        else:
            line, col = self.getEndPos()
            buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
            if buf and self.incrementalSearchActive:
                if self.incrementalSearchString:
                    idx = self.rsearchHistory(self.incrementalSearchString, self.histidx)
                    if idx >= 0:
                        self.histidx = idx
                        self.useHistory()
                else:
                    idx = self.rsearchHistory(buf)
                    if idx >= 0:
                        self.histidx = idx
                        self.incrementalSearchString = buf
                        self.useHistory()
            else:
                if self.histidx < 0:
                    self.histidx = len(self.history)
                if self.histidx > 0:
                    self.histidx = self.histidx - 1
                    self.useHistory()

    def handleQScintillaLineDown(self, cmd):
        """
        Private method to handle the Down key.
        
        @param cmd QScintilla command
        """
        if self.isListActive():
            self.SendScintilla(cmd)
        else:
            line, col = self.getEndPos()
            buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
            if buf and self.incrementalSearchActive:
                if self.incrementalSearchString:
                    idx = self.searchHistory(self.incrementalSearchString, self.histidx)
                    if idx >= 0:
                        self.histidx = idx
                        self.useHistory()
                else:
                    idx = self.searchHistory(buf)
                    if idx >= 0:
                        self.histidx = idx
                        self.incrementalSearchString = buf
                        self.useHistory()
            else:
                if self.histidx >= 0 and self.histidx < len(self.history):
                    self.histidx += 1
                    self.useHistory()

    def handleQScintillaCharLeftExtend(self):
        """
        Private method to handle the Extend Selection Left command.
        """
        self.handleQScintillaLeftCommand(self.extendSelectionLeft)

    def handleQScintillaWordLeftExtend(self):
        """
        Private method to handle the Extend Selection Left one word command.
        """
        self.handleQScintillaLeftCommand(self.extendSelectionWordLeft)

    def handleQScintillaVCHomeExtend(self):
        """
        Private method to handle the Extend Selection to start of line command.
        """
        line, col = self.getCursorPosition()
        if self.text(line).startsWith(sys.ps1):
            col = len(sys.ps1)
        elif self.text(line).startsWith(sys.ps2):
            col = len(sys.ps2)
        else:
            col = 0
            
        self.extendSelectionToBOL()
        while col > 0:
            self.extendSelectionRight()
            col -= 1

    def handleQScintillaAutoCompletionCommand(self, cmd):
        """
        Private method to handle a command for autocompletion only.
        
        @param cmd QScintilla command
        """
        if self.isListActive() or self.isCallTipActive():
            self.SendScintilla(cmd)

    def executeCommand(self, cmd):
        """
        Private slot to execute a command.
        
        @param cmd command to be executed by debug client (string)
        """
        if not self.inRawMode:
            self.inCommandExecution = True
            self.interruptCommandExecution = False
            if not cmd:
                cmd = ''
            else:
                if len(self.history) == self.maxHistoryEntries:
                    del self.history[0]
                self.history.append(QString(cmd))
                self.histidx = -1
            if cmd.startswith('start'):
                if not self.passive:
                    cmdList = cmd.split(None, 1)
                    if len(cmdList) < 2:
                        self.dbs.startClient(0) # same as reset
                    else:
                        self.dbs.startClient(0, cmdList[1].capitalize())
                    cmd = ''
            elif cmd == 'clear':
                # Display the banner.
                self.getBanner()
                if not self.passive:
                    return
                else:
                    cmd = ''
            elif cmd == 'reset':
                self.dbs.startClient(0)
                if self.passive:
                    return
                else:
                    cmd = ''

            self.dbs.remoteStatement(cmd)
            while self.inCommandExecution: qApp.processEvents()
        else:
            if not self.echoInput:
                cmd = unicode(self.buff)
            self.inRawMode = 0
            self.echoInput = 1
            if not cmd:
                cmd = ''
            else:
                cmd = str(cmd.replace(unicode(self.prompt), ""))
            self.dbs.remoteRawInput(cmd)

    def useHistory(self):
        """
        Private method to display a command from the history.
        """
        if self.histidx < len(self.history):
            cmd = self.history[self.histidx]
        else:
            cmd = QString()
            self.incrementalSearchString = ""
            self.incrementalSearchActive = False

        self.setCursorPosition(self.prline,self.prcol)
        self.setSelection(self.prline,self.prcol,\
                          self.prline,self.lineLength(self.prline))
        self.removeSelectedText()
        self.insertText(cmd)

    def searchHistory(self, txt, startIdx=-1):
        """
        Private method used to search the history.
        
        @param txt text to match at the beginning (string or QString)
        @param startIdx index to start search from (integer)
        @return index of 
        """
        if startIdx == -1:
            idx = 0
        else:
            idx = startIdx + 1
        while idx < len(self.history) and \
              not self.history[idx].startsWith(txt):
            idx += 1
        return idx

    def rsearchHistory(self, txt, startIdx=-1):
        """
        Private method used to reverse search the history.
        
        @param txt text to match at the beginning (string or QString)
        @param startIdx index to start search from (integer)
        @return index of 
        """
        if startIdx == -1:
            idx = len(self.history) - 1
        else:
            idx = startIdx - 1
        while idx >= 0 and \
              not self.history[idx].startsWith(txt):
            idx -= 1
        return idx

    def focusNextPrevChild(self,next):
        """
        Reimplemented to stop Tab moving to the next window.
        
        While the user is entering a multi-line command, the movement to
        the next window by the Tab key being pressed is suppressed.
        
        @param next next window
        @return flag indicating the movement
        """
        if next and self.inContinue:
            return 0

        return QextScintillaCompat.focusNextPrevChild(self,next)

    def contextMenuEvent(self,ev):
        """
        Reimplemented to show our own context menu.
        
        @param ev context menu event (QContextMenuEvent)
        """
        self.menu.popup(ev.globalPos())
        ev.accept()
        
    def handleClear(self):
        """
        Public slot to handle the 'clear' context menu entry.
        """
        # Display the banner.
        self.getBanner()
        
    def handleResetAndClear(self):
        """
        Private slot to handle the 'reset and clear' context menu entry.
        """
        self.handleReset()
        self.handleClear()
        
    def handleReset(self):
        """
        Private slot to handle the 'reset' context menu entry.
        """
        self.dbs.startClient(0)
        
    def handleStartPython(self):
        """
        Private slot to handle the 'Start Python' context menu entry.
        """
        self.dbs.startClient(0, 'Python')
        
    def handleStartRuby(self):
        """
        Private slot to handle the 'Start Ruby' context menu entry.
        """
        self.dbs.startClient(0, 'Ruby')
        
    def handlePreferencesChanged(self):
        """
        Public slot to handle the preferencesChanged signal.
        """
        if self.lexer is not None:
            # read the lexer settings
            self.lexer.readSettings(Preferences.Prefs.settings, "/eric3/Scintilla")
            if self.lexer.language() == 'Python':
                if Preferences.getEditor("PythonAutoIndent"):
                    if QSCINTILLA_VERSION() > 0x010600:
                        self.lexer.setAutoIndentStyle(QextScintilla.AiOpening)
                    else:
                        self.lexer.setAutoIndentStyle(0)
                else:
                    self.lexer.setAutoIndentStyle(QextScintilla.AiMaintain)

        # set margin 0 configuration
        self.setMargin0()
        self.setTextDisplay()
        
        # set the autocompletion and calltips function
        self.setAutoCompletion()
        self.setCallTips()
        
        # do the history related stuff
        self.maxHistoryEntries = Preferences.getShell("MaxHistoryEntries")
        for key in self.historyLists.keys():
            self.historyLists[key] = \
                self.historyLists[key][-self.maxHistoryEntries:]

    def showCompletions(self, completions, text):
        """
        Private method to display the possible completions.
        """
        if len(completions) == 0:
            return
            
        if len(completions) > 1:
            completions.sort()
            comps = QStringList()
            for comp in completions:
                comps.append(comp)
            self.showUserList(1, comps)
            self.completionText = text
        else:
            txt = completions[0]
            if text != "":
                txt = txt.replace(text, "")
            self.insertText(txt)
            self.completionText = ""

    def handleCompletionList(self, txt, id):
        """
        Private slot to handle the selection from the completion list.
        
        @param txt the selected text (QString)
        @param id the ID of the user list (should be 1) (integer)
        """
        if id == 1:
            txt = unicode(txt)
            if self.completionText != "":
                txt = txt.replace(self.completionText, "")
            self.insertText(txt)
            self.completionText = ""
    
    #################################################################
    ## Drag and Drop Support
    #################################################################
    
    def eventFilter(self, obj, evt):
        """
        Protected method to handle some event on behalve of our componentes.
        
        @param obj destination object of the event (QObject)
        @param evt the event to be handled (QEvent)
        @return flag to indicate, if the event was handled
        """
        used = False
        evtType = evt.type()
        if evtType == QEvent.DragEnter:
            self.dragEnterEvent(evt)
            used = True
        elif evtType == QEvent.DragMove:
            self.dragMoveEvent(evt)
            used = True
        elif evtType == QEvent.DragLeave:
            self.dragLeaveEvent(evt)
            used = True
        elif evtType == QEvent.Drop:
            self.dropEvent(evt)
            used = True
        elif evtType == QEvent.MouseButtonPress:
            used = self.mousePress(evt)
        if not used:
            used = QextScintillaCompat.eventFilter(self, obj, evt)
        
        return used
        
    def dragEnterEvent(self, event):
        """
        Protected method to handle the drag enter event.
        
        @param event the drag enter event (QDragEnterEvent)
        """
        if QUriDrag.canDecode(event) or \
           QTextDrag.canDecode(event):
            self.inDragDrop = 1
            event.accept(1)
        else:
            QextScintillaCompat.dragEnterEvent(self, event)
        
    def dragMoveEvent(self, event):
        """
        Protected method to handle the drag move event.
        
        @param event the drag move event (QDragMoveEvent)
        """
        if self.inDragDrop:
            event.accept(1)
        else:
            QextScintillaCompat.dragMoveEvent(self, event)
        
    def dragLeaveEvent(self, event):
        """
        Protected method to handle the drag leave event.
        
        @param event the drag leave event (QDragLeaveEvent)
        """
        if self.inDragDrop:
            self.inDragDrop = 0
            event.accept(1)
        else:
            QextScintillaCompat.dragLeaveEvent(self, event)
        
    def dropEvent(self, event):
        """
        Protected method to handle the drop event.
        
        @param event the drop event (QDropEvent)
        """
        if QUriDrag.canDecode(event):
            flist = QStringList()
            ok = QUriDrag.decodeLocalFiles(event, flist)
            if ok:
                event.acceptAction()
                for fname in flist:
                    if not QFileInfo(fname).isDir():
                        self.vm.handlePythonFile(str(fname))
                    else:
                        KQMessageBox.information(None,
                            self.trUtf8("Drop Error"),
                            self.trUtf8("""<p><b>%1</b> is not a file.</p>""")
                                .arg(fname),
                            self.trUtf8("&OK"),
                            QString.null,
                            QString.null,
                            0, -1)
            del flist   # explicit destruction
        elif QTextDrag.canDecode(event):
            s = QString()
            ok = QTextDrag.decode(event, s)
            if ok and not s.isEmpty():
                event.acceptAction()
                self.executeLines(unicode(s))
            del s
        else:
            QextScintillaCompat.dropEvent(self, event)
        self.inDragDrop = 0
    
    def focusInEvent(self, event):
        """
        Public method called when the shell receives focus.
        
        @param event the event object (QFocusEvent)
        """
        try:
            self.vm.editActGrp.setEnabled(False)
            self.vm.editorActGrp.setEnabled(True)
            self.vm.copyAct.setEnabled(True)
            self.vm.pasteAct.setEnabled(True)
            self.vm.deleteAct.setEnabled(True)
            self.vm.zoomInAct.setEnabled(True)
            self.vm.zoomOutAct.setEnabled(True)
            self.vm.zoomToAct.setEnabled(True)
        except AttributeError:
            pass
        self.setCaretWidth(self.caretWidth)
        QextScintillaCompat.focusInEvent(self, event)
    
    def focusOutEvent(self, event):
        """
        Public method called when the shell loses focus.
        
        @param event the event object (QFocusEvent)
        """
        try:
            self.vm.zoomInAct.setEnabled(False)
            self.vm.zoomOutAct.setEnabled(False)
            self.vm.zoomToAct.setEnabled(False)
            self.vm.copyAct.setEnabled(False)
            self.vm.pasteAct.setEnabled(False)
            self.vm.deleteAct.setEnabled(False)
            self.vm.editorActGrp.setEnabled(False)
        except AttributeError:
            pass
        self.setCaretWidth(0)
        QextScintillaCompat.focusOutEvent(self, event)
    
    def insert(self, text):
        """
        Public slot to insert text at the current cursor position.
        
        The cursor is advanced to the end of the inserted text.
        
        @param text text to be inserted (string or QString)
        """
        txt = QString(text)
        l = txt.length()
        line, col = self.getCursorPosition()
        self.insertAt(txt, line, col)
        self.setCursorPosition(\
            line + txt.contains(self.linesepRegExp),
            col  + l)
