#----------------------------------------------------------------------------
# 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.39 2005/05/18 11:44:05 riaan Exp $
# Copyright:    (c) 2000 - 2005 : 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

import wx

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 = wx.NewId()
wxID_TOPPAGECHANGED = wx.NewId()
class DebuggerFrame(wx.Frame, Utils.FrameRestorerMixin):
    debug_client = None
    _destroyed = 0
    _closing = 0

    def __init__(self, editor, filename=None, slave_mode=1):
        wx.Frame.__init__(self, editor, -1, 'Debugger',
         style=wx.DEFAULT_FRAME_STYLE | wx.CLIP_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 = wx.ImageList(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 = wx.ToolBar(self, -1,
              style=wx.TB_HORIZONTAL | wx.NO_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(wx.AcceleratorTable( [
          (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 = wx.SplitterWindow(self, -1,
               style=wx.SP_NOBORDER|Preferences.splitterStyle)

        (stackImgIdx, breaksImgIdx, watchesImgIdx, localsImgIdx,
                  globalsImgIdx) = range(5)

        # Create a Notebook
        self.nbTop = wx.Notebook(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 = wx.Notebook(self.splitter, wxID_PAGECHANGED,
              style=wx.CLIP_CHILDREN)
        self.nbBottom.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChange, id=wxID_PAGECHANGED)
        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(wx.SPLIT_HORIZONTAL)

        self.mlc = 0
        self.frame = None

        self.lastStepView = None
        self.lastStepLineno = -1

        self.stepping_enabled = 1

        self.setParams([])

        self.setServerClientPaths([])

        self.Bind(EVT_DEBUGGER_OK, self.OnDebuggerOk, id=self.GetId())
        self.Bind(EVT_DEBUGGER_EXC, self.OnDebuggerException, id=self.GetId())
        self.Bind(EVT_DEBUGGER_STOPPED, self.OnDebuggerStopped, id=self.GetId())

        # 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 = wx.PyTimer(self.OnStreamTimer)
        self.stream_timer.Start(100)

        self.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnToolOver)
        self.Bind(wx.EVT_CLOSE, 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 = wx.SPLIT_HORIZONTAL
            else:
                mode = wx.SPLIT_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:
                wx.Yield()
            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, Preferences.debugServerArgs)
        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:
            wx.MessageBox('The debugger process stopped prematurely.',
                  'Debugger stopped', wx.OK | wx.ICON_EXCLAMATION | wx.CENTRE)

        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)
            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 = wx.MessageBox(msg + '\n\nStop debugger?',
                  'Debugger Communication Exception',
                  wx.YES_NO | wx.YES_DEFAULT | wx.ICON_EXCLAMATION |
                  wx.CENTRE) == wx.YES

        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:
            wx.LogError('No stack available!')
            return

        self.stackView.Select(idx)
        self.stackView.OnGotoSource()
        dlg = wx.TextEntryDialog(self, 'Enter line number to jump to:',
              'Debugger - Jump', str(self.stackView.stack[idx]['lineno']))
        try:
            if dlg.ShowModal() != wx.ID_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() == wx.SPLIT_HORIZONTAL:
            self.splitter.SetSplitMode(wx.SPLIT_VERTICAL)
            self.splitter.SplitVertically(self.nbTop, self.nbBottom)
            sashpos = self.splitter.GetClientSize().x / 2
        else:
            self.splitter.SetSplitMode(wx.SPLIT_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))
