#----------------------------------------------------------------------------
# Name:         Debugger.py
# Purpose:      wxPython debugger, started as a port of IDLE's debugger
#               written by Guido van Rossum
#
# Authors:      Riaan Booysen, Shane Hathaway
#
# Created:      2000/01/11
# RCS-ID:       $Id: Debugger.py,v 1.35 2004/08/16 13:18:52 riaan Exp $
# Copyright:    (c) 2000 - 2004 : Riaan Booysen, Shane Hathaway
# Licence:      GPL
#----------------------------------------------------------------------------

# XXX I must still try to see if it's not possible the change code while
# XXX debugging, reload sometimes works
# XXX Going to source code on an error

import sys, os

from wxPython.wx import *

import Preferences, Utils
from Preferences import pyPath, IS, flatTools, keyDefs

from DebuggerControls import StackViewCtrl, BreakViewCtrl, NamespaceViewCtrl,\
                             WatchViewCtrl, DebugStatusBar
import PathMappingDlg

from Breakpoint import bplist
from DebugClient import EVT_DEBUGGER_OK, EVT_DEBUGGER_EXC, \
     EVT_DEBUGGER_STOPPED, EmptyResponseError

# When an output window surpasses these limits, it will be trimmed.
TEXTCTRL_MAXLEN = 30000
TEXTCTRL_GOODLEN = 20000

STOP_GENTLY = 0

wxID_PAGECHANGED = wxNewId()
wxID_TOPPAGECHANGED = wxNewId()
class DebuggerFrame(wxFrame, Utils.FrameRestorerMixin):
    debug_client = None
    _destroyed = 0
    _closing = 0

    def __init__(self, editor, filename=None, slave_mode=1):
        wxFrame.__init__(self, editor, -1, 'Debugger',
         style=wxDEFAULT_FRAME_STYLE|wxCLIP_CHILDREN|Preferences.childFrameStyle)

        self.winConfOption = 'debugger'
        self.loadDims()

        self.editor = editor
        self.running = 0
        self.slave_mode = slave_mode
        if filename:
            self.setDebugFile(filename)
        else:
            self.filename = ''

        self.SetIcon(IS.load('Images/Icons/Debug.ico'))

        self.viewsImgLst = wxImageList(16, 16)
        self.viewsImgLst.Add(IS.load('Images/Debug/Stack.png'))
        self.viewsImgLst.Add(IS.load('Images/Debug/Breakpoints.png'))
        self.viewsImgLst.Add(IS.load('Images/Debug/Watches.png'))
        self.viewsImgLst.Add(IS.load('Images/Debug/Locals.png'))
        self.viewsImgLst.Add(IS.load('Images/Debug/Globals.png'))
        self.viewsImgLst.Add(IS.load('Images/Debug/Output.png'))

        self.invalidatePanes()

        self.sb = DebugStatusBar(self)
        self.SetStatusBar(self.sb)

        self.toolbar = wxToolBar(self, -1,
              style=wxTB_HORIZONTAL|wxNO_BORDER|flatTools)
        self.SetToolBar(self.toolbar)

        self.runId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/Debug.png', 'Debug/Continue - %s'%keyDefs['Debug'][2],
          self.OnDebug) #, runs in debugger, stops at breaks and exceptions'
        self.runFullSpdId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/DebugFullSpeed.png', 'Debug/Continue full speed',
          self.OnDebugFullSpeed) #'stops only at hard (code) breaks and exceptions'
        self.stepId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/Step.png', 'Step - %s'%keyDefs['DebugStep'][2],
          self.OnStep)
        self.overId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/Over.png', 'Over - %s'%keyDefs['DebugOver'][2],
          self.OnOver)
        self.outId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/Out.png', 'Out - %s'%keyDefs['DebugOut'][2],
          self.OnOut)
        self.jumpId = -1
        if sys.version_info[:2] >= (2, 3):
            self.jumpId = Utils.AddToolButtonBmpIS(self, self.toolbar,
              'Images/Debug/Jump.png', 'Jump to line', self.OnJump)
            
        self.pauseId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/Pause.png',  'Pause', self.OnPause)
        self.stopId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/Stop.png',  'Stop', self.OnStop)
        self.toolbar.AddSeparator()
        self.sourceTraceId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/SourceTrace-Off.png',  'Trace in source',
          self.OnSourceTrace, '1')
        self.debugBrowseId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/DebugBrowse.png',  'Debug browsing',
          self.OnDebugBrowse, '1')
        if Preferences.psPythonShell == 'Shell':
            self.shellNamespaceId = Utils.AddToolButtonBmpIS(self, self.toolbar,
              'Images/Debug/ShellDebug.png',  'Eval in shell',
              self.OnDebugNamespace, '1')
        else:
            self.shellNamespaceId = -1
        self.toolbar.AddSeparator()
        self.pathMappingsId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/PathMapping.png',  'Edit client/server path mappings...',
          self.OnPathMappings)
        self.splitOrientId = Utils.AddToolButtonBmpIS(self, self.toolbar,
          'Images/Debug/SplitOrient.png',  'Toggle split orientation',
          self.OnToggleSplitOrient)

        self.SetAcceleratorTable(wxAcceleratorTable( [
          (keyDefs['Debug'][0], keyDefs['Debug'][1], self.runId),
          (keyDefs['DebugStep'][0], keyDefs['DebugStep'][1], self.stepId),
          (keyDefs['DebugOver'][0], keyDefs['DebugOver'][1], self.overId),
          (keyDefs['DebugOut'][0], keyDefs['DebugOut'][1], self.outId) ] ))

        self.toolbar.Realize()

        self.toolbar.ToggleTool(self.sourceTraceId, true)
        self.toolbar.ToggleTool(self.debugBrowseId, false)

        self.splitter = wxSplitterWindow(self, -1,
               style=wxSP_NOBORDER|Preferences.splitterStyle)

        (stackImgIdx, breaksImgIdx, watchesImgIdx, localsImgIdx,
                  globalsImgIdx) = range(5)

        # Create a Notebook
        self.nbTop = wxNotebook(self.splitter, wxID_TOPPAGECHANGED)
        self.nbTop.SetImageList(self.viewsImgLst)

        self.stackView = StackViewCtrl(self.nbTop, None, self)
        self.nbTop.AddPage(self.stackView, 'Stack', imageId=stackImgIdx)

        self.breakpts = BreakViewCtrl(self.nbTop, self)
        self.nbTop.AddPage(self.breakpts, 'Breakpoints', imageId=breaksImgIdx)

        # Create a Notebook
        self.nbBottom = wxNotebook(self.splitter, wxID_PAGECHANGED,
              style=wxCLIP_CHILDREN)
        EVT_NOTEBOOK_PAGE_CHANGED(self.nbBottom, wxID_PAGECHANGED,
                                  self.OnPageChange)
        self.nbBottom.SetImageList(self.viewsImgLst)

        self.watches = WatchViewCtrl(self.nbBottom, self.viewsImgLst, self)
        self.nbBottom.AddPage(self.watches, 'Watches', imageId=watchesImgIdx)

        self.locs = NamespaceViewCtrl(self.nbBottom, self, 1, 'local')
        self.nbBottom.AddPage(self.locs, 'Locals', imageId=localsImgIdx)

        self.globs = NamespaceViewCtrl(
            self.nbBottom, self, 0, 'global')

        self.nbBottom.AddPage(self.globs, 'Globals', imageId=globalsImgIdx)

        self.splitter.SetMinimumPaneSize(40)

        self.splitter.SplitHorizontally(self.nbTop, self.nbBottom)
        self.splitter.SetSashPosition(175)
        self.splitter.SetSplitMode(wxSPLIT_HORIZONTAL)

        self.mlc = 0
        self.frame = None

        self.lastStepView = None
        self.lastStepLineno = -1

        self.stepping_enabled = 1

        self.setParams([])
        
        self.setServerClientPaths([])

        EVT_DEBUGGER_OK(self, self.GetId(), self.OnDebuggerOk)
        EVT_DEBUGGER_EXC(self, self.GetId(), self.OnDebuggerException)
        EVT_DEBUGGER_STOPPED(self, self.GetId(), self.OnDebuggerStopped)
        
        # used to indicate when the debugger start,
        # would be better if there was a start event in addition to the OK event
        self._pid = None
        self._erroutFrm = self.editor.erroutFrm

        self.stream_timer = wxPyTimer(self.OnStreamTimer)
        self.stream_timer.Start(100)

        EVT_MENU_HIGHLIGHT_ALL(self, self.OnToolOver)
        EVT_CLOSE(self, self.OnCloseWindow)

    def destroy(self):
        if self._destroyed:
            return
        self._destroyed = 1

        self.breakpts.destroy()
        self.watches.destroy()
        self.locs.destroy()
        self.globs.destroy()
        self.sb.stateCols = None
        if self.stream_timer is not None:
            self.stream_timer.Stop()
            self.stream_timer = None

    def setDefaultDimensions(self):
        self.SetDimensions(0, Preferences.underPalette,
              Preferences.inspWidth, Preferences.bottomHeight)

    _sashes_inited = 0
    def initSashes(self):
        if not self._sashes_inited:
            s = self.GetSize()
            if s.x /float(s.y) > 1:
                mode = wxSPLIT_HORIZONTAL
            else:
                mode = wxSPLIT_VERTICAL
            self.splitter.SetSplitMode(mode)
            self.OnToggleSplitOrient()
            self._sashes_inited = 1

    def add_watch(self, name, local):
        self.watches.add_watch(name, local)
        self.nbBottom.SetSelection(0)
        self.invalidatePanes()
        self.updateSelectedPane()

    def OnPageChange(self, event):
        sel = event.GetSelection()
        if sel >= 0:
            self.updateSelectedPane(sel)
        event.Skip()

    def invalidatePanes(self):
        self.updated_panes = [0, 0, 0]

    def updateSelectedPane(self, pageno=-1, do_request=1, force=0):
        if pageno < 0:
            pageno = self.nbBottom.GetSelection()
        if not self.updated_panes[pageno] or force:
            frameno = self.stackView.getSelection()
            if pageno == 0:
                self.watches.showLoading()
                if do_request:
                    self.requestWatches(frameno)
            else:
                if pageno==1: self.locs.showLoading()
                else: self.globs.showLoading()
                if do_request:
                    self.requestDict((pageno==1), frameno)

    def requestWatches(self, frameno):
        ws = self.watches.watches
        exprs = []
        for name, local in ws:
            exprs.append({'name':name, 'local':local})
        if exprs:
            self.invokeInDebugger(
                'evaluateWatches', (exprs, frameno), 'receiveWatches')
        else:
            # No exprs, so no request is necessary.
            self.watches.load_dict(None)
            self.updated_panes[0] = 1

    def receiveWatches(self, status):
        frameno = status['frameno']
        if frameno == self.stackView.getSelection():
            self.updated_panes[0] = 1
            self.watches.load_dict(status['watches'])
        else:
            # Re-request.
            self.updateSelectedPane()

    def requestDict(self, locs, frameno):
        self.invokeInDebugger(
            'getSafeDict', (locs, frameno), 'receiveDict')

    def receiveDict(self, status):
        frameno = status['frameno']
        if frameno == self.stackView.getSelection():
            if status.has_key('locals'):
                self.updated_panes[1] = 1
                self.locs.load_dict(status['locals'])
            if status.has_key('globals'):
                self.updated_panes[2] = 1
                self.globs.load_dict(status['globals'])
        else:
            # Re-request.
            self.updateSelectedPane()

    def requestWatchSubobjects(self, name, local, pos):
        self.invokeInDebugger(
            'getWatchSubobjects', (name, self.stackView.getSelection()),
            'receiveWatchSubobjects', (name, local, pos))

    def receiveWatchSubobjects(self, subnames, name, local, pos):
        for subname in subnames:
            self.watches.add_watch('%s.%s' % (name, subname), local, pos)
            pos = pos + 1
        self.nbBottom.SetSelection(0)
        self.invalidatePanes()
        self.updateSelectedPane()

    def requestVarValue(self, name):
        self.invokeInDebugger(
            'pprintVarValue', (name, self.stackView.getSelection()),
            'receiveVarValue')

    def receiveVarValue(self, val):
        if val:
            self.editor.setStatus(val)

    def getVarValue(self, name):
        if not name.strip():
            return None
        self._hasReceivedVal = 0
        self._receivedVal = None
        self.invokeInDebugger(
            'pprintVarValue', (name, self.stackView.getSelection()),
            'receiveVarValue2')
        # XXX I'm not comfortable with this...
        try:
            while not self._hasReceivedVal:
                wxYield()
            return self._receivedVal
        finally:
            del self._hasReceivedVal
            del self._receivedVal

    def receiveVarValue2(self, val):
        self._receivedVal = val
        self._hasReceivedVal = 1

    def valueToOutput(self, name):
        val = self.getVarValue(name)
        self.editor.erroutFrm.outputTC.SetValue('')
        self.editor.erroutFrm.appendToOutput(val)

#---------------------------------------------------------------------------

    def setParams(self, params):
        self.params = params

    def setDebugFile(self, filename):
        self.filename = filename
        title = '%s - %s' % (os.path.basename(filename), filename)
        self.setTitleInfo(title)

    def setTitleInfo(self, info):
        pidinfo = ''
        if self.debug_client:
            pid = self.debug_client.getProcessId()
            if pid:
                pidinfo = '(%s) ' % pid
        title = 'Debugger %s- %s' % (pidinfo, info)
        self.SetTitle(title)

    def setDebugClient(self, client=None):
        if client is None:
            from ChildProcessClient import ChildProcessClient
            client = ChildProcessClient(self, '--zope')
        self.debug_client = client
    
    def setServerClientPaths(self, paths):
        self.serverClientPaths = paths
            

    def invokeInDebugger(self, m_name, m_args=(), r_name=None, r_args=()):
        """
        Invokes a method asynchronously in the debugger,
        possibly expecting a debugger event to be generated
        when finished.
        """
        self.debug_client.invokeOnServer(m_name, m_args, r_name, r_args)

    def killDebugger(self):
        if self._destroyed:
            return
        self.running = 0
        if self.debug_client:
            try:
                self.debug_client.kill()
            except:
                print 'Error on killing debugger: %s: %s'%sys.exc_info()[:2]
        self.clearViews()

    def OnDebuggerStopped(self, event):
        """Called when a debugger process stops."""
        show_dialog = 0
        if self.running:
            show_dialog = 1
        self.killDebugger()
        if self._closing:
            # Close the window.
            self.destroy()
            self.Destroy()
        elif show_dialog:
            wxMessageBox('The debugger process stopped prematurely.',
                  'Debugger stopped', wxOK | wxICON_EXCLAMATION | wxCENTRE)
        
        if self._pid:
            self._erroutFrm.processFinished(self._pid)
            self._pid = None

    def OnStreamTimer(self, event=None):
        if self.stream_timer:
            self.updateErrOutWindow()

    def updateErrOutWindow(self):
        if self.debug_client:
            info = self.debug_client.pollStreams()
            if info and self.editor:
                stdout_text, stderr_text = info
                if stdout_text:
                    self.editor.erroutFrm.appendToOutput(stdout_text)
                if stderr_text:
                    self.editor.erroutFrm.appendToErrors(stderr_text)

    def OnDebuggerOk(self, event):
        if self._destroyed:
            return

        if self._pid is None and self.debug_client:
            self._pid = self.debug_client.getProcessId()
            script = os.path.basename(self.filename)
            intp = os.path.basename(self.debug_client.pyIntpPath)
            if intp:
                self._erroutFrm.processStarted(intp, self._pid, script, 'Debug') 

        self.restoreDebugger()
        self.enableStepping()
        receiver_name = event.GetReceiverName()
        if receiver_name is not None:
            rcv = getattr(self, receiver_name)
            apply(rcv, (event.GetResult(),) + event.GetReceiverArgs())

    def OnDebuggerException(self, event):
        if self._destroyed:
            return

        self.enableStepping()
        t, v = event.GetExc()
        if isinstance(v, EmptyResponseError):
            if not self.debug_client or not self.debug_client.isAlive():
                # If the debugger was killed, this exception is normal.
                return

        self.restoreDebugger()
        if hasattr(t, '__name__'):
            t = t.__name__
        msg = '%s: %s.' % (t, v)

        confirm = wxMessageBox(msg + '\n\nStop debugger?',
                  'Debugger Communication Exception',
                  wxYES_NO | wxYES_DEFAULT | wxICON_EXCLAMATION |
                  wxCENTRE) == wxYES

        if confirm:
            self.killDebugger()

    def runProcess(self, autocont=0):
        self.running = 1
        self.sb.updateState('Waiting...', 'busy')
        brks = bplist.getBreakpointList()
        for brk in brks:
            brk['filename'] = self.clientFNToServerFN(brk['filename'])
        if self.slave_mode:
            # Work with a child process.
            add_paths = simplifyPathList(pyPath)
            add_paths = map(self.clientFNToServerFN, add_paths)
            filename = self.clientFNToServerFN(self.filename)
            self.invokeInDebugger(
                'runFileAndRequestStatus',
                (filename, self.params or [], autocont, add_paths, brks),
                'receiveDebuggerStatus')
        else:
            # Work with a peer or remote process.
            self.invokeInDebugger(
                'setupAndRequestStatus',
                (autocont, brks),
                'receiveDebuggerStatus')

        # InProcessClient TODO: setup the execution environment
        # the way it used to be done.

    def proceedAndRequestStatus(self, command, temp_breakpoint=None, args=()):
        # Non-blocking.
        self.sb.updateState('Running...', 'busy')
        if not temp_breakpoint:
            temp_breakpoint = 0
        self.invokeInDebugger('proceedAndRequestStatus',
                              (command, temp_breakpoint, args),
                              'receiveDebuggerStatus')

    def clientFNToServerFN(self, filename):
        """Converts a filename on the client to a filename on the server.

        Currently just turns file URLs into paths.  If you want to be able to
        set breakpoints when running the client in a different environment
        from the server, you'll need to expand this.
        """
        # XXX should we .fncache this? Files changing names are undefined
        # XXX during the lifetime of the debugger

        from Explorers.Explorer import splitURI, getTransport
        from Explorers.ExplorerNodes import all_transports

        prot, category, filepath, filename = splitURI(filename)
        if prot == 'zope':
            node = getTransport(prot, category, filepath, all_transports)
            if node:
                props = node.properties
                return 'zopedebug://%s:%s/%s/%s'%(props['host'],
                      props['httpport'], filepath, node.metatype)
            else:
                raise Exception('No Zope connection for: %s'%filename)
        elif prot == 'zopedebug':
            raise Exception('"zopedebug" is a server filename protocol')
        else:
        #if prot == 'file':
            if self.serverClientPaths:
                normFilepath = os.path.normcase(filepath)
                for serverPath, clientPath in self.serverClientPaths:
                    normClientPath = os.path.normcase(clientPath)
                    if normFilepath.startswith(normClientPath):
                        return serverPath+normFilepath[len(normClientPath):]
            return filepath
            
    def serverFNToClientFN(self, filename):
        """Converts a filename on the server to a filename on the client.

        Currently just generates URLs.  If you want to be able to
        set breakpoints when running the client in a different environment
        from the server, you'll need to expand this.
        """
        from Explorers.Explorer import splitURI
        if self.serverClientPaths:
            normFilepath = os.path.normcase(filename)
            for serverPath, clientPath in self.serverClientPaths:
                normServerPath = os.path.normcase(serverPath)
                if normFilepath.startswith(normServerPath):
                    return splitURI(clientPath+normFilepath[len(normServerPath):])[3]

        return splitURI(filename)[3]


    def deleteBreakpoints(self, filename, lineno):
        fn = self.clientFNToServerFN(filename)
        self.invokeInDebugger('clearBreakpoints', (fn, lineno))
        self.breakpts.refreshList()

    def adjustBreakpoints(self, filename, lineno, delta):
        fn = self.clientFNToServerFN(filename)
        self.invokeInDebugger('adjustBreakpoints', (fn, lineno, delta))
        self.breakpts.refreshList()

    def setBreakpoint(self, filename, lineno, tmp):
        fn = self.clientFNToServerFN(filename)
        self.invokeInDebugger('addBreakpoint', (fn, lineno, tmp))
        self.breakpts.refreshList()

    def requestDebuggerStatus(self):
        self.sb.updateState('Waiting...', 'busy')
        self.invokeInDebugger('getStatusSummary', (),
                              'receiveDebuggerStatus')

    def receiveDebuggerStatus(self, info):
        self.updateErrOutWindow()

        self.setDebugFile(self.filename)

        # Determine the current lineno, filename, and
        # funcname from the stack.

        # Update call stack
        stack = info['stack']
        # Translate server filenames to client filenames.
        for frame in stack:
            frame['client_filename'] = (
                self.serverFNToClientFN(frame['filename']))

        # Determine the current lineno, filename, and
        # funcname from the stack.
        if stack:
            bottom = stack[-1]
            filename = bottom['client_filename']
            funcname = bottom['funcname']
            lineno = bottom['lineno']
            base = os.path.basename(filename)
        else:
            filename = funcname = lineno = base = ''

        # Show running status.
        self.running = info['running']
        if self.running:
            message = "%s:%s" % (base, lineno)
            if funcname != "?":
                message = "%s: %s()" % (message, funcname)
        else:
            message = 'Finished.'
        self.sb.updateInstructionPtr(message)

        # Show exception information.
        exc_type = info.get('exc_type', None)
        exc_value = info.get('exc_value', None)
        if exc_type is not None:
            m1 = exc_type
            if exc_value is not None:
                try:
                    m1 = "%s: %s" % (m1, str(exc_value))
                except:
                    m1 = 'internal error'
            self.sb.updateState(m1)
        else:
            self.sb.updateState('Ready.', 'info')

        # Load the stack view.
        sv = self.stackView
        if sv:
            i = info['frame_stack_len']
            sv.load_stack(stack, i)
            sv.selectCurrentEntry()

        # Update breakpoints view with stats.
        breaks = info['breaks']
        for item in breaks:
            item['client_filename'] = self.serverFNToClientFN(
                item['filename'])
        self.breakpts.updateBreakpointStats(breaks)

        self.breakpts.refreshList()

        # If at a breakpoint, display status.
        if bplist.hasBreakpoint(filename, lineno):
            bplist.clearTemporaryBreakpoints(filename, lineno)
            self.sb.updateState('Breakpoint.', 'break')
            self.breakpts.selectBreakpoint(filename, lineno)

        self.selectSourceLine(filename, lineno)

        # All info in watches, locals, and globals panes is now invalid.
        self.invalidatePanes()
        # Update the currently selected pane.
        self.updateSelectedPane()
        # Receive stream data even if the user isn't looking.
        self.updateErrOutWindow()

        self.restoreDebugger()
        self.refreshTools()

    def restoreDebugger(self):
        if self.editor:
            if self.editor.palette.IsShown():
                if self.editor.palette.IsIconized():
                    self.editor.palette.restore()
                    self.editor.restore()
            elif self.editor.IsIconized():
                self.editor.restore()

    def clearStepPos(self):
        if self.lastStepView is not None:
            if hasattr(self.lastStepView, 'clearStepPos'):
                self.lastStepView.clearStepPos(self.lastStepLineno)
            self.lastStepView = None

    def getEditorSourceView(self, filename):
        if self.editor.modules.has_key(filename):
            return self.editor.modules[filename].model.getSourceView()
        else:
            return None

    def selectSourceLine(self, filename, lineno):
        if self.isSourceTracing():
            self.clearStepPos()
            if not filename:
                return

            #self.editor.SetFocus()
            try:
                self.editor.openOrGotoModule(filename)
            except Exception, err:
                self.editor.setStatus(
                      'Debugger: Failed to open file %s'%filename, 'Error')
            else:
                model = self.editor.getActiveModulePage().model
                view = model.getSourceView()
                if view is not None:
                    view.focus(false)
                    #view.selectLine(lineno - 1)
                    view.GotoLine(lineno - 1)
                    if hasattr(view, 'setStepPos'):
                        view.setStepPos(lineno - 1)
                    else:
                        view.selectLine(lineno - 1)
                    self.lastStepView = view
                    self.lastStepLineno = lineno - 1

    def isSourceTracing(self):
        return self.toolbar.GetToolState(self.sourceTraceId)

    def isDebugBrowsing(self):
        return self.toolbar.GetToolState(self.debugBrowseId) and self.running

    def isInShellNamepace(self):
        return self.toolbar.GetToolState(self.shellNamespaceId)

    def isRunning(self):
        return self.running

    def ensureRunning(self, cont_if_running=0, cont_always=0,
                      temp_breakpoint=None):
        """Starts the debugger if it is not currently running.

        If cont_always or if the debugger is already running and
        cont_if_running is set, the debugger is put in set_continue
        mode.
        """
        if self.isRunning():
            if cont_if_running or cont_always:
                self.doDebugStep('set_continue', temp_breakpoint)
        else:
            # Assume temp. breakpoint is already in bplist.
            self.runProcess(cont_always)

    def enableTools(self, stepping, running):
        for wid, enabled in ((self.runId, stepping),
                             (self.runFullSpdId, stepping),
                             (self.stepId, stepping),
                             (self.overId, stepping),
                             (self.outId, stepping),
                             (self.jumpId, stepping),
                             (self.pauseId, not stepping),
                             (self.stopId, running),
                             (self.debugBrowseId, running),
                             (self.shellNamespaceId, running)):
            if wid != -1:
                self.toolbar.EnableTool(wid, enabled)

    def refreshTools(self):
        self.enableTools(self.stepping_enabled, self.running)

    def enableStepping(self):
        self.stepping_enabled = 1
        self.refreshTools()

    def disableStepping(self):
        self.stepping_enabled = 0
        self.refreshTools()

    def doDebugStep(self, method=None, temp_breakpoint=None, args=()):
        if self.stepping_enabled:
            self.disableStepping()
            self.clearStepPos()
            self.invalidatePanes()
            self.updateSelectedPane(do_request=0)
            if not self.isRunning():
                self.runProcess()
            elif method:
                self.proceedAndRequestStatus(method, temp_breakpoint, args)
        else:
            if temp_breakpoint:
                self.setBreakpoint(temp_breakpoint[0], temp_breakpoint[1], 1)

    def OnDebug(self, event):
        if Preferences.minimizeOnDebug:
            self.editor.minimizeBoa()
        self.doDebugStep('set_continue')

    def OnDebugFullSpeed(self, event):
        if Preferences.minimizeOnDebug:
            self.editor.minimizeBoa()
        self.doDebugStep('set_continue', args=(1,))

    def OnStep(self, event):
        self.doDebugStep('set_step')

    def OnOver(self, event):
        self.doDebugStep('set_step_over')

    def OnOut(self, event):
        self.doDebugStep('set_step_out')

    def OnPause(self, event):
        # Only meaningful when running.
        if not self.stepping_enabled:
            self.invokeInDebugger('set_pause')

    def OnStop(self, event):
        if STOP_GENTLY:
            self.clearStepPos()
            self.enableStepping()
            self.invalidatePanes()
            self.updateSelectedPane(do_request=0)
            self.proceedAndRequestStatus('set_quit')
        else:
            self.killDebugger()

    def OnJump(self, event):
        idx = len(self.stackView.stack)-1
        if idx < 0:
            wxLogError('No stack available!')
            return
        
        self.stackView.Select(idx)
        self.stackView.OnGotoSource()
        dlg = wxTextEntryDialog(self, 'Enter line number to jump to:', 
              'Debugger - Jump', str(self.stackView.stack[idx]['lineno']))
        try:
            if dlg.ShowModal() != wxID_OK:
                return
            
            lineno = int(dlg.GetValue())
        finally:
            dlg.Destroy()
        
        #self.doDebugStep('set_step_jump', args=(lineno,))
        self.clearStepPos()
        self.invalidatePanes()
        self.updateSelectedPane(do_request=0)
        if not self.isRunning():
            self.runProcess()
        else:
            self.proceedAndRequestStatus('set_step_jump', None, (lineno,))

    def OnSourceTrace(self, event):
        pass

    def OnDebugBrowse(self, event):
        pass

    def clearViews(self):
        self.clearStepPos()
        self.stackView.load_stack([])
        self.watches.load_dict({})
        self.locs.load_dict({})
        self.globs.load_dict({})
        self.sb.updateState('Ready', 'info')
        self.refreshTools()

    def OnCloseWindow(self, event):
        self._closing = 1
        if self.debug_client:
            was_alive = self.debug_client.isAlive()
        try:
            self.killDebugger()
        finally:
            # closing must succeed
            if self.editor:
                try:
                    if self.isInShellNamepace():
                        self.editor.shell.debugShell(0, None)
                except:
                    pass
                    #cls, err = sys.exc_info()[:2]
                self.editor.debugger = None
                self.editor = None
            if not was_alive:
                self.destroy()
                self.Destroy()
            # else wait for an OnDebuggerStopped message

    def isInShellNamepace(self):
        if self.shellNamespaceId == -1:
            return false
        else:
            return self.toolbar.GetToolState(self.shellNamespaceId)

    def OnDebugNamespace(self, event):
        self.editor.OnSwitchShell()
        self.editor.shell.debugShell(self.isInShellNamepace(), self)

    def OnToggleSplitOrient(self, event=None):
        if self.splitter.GetSplitMode() == wxSPLIT_HORIZONTAL:
            self.splitter.SetSplitMode(wxSPLIT_VERTICAL)
            self.splitter.SplitVertically(self.nbTop, self.nbBottom)
            sashpos = self.splitter.GetClientSize().x / 2
        else:
            self.splitter.SetSplitMode(wxSPLIT_HORIZONTAL)
            self.splitter.SplitHorizontally(self.nbTop, self.nbBottom)
            sashpos = self.splitter.GetClientSize().y / 2
        self.splitter.SetSashPosition(sashpos)

    def OnPathMappings(self, event=None, paths=None):
        if paths is None:
            paths = self.serverClientPaths[:]

        newPaths = PathMappingDlg.showPathsMappingDlg(self, paths)
        if newPaths is not None:
            self.serverClientPaths = newPaths
            return true
        else:
            return false

    def OnToolOver(self, event):
        pass



def simplifyPathList(data,
                     SequenceTypes=(type(()), type([])),
                     ExcludeTypes=(type(None),) ):
    # Takes a possibly nested structure and turns it into a flat tuple.
    if type(data) in SequenceTypes:
        newdata = []
        for d in data:
            nd = simplifyPathList(d)
            if nd:
                newdata.extend(nd)
        return newdata
    elif type(data) in ExcludeTypes:
        return ()
    else:
        return list(str(data).split(os.pathsep))
