' Gambas module file

Public Terminals As String[] = ["", "konsole", "gnome-terminal", "mate-terminal", "xfce4-terminal", "lxterminal", "xterm"]
Public TerminalNames As String[] = [("(Default)"), "KDE terminal", "Gnome terminal", "Mate terminal", "XFCE terminal", "LXDE terminal", "X11 terminal"]

Public Browsers As String[] = ["", "firefox", "chromium"]
Public BrowserNames As String[] = [("(Internal)"), "Firefox", "Chromium"]

Private Const DEBUG_REMOTE As Boolean = False

Private UseTerminal As Boolean
Private RedirectStderr As Boolean
Private UseHttpServer As Boolean
Private Profiling As Boolean
Private DebugInside As Boolean

Public Startup As String

Public ProcessId As Integer
Public ProcessFiles As Integer
Public ProcessMemory As Long
Public ProcessTempDir As String

Public Enum ACTION_RUN, ACTION_RUN_WITH, ACTION_STEP, ACTION_FORWARD, ACTION_RETURN_FROM, ACTION_RUN_EXTERN

Private Const STATE_LAST As Integer = -1
Private Const STATE_STOP As Integer = 0
Private Const STATE_RUNNING As Integer = 1
Private Const STATE_DEBUG As Integer = 2
Private Const STATE_STARTING As Integer = 3

Private $bTest As Boolean
Private $iState As Integer
Private $hProcess As Process
Private $hObserver As Observer

Private $sPosComp As String
Private $sPosClass As String
Private $iPosLine As Integer

Private $sError As String

Private $bStart As Boolean
Private $aCmdStart As String[]

Private $sAddBreakpoint As String
Private $sRemoveBreakpoint As String

Private $bIgnoreNextInfo As Boolean

Private $bConsole As Boolean

Private $sFifo As String

Private $bLastCommandMayBeLong As Boolean

Public BalloonControl As Control
Public BalloonX As Integer
Public BalloonY As Integer

Private $sProfilePath As String
'Private $bUpdateBreakOnError As Boolean

Private $sPipeError As String
Private $hPipeError As File
'Private $hBrowser As Process

Private $hTimerShowConsole As Timer

Private $bExtern As Boolean
Private $sExternLink As String
Private $hTimerExtern As Timer
Private $hTimerWatch As Timer

Private $sBuffer As String

Private $sRemoteDebuggerHelper As String
Private $bRemote As Boolean
Private $hRemoteFifo As Process
Private $sRemoteBuffer As String
Private $bRemoteFifoAuth As Boolean
Private $bProcessAuth As Boolean
Private $bProcessIgnoreNextNewLine As Boolean
Private $bGnomeTerminal As Boolean

Private $bCloseWebBrowser As Boolean
Private $hBrowser As Process

Private $cProject As Collection
Private $cIgnoreProject As Collection

Public Sub Init()
 
  $sFifo = Debug.Begin()
  SetState(STATE_STOP)
  
  $hTimerShowConsole = New Timer As "TimerShowConsole"
  $hTimerShowConsole.Delay = 50

End

Public Sub Exit()

  $hTimerShowConsole = Null
  Debug.End

End

Public Sub CheckErrorMessage(hEditor As FEditor)
  
  Dim sPath As String
  
  If Not $sError Then Return
  
  If hEditor Then
    If hEditor.Name == $sPosClass And If hEditor.GetEditor().Line = ($iPosLine - 1) Then
      hEditor.ShowErrorMessage($sError)
    Else If $iPosLine > hEditor.GetEditor().Count Then
      sPath = Project.FindForm(hEditor.Name)
      If sPath Then FMain.ShowError($sError, sPath)
    Endif
  Endif
  
  FDebugger.ShowError($sError)
  $hTimerShowConsole.Stop
  
End


Private Sub SetState(iState As Integer)

  'Dim bDebug As Boolean
  Dim bEnable As Boolean

  'PRINT "SetState:"; iState
  
  'bDebug = $iState <> STATE_STOP And $iState <> STATE_STARTING

  Action["run-extern"].Enabled = iState = STATE_STOP
  Action["pause"].Enabled = iState = STATE_RUNNING
  bEnable = iState <> STATE_RUNNING And iState <> STATE_STARTING
  Action["forward"].Enabled = bEnable
  Action["step"].Enabled = bEnable
  Action["return"].Enabled = bEnable And iState <> STATE_STOP
  Action["start"].Enabled = bEnable
  Action["until"].Enabled = bEnable
  Action["stop"].Enabled = iState <> STATE_STOP

  If iState = $iState Then Return
  
  $iState = iState
  FOutput.OnProjectDebugState
  FDebugger.OnProjectDebugState
  FMain.OnProjectDebugState
  
  If $hTimerWatch Then
    If $iState = STATE_RUNNING Then
      'TimerWatch_Timer
      $hTimerWatch.Restart
    Endif
  Endif
  '$hTimerDebugPanel.Restart
  
  If $iState = STATE_DEBUG Then FMain.Activate()
  
End

' Public Sub TimerDebugPanel_Timer()
'   
'   If $iState = STATE_RUNNING Then 
'     FDebugInfo.ShowConsole
'   Else
'     FDebugInfo.ShowDebugging
'   Endif
'   
'   $hTimerDebugPanel.Stop
'   
' End

Public Sub Pause()

  If $iState <> STATE_RUNNING Then Return
  
  If $bRemote Then
    WriteCommand("p" & CStr(ProcessId))
  Else
    If ProcessId Then Debug.Signal(ProcessId)
  Endif

End

' Public Sub UpdateBreakOnError()
'   
'   If $iState = STATE_STOP Then Return
'   
'   If $iState = STATE_RUNNING Then
'     $bUpdateBreakOnError = True
'     $hProcess.Signal
'   Else
'     BreakOnError
'   Endif
'   
' End


' Public Sub Process_Error(sData As String)
' 
'   Insert(sData)
' 
' End
' 
Public Sub File_Read()
  
  Dim sData As String
  
  sData = Read #$hPipeError, Lof($hPipeError)
  FOutput.Insert(sData)
  
End

Public Sub Process_Read()
  
  Dim sData As String
  
  $hTimerShowConsole.Restart
  If $bRemote Then
    sData = Read #$hProcess, Lof($hProcess)
    If DEBUG_REMOTE Then Debug Quote(sData)
    If $bProcessAuth Then
      If $bProcessIgnoreNextNewLine And If sData Begins "\r\n" Then
        sData = Mid$(sData, 3)
        $bProcessIgnoreNextNewLine = False
        If Not sData Then Return
      Endif
      FOutput.Insert(sData, True)
    Endif
  Endif
  
End

Public Sub Process_Prompt((Prompt) As String, (Answer) As String)
  
  $bProcessAuth = True
  $bProcessIgnoreNextNewLine = True
  
End

Public Sub TimerShowConsole_Timer()
  
  If $hProcess Then 
    If $bTest Then
      FDebugInfo.ShowTest
    Else
      FDebugInfo.ShowConsole
    Endif
  Endif
  $hTimerShowConsole.Stop
  
End

Private Sub Translate(sErr As String) As String
  
  If InStr(sErr, " ") Then Return Tr$(sErr)
  Return sErr
  
End

Public Sub MakeMessage(sErr As String) As String
  
  Dim aErr As String[]
  Dim iInd As Integer
  
  ' Substitution can be recursive in error messages!
  
  aErr = Split(sErr, "|")
  aErr.Resize(aErr.Count + 4)
  
  For iInd = aErr.Max - 4 DownTo 0
    If iInd = 0 Or If InStr(aErr[iInd], "&1") Then
      aErr[iInd] = Subst(Tr$(aErr[iInd]), Translate(aErr[iInd + 1]), Translate(aErr[iInd + 2]), Translate(aErr[iInd + 3]), Translate(aErr[iInd + 4]))
    Endif
    If Left(aErr[iInd]) = "#" Then
      Select Case Mid(aErr[iInd], 2)
        Case "1"
          aErr[iInd] = ("first")
        Case "2"
          aErr[iInd] = ("second")
        Case "3"
          aErr[iInd] = ("third")
        Case Else
          Try aErr[iInd] = CInt(Mid(aErr[iInd], 2))
      End Select
    Endif
  Next
  
  Return aErr[0]
  
End


Public Sub Debug_Read(Data As String)
  
  Dim aData As String[]
  Dim aPos As String[]
  Dim sPos As String
  Dim aFrame As String[]
  'Dim aLocal As String[]
  Dim sClass As String
  Dim iLine As Integer
  Dim sVar As String
  Dim sError As String
  Dim sWhere As String
  Dim iPos As Integer
  Dim sComp As String
  Dim bLineChanged As Boolean
  Dim iPid As Integer

  'Debug ">>> "; Quote(Data)

  $sBuffer &= Data
  iPos = InStr($sBuffer, "\n") 
  If iPos = 0 Then Return
  
  Data = Left($sBuffer, iPos - 1)
  $sBuffer = Mid$($sBuffer, iPos + 1)

  If Data = "!" Then 
    Signal
    Return 
  Else If Data = "!!" Then
    $bStart = True
    Signal
    Return
  Else If Not Data Then
    Return
  Endif
  
  'Debug Quote(Data)
  aData = Split(Data, "\t")
  
  Select Left$(aData[0])
  
    Case "*", "@"
    
      If aData.Count < 5 Then 
        Debug Data
        Return
      Endif
    
      ' Information, printed each time the debugger stops
      ' Format is: 
      '   [0] *[pid] or @[pid] 
      '   [1] Error message
      '   [2] Stack trace
      '   [3] Local variables
      '   [4] Current object
    
      Try iPid = CInt(Scan(aData[0], "?[[]*]")[0])
      If iPid And If iPid <> ProcessId Then
        ProcessId = iPid
        ProcessFiles = -1
        ProcessMemory = -1
        If Not $hTimerWatch Then
          $hTimerWatch = New Timer As "TimerWatch"
          $hTimerWatch.Delay = 1000
        Endif
        $hTimerWatch.Start
        TimerWatch_Timer
        FDebugger.UpdateProcessInfo(ProcessId, ProcessFiles, ProcessMemory)
      Endif
    
      aPos = Split(Trim(aData[2]), " ")
      While aPos.Count
        If aPos.Last <> "?" Then Break
        aPos.Remove(aPos.Max)
      Wend
  
      For Each sPos In aPos

        If sPos = "?" Or If sPos = "..." Then Continue
        aFrame = Scan(sPos, "[[]*].*.*.*")

        sComp = aFrame[0]
        sClass = aFrame[1]
        iLine = 0
        Try iLine = CInt(aFrame[3])

        If sClass And If iLine Then
          If DebugInside Or If sComp = "$" Then Break
        Endif
        sClass = ""

      Next
  
      'If iLine = 0 Then sClass = ""
      
      $sError = ""
      
      If sClass Then 
        bLineChanged = SetCurrentLine(sComp, sClass, iLine)
      Endif
      
      If Not aData[1] And If Left(aData[0]) = "*" Then
        If Not bLineChanged Or If aPos.Count = 0 Then 
        
          If $bIgnoreNextInfo Then 
            $bIgnoreNextInfo = False
          Else
            Command("", STATE_RUNNING)
          Endif
          Return
          
        Endif
      Endif
  
      FDebugger.DefineVariable(Trim(aData[3]), Trim(aData[4]))

      FDebugger.FillStack(aPos, bLineChanged)
      
      If aData[1] Then 
        
        sError = MakeMessage(aData[1])
        
        If sClass Then
          sWhere = $sPosClass & ":" & CStr($iPosLine)
          If $sPosComp <> "$" Then sWhere = "[" & $sPosComp & "]." & sWhere
        Else
          sWhere = ""
        Endif
        
        $sError = Project.FormatError(sError, sWhere)
        
        If sClass Then
          Try CheckErrorMessage(Project.ActiveForm)
        Else
          Try CheckErrorMessage(Null)
        Endif
        
      Endif

      SetState(STATE_DEBUG)

    'Case "I", "W"
      'FDebugInfo.Message(aData[1])
    
    Case "?", "&"
    
      sVar = Mid$(aData[0], 2)
      
      Select Case Left(sVar)
      
        ' Case "L"
        '   FDebugInfo.AddLocal(Mid$(sVar, 2), aData[1])
        ' 
        ' Case "O"
        '   FDebugInfo.AddObject(Mid$(sVar, 2), aData[1])
        '   
        Case "V"
          FDebugger.AddVariable(Mid$(sVar, 2), aData[1])

        Case "W"
          FDebugger.AddResultWatch(Mid$(sVar, 2), aData[1])

        Case "I"
          FDebugger.AddResultWatch(Mid$(sVar, 2), aData[1], True)
          
        Case "X"
          FDebugExpr.AddResult(Mid$(sVar, 2), aData[1])
          
        Case "$"
          ProcessTempDir = UnQuote(aData[1])
          
      End Select
      
    Case "!"
      
      FOutput.InsertResult(aData[1])
      
    Case "="
      
      sVar = Mid$(aData[0], 2)
      
      Select Case Left(sVar)
      
        Case "X"
          FDebugExpr.AddResult(Mid$(sVar, 2), aData[1], True)
          
        Case "L"
          FDebugger.AddLocal(Mid$(sVar, 2), aData[1], True)
 
        Case "O"
          FDebugger.AddObject(Mid$(sVar, 2), aData[1], True)
 
        Case "V"
          FDebugger.AddVariable(Mid$(sVar, 2), aData[1], True)

      End Select
      
    Case "#"
    
      sVar = Mid$(aData[0], 2)
      
      Select Case Left(sVar)
      
        Case "X"
          If Not aData[1] Then
            ' sending to Ballon
            Command("?I" & Mid$(sVar, 2) & ":\t" & Mid$(sVar, 2))  
            
          Else
            
            If Left(aData[1]) = "!" Then
                          
              If FDebugExpr.Exist(Mid$(sVar, 2)) Then
                FDebugExpr.Disable(Mid$(sVar, 2), Mid$(aData[1], 2))
              Else 
                Command("?I" & Mid$(sVar, 2) & ":\t" & Mid$(sVar, 2))  
              End If
                          
            Else 
              
              FDebugExpr.Add(Mid$(sVar, 2), aData[1])
      
            Endif 
      
          Endif
          
      End Select
  
    Case "i"
      
      sVar = Mid$(aData[0], 2)
      If Not sVar Then
        $hTimerWatch.Stop
      Else
        aData = Split(sVar)
        Try ProcessFiles = CInt(aData[0])
        Try ProcessMemory = CLong(aData[1])
        FDebugger.UpdateProcessInfo(-1, ProcessFiles, ProcessMemory)
      Endif

  End Select
  
End

Public Function DebugExprCheck(sExpr As String) As Boolean

  Return Not (Left(sExpr) = "!")

End

Public Sub Process_Kill()

  Dim sMsg As String
  Dim bTest As Boolean
  Dim sAppId As String

  ' Clean up temporary files not automatically removed
  sAppId = "org.gambas." & CStr(ProcessId)
  Try Shell.RmDir(File.Dir(Application.TempDir) &/ CStr(ProcessId))
  Try Kill User.Home &/ ".local/share/applications/gambas/" & sAppId & ".desktop"
  Try Rmdir User.Home &/ ".local/share/applications/gambas"
  Try Shell.RmDir(Desktop.DataDir &/ sAppId)
  Try Shell.RmDir(Desktop.CacheDir &/ sAppId)
  Try Shell.RmDir(Desktop.RuntimeDir &/ CStr(ProcessId))

  If $bRemote And If DEBUG_REMOTE Then Debug

  bTest = $bTest
  
  If bTest Then
    FDebugInfo.GetTestTerminal().EnsureVisible()
  Else
    FOutput.GetTerminal().EnsureVisible()
  Endif
    
  If Not Project.AboutToQuit Then AfterStop
  
  If Not $hProcess Then Return ' $hProcess may be NULL if the event loop is called somewhere during AfterStop()

  If $hProcess.State = Process.Crashed Then
    ' SIGKILL, SIGTERM and SIGINT are not considered as bugs in the program
    If bTest Then FDebugInfo.StopTest(MTest.TEST_ABORT)
    Select Case $hProcess.Value
      Case Signal.SIGINT
        sMsg = ("The program has been interrupted by CTRL+C.")
      Case Signal.SIGKILL
        'sMsg = ("The program has been killed.")
      Case Signal.SIGTERM
        sMsg = ("The program has been terminated.")
      Default
        FCrash.Run($hProcess)
    End Select
    If sMsg Then FMain.ShowWarning(sMsg)
  Else If $hProcess.Value Then
    If bTest Then 
      FDebugInfo.StopTest(MTest.TEST_FAIL)
    Else
      FDebugButton.SetMessage(Subst(("The program has returned the value &1."), $hProcess.Value))
    Endif
  Else
    If bTest Then FDebugInfo.StopTest(MTest.TEST_SUCCESS)
  Endif

  $hProcess = Null

  If Profiling Then 
    Project.ProjectTree[Project.KEY_PROFILE].Visible = True
    Project.OpenFile($sProfilePath)
  Endif

End

Private Sub AfterStop()

  Dim hForm As Object

  If $iState = STATE_STOP Then Return

  $sAddBreakPoint = ""
  $sRemoveBreakPoint = ""

  Debug.Stop
  
  If $hRemoteFifo Then
    Try $hRemoteFifo.Kill
    $hRemoteFifo.Wait(0.5)
  Endif
  
  If $sPipeError Then
    $hPipeError.Close
    $sPipeError = ""
  Endif
  
  SetCurrentLine("", "", 0)

  SetState(STATE_STOP)
  'UpdateMinimize
  Project.Running = False
  
  Project.ClearOtherProjects

  WriteDebugSettings()

  FDebugger.Clear
  FDebugExpr.Clear

  $sError = ""
  Balloon.Hide
  
  If $bCloseWebBrowser Then FBrowser.Hide
  If $hBrowser Then
    Try $hBrowser.Kill
    $hBrowser = Null
  Endif

  For Each hForm In Project.Files
    Try hForm.OnProjectDebug()
  Next

  FMain.OnProjectDebug
  FOutput.OnProjectDebug
  FSearch.OnProjectDebug

  If Not $bConsole Then FMain.ShowBottomPanel(False, True)
  FDebugInfo.UpdateView
  FDebugger.OnProjectDebug
  FDebugInfo.OnProjectDebug
  
  If $bExtern Then
    $hTimerExtern.Stop
    $hTimerExtern = Null
    Try Kill $sExternLink
    Try Kill $sExternLink & ".conf"
    FDebugger.StopWaitForRemote
    $bExtern = False
  Endif

  Action["console"].Value = $bConsole

  'FMain.ActivateCurrentWindow

  If $hTimerWatch Then
    $hTimerWatch.Stop
    $hTimerWatch = Null
  Endif

  '$hTimerShowConsole.Stop
  
  'IF Project.ActiveForm THEN
  '  TRY Project.ActiveForm.Show
  'ENDIF

End


Public Sub Stop()

  If $iState = STATE_STOP Then Return

  If $hProcess Then
    If $bGnomeTerminal Then Pause()
    Try $hProcess.Kill
    $hProcess.Wait(1)
  Else If $bExtern Then
    If ProcessId > 0 Then Try Signal.Send(ProcessId, Signal.SIGKILL)
  Endif
  AfterStop

End


Public Sub Forward()

  If $iState = STATE_RUNNING Then Return

  If $iState = STATE_STOP Then
    Start("n")
    Return
  Endif

  Command("n", STATE_RUNNING)

End


Public Sub ReturnFrom()

  If $iState = STATE_RUNNING Then Return

  If $iState = STATE_STOP Then
    Start("n")
    Return
  Endif

  Command("f", STATE_RUNNING)

End



' PUBLIC SUB btnForward_Click()
' 
'   IF $iState = STATE_STOP THEN
'     Project.Run(FALSE, 2)
'   ELSE
'     Forward
'   ENDIF
' 
' END


Public Sub Step()

  If $iState = STATE_RUNNING Then Return

  If $iState = STATE_STOP Then
    Start("s")
    Return
  Endif

  Command("s", STATE_RUNNING)

End

' PUBLIC SUB btnStep_Click()
' 
'   IF $iState = STATE_STOP THEN
'     Project.Run(FALSE, 1)
'   ELSE
'     ME.Step
'   ENDIF
' 
' END



Private Function SetCurrentLine(sComp As String, sClass As String, iLine As Integer) As Boolean

  Dim hForm As FEditor

  If sComp And If sClass And If iLine > 0 Then
    If sComp = $sPosComp And If sClass = $sPosClass And If iLine = $iPosLine Then Return
  Endif

  If $sPosClass Then

    hForm = FindEditor($sPosComp, $sPosClass, False)
    If hForm Then hForm.GetEditor().StopLine = -1

  Endif

  $sPosComp = sComp
  $sPosClass = sClass
  $iPosLine = iLine

  If $sPosClass Then

    hForm = FindEditor($sPosComp, $sPosClass, True)
    If hForm Then
      With hForm
        If $iPosLine <= .GetEditor().Count Then
          Project.OpenFile(hForm.Path, $iPosLine)
          .GetEditor().StopLine = $iPosLine - 1
        Endif
      End With
    Endif

  Endif

  Return True
  
End

Private Sub WriteCommand(sCmd As String)
  
  'Debug Quote(sCmd)
  If $hRemoteFifo Then
    If DEBUG_REMOTE Then Debug "---> "; sCmd
    Print #$hRemoteFifo, sCmd
    'Flush #$hRemoteFifo
  Else
    Debug.Write(sCmd)
  Endif
  
End

Public Sub RunUntil(hForm As FEditor, iLine As Integer)

  Dim bOn As Boolean
  Dim sClass As String
  Dim sBreak As String

  If $iState = STATE_RUNNING Then Return

  sClass = hForm.GetNameForBreakpoint()

  With hForm.GetEditor()
    bOn = Not .[iLine].Breakpoint
  End With

  If bOn Then
    'PRINT "RunUntil "; sClass; "."; iLine
    sBreak = sClass & "." & CStr(iLine)
    If $iState <> STATE_DEBUG Then
      $sAddBreakpoint = MakeBreakpoint(sBreak, True)
    Else
      WriteCommand(MakeBreakpoint(sBreak, True))
    Endif
    $sRemoveBreakpoint = MakeBreakpoint(sBreak, False)
  Endif

  Run

End

Public Sub Run(Optional bTest As Boolean)

  If $iState = STATE_RUNNING Then Return

  $bLastCommandMayBeLong = True

  If $iState = STATE_STOP Then
    
    $bTest = bTest
  
    If bTest Then
      If Not Project.HasTest() Then
        $bLastCommandMayBeLong = False
        Message.Warning(("The project has no test module."))
        Return
      Endif
      FDebugInfo.StartTest()
    Endif

    Start("g")
    
  Else
    
    Command("g", STATE_RUNNING)
    
  Endif

  $bLastCommandMayBeLong = False

End

' PUBLIC SUB btnGo_Click()
' 
'   IF $iState = STATE_STOP THEN
'     Project.Run(FALSE)
'   ELSE
'     Run
'   ENDIF
' 
' END


' PRIVATE SUB SplitArg(sArg AS String) AS String[]
'   
'   DIM aArg AS NEW String[]
'   DIM iPos AS Integer
'   DIM sCar AS String
'   DIM sElt AS String
'   DIM bIgnoreSpace AS Boolean
'   
'   sArg = Trim(sArg)
'   IF sArg THEN
'   
'     FOR iPos = 1 TO Len(sArg)
'     
'       sCar = Mid$(sArg, iPos, 1)
'       IF sCar = " " AND IF NOT bIgnoreSpace THEN
'         aArg.Add(sElt)
'         sElt = ""
'         CONTINUE
'       ENDIF
'       IF sCar = Chr$(34) THEN 
'         bIgnoreSpace = NOT bIgnoreSpace
'         CONTINUE 
'       ENDIF
'       IF sCar = "\\" THEN 
'         INC iPos
'         sElt &= Mid$(sArg, iPos, 1)
'         CONTINUE 
'       ENDIF 
'       sElt &= sCar
'     NEXT
'     
'     aArg.Add(sElt)
'     
'   ENDIF 
'   
'   RETURN aArg
'   
' END


' PRIVATE SUB SplitArgument(sArg AS String) AS String[]
'   
'   DIM I, J AS Integer
'   DIM aArg AS NEW String[]
'   DIM iCar AS Integer
'   DIM iWait AS Integer
' 
'   I = 1
'   DO
'     INC J
'     IF J > Len(sArg) THEN BREAK 
'     iCar = Asc(Mid$(sArg, J, 1))
'     
'     IF iCar = iWait THEN 
'       iWait = 0
'       CONTINUE 
'     ENDIF
'     
'     IF iCar = 34 OR iCar = Asc("'") THEN 
'       iWait = iCar  
'     ENDIF
'     
'     IF iCar < 32 THEN 
'       IF J > I THEN aArg.Add(Mid$(sArg, I, J - I))
'       I = J + 1
'       CONTINUE 
'     ENDIF
'     
'   LOOP 
'   
'   IF J > I THEN aArg.Add(Mid$(sArg, I, J - I))
'   
'   RETURN aArg
'   
' END

Private Sub MakeBreakpoint(sBreakpoint As String, bOn As Boolean) As String

  Dim aBreak As String[]
  
  aBreak = Breakpoints.FromString(sBreakpoint)
  Return If(bOn, "+", "-") & Breakpoints.ToString(aBreak)
  
End

Private Sub GetTestSuite() As String

  Dim hSettings As Settings
  Dim iSuite As Integer
  Dim sSuite As String
  Dim aTest As String[]
  Dim I As Integer

  hSettings = New Settings(Project.Dir &/ ".test")
  iSuite = hSettings["/TestSuites/TestWith", 0]
  If iSuite > 0 Then Return "@" & hSettings["/TestSuites/" & CStr(iSuite) &/ "Name"]

  sSuite = hSettings["/TestSuites/Default"]
  
  If Not sSuite Then
    aTest = MTest.GetTestModules()
    For I = 0 To aTest.Max
      aTest[I] = File.BaseName(aTest[I])
    Next
    sSuite = aTest.Join()
  Endif
  Return sSuite

End

Private Sub Start(sCmd As String)

  Dim hForm As Object
  'Dim hEdit As FEditor
  Dim aExec As String[]
  Dim bValgrind As Boolean = False
  Dim sTitle As String
  Dim sProg As String
  Dim sFile As String
  Dim aEnv As String[]
  Dim sOpenWebBrowser As String
  Dim sPort As String
  Dim iPos As Integer
  Dim I As Integer
  Dim aProg As String[]
  Dim sRemoteServer As String
  Dim sRemoteFolder As String
  Dim sRemoteUser As String
  Dim aRemoteCmd As String[]
  Dim sRemotePassword As String
  Dim hCopyProcess As Process
  Dim sFifo As String
  Dim sRemoteDebuggerHelper As String
  Dim iCurrentArg As Integer
  Dim aArguments As String[][]
  Dim sBrowser As String
  Dim sDir As String

  If Project.Running Then Return
  
  '$cShow.Clear
  
  ReadSettings

  $bConsole = Action["console"].Value  

  $bRemote = Project.Config["/Debug/Remote"]
  If $bRemote Then
    
    If Project.MakeExecutable(True, True, False) Then Return
    
    ' Create the debugger helper
    
    sRemoteDebuggerHelper = CreateDebuggerHelper()
    If Not sRemoteDebuggerHelper Then Return
    
    sRemoteServer = Project.Config["/Debug/RemoteServer"]
    sRemoteFolder = Project.Config["/Debug/RemoteDirectory"]
    sRemoteUser = Project.Config["/Debug/RemoteUser"]
    Try sRemotePassword = Desktop.Passwords[Project.Name &/ "RemoteDebug"]
    
    ' Create the remote directory and the remote fifo
    
    Project.SetMessage(("Initialize remote debugging..."))
    
    If sRemoteFolder = "~" Then
      sRemoteFolder = "/home" &/ sRemoteUser
    Else If sRemoteFolder Begins "~/" Then
      sRemoteFolder = "/home" &/ sRemoteUser &/ Mid$(sRemoteFolder, 3)
    Endif
    
    sFifo = sRemoteFolder &/ ".remote."
    
    aExec = ["ssh", "-q", "-o", "StrictHostKeyChecking=no", sRemoteUser & "@" & sRemoteServer, 
      "mkdir -p " & Shell(sRemoteFolder) & " && " &
      "rm -f " & Shell(sFifo & "in") & " " & Shell(sFifo & "out") & " && " &
      "mkfifo " & Shell(sFifo & "in") & " " & Shell(sFifo & "out")]
    $sRemoteBuffer = aExec.Join(" ")
    hCopyProcess = Exec aExec For Input Output As "RemoteProcess"
    hCopyProcess.Expect("password:", sRemotePassword)
    hCopyProcess.Wait
    
    If hCopyProcess.Value Then
      FMain.ShowErrorWithOutput(("Unable to initialize remote debugging."), $sRemoteBuffer)
      Return
    Endif
    
    ' Copy the executable to the remote server
    
    $sRemoteBuffer = ""
    hCopyProcess = Exec ["scp", "-q", "-o", "StrictHostKeyChecking=no", Project.GetExecPath(), sRemoteDebuggerHelper, sRemoteUser & "@" & sRemoteServer & ":" & sRemoteFolder] For Input Output As "RemoteProcess"
    hCopyProcess.Expect("password:", sRemotePassword)
    hCopyProcess.Wait
    
    If hCopyProcess.Value Then
      FMain.ShowErrorWithOutput(("Unable to copy executable to the remote server."), $sRemoteBuffer)
      Return
    Endif
    
    Project.SetMessage(("OK"), True)
    
  Else
    
    If Project.Compile() Then Return
    
    sFifo = $sFifo

  Endif

  ClearOutput

  'PRINT "EXEC "; "gbx -gs " & File.Dir(Project.Path) & " -- " & Project.Arguments

  ProcessId = 0
  
  MakeInitialCommands(sCmd)

  $bStart = True
  Project.Running = True

  For Each hForm In Project.Files
    Try hForm.OnProjectDebug()
  Next
  FOutput.OnProjectDebug
  FMain.OnProjectDebug
  FSearch.OnProjectDebug

  FMain.ShowBottomPanel(True, True)
  
  FDebugInfo.UpdateView
  FDebugger.OnProjectDebug
  FDebugInfo.OnProjectDebug

  aEnv = Project.GetEnvironment().Copy()
  If $bRemote Then Try aEnv.Insert(Project.Config["/Debug/RemoteEnvironment"])
  I = 0
  While I < aEnv.Count
    If aEnv[I] Begins " " Then
      aEnv.Remove(I)
    Else
      Inc I
    Endif
  Wend
  
  If Not aEnv.Exist("GB_GUI=*", gb.Like) Then 
    aEnv.Add("GB_GUI=")
  Endif
  
  If $bTest Then
    
    aExec = [System.Path &/ "bin/gbx" & System.Version, "-g", "-f", sFifo, "-T", GetTestSuite(), File.Dir(SConv(Project.Path))]
    
    $hProcess = FDebugInfo.GetTestTerminal().Exec(aExec, aEnv)
    $hProcess.Term.Echo = True
    $hObserver = New Observer($hProcess, True) As "Process"

  Else

    'If Profiling Then FProfile.CleanProfileFiles
  
    If $bRemote Then
      aExec = ["gbr" & System.Version]
    Else
      aExec = [System.Path &/ "bin/gbx" & System.Version]
    Endif
    
    aExec.Add("-g")

    aExec.Add("-f")
    aExec.Add(sFifo)
    
    If Startup Then 
      aExec.Add("-s")
      aExec.Add(Startup)
    Endif
    
    If Project.CanUseHttpServer() Then 
      If UseHttpServer Or If Project.Components.Exist("gb.httpd") Then
        aExec.Add("-H")
        iPos = aEnv.Find("GB_HTTPD_PORT=*", gb.Like)
        sPort = "8080"
        If iPos < 0 Then
          aEnv.Add("GB_HTTPD_PORT=8080")
        Else
          Try sPort = Scan(aEnv[iPos], "*=*")[1]
        Endif
        sOpenWebBrowser = "http://localhost:" & sPort
      Endif
    Endif
    
    If Profiling Then 
      
      aExec.Add("-p")
      
      If Project.ReadOnly Then
        sDir = Application.TempDir
      Else
        sDir = Project.Dir &/ ".profile"
        Try Mkdir sDir
      Endif
      
      sFile = Project.GetUniqueName(sDir, Project.Name & "-" & Format(Now, "yyyy-mm-dd-hh-nn-ss") & ".prof")
      $sProfilePath = sDir &/ sFile
      
      aExec.Add($sProfilePath)
      
    Endif
    
    If $bRemote Then
      aExec.Add(sRemoteFolder &/ File.Name(Project.GetExecPath()))
    Else
      aExec.Add(File.Dir(SConv(Project.Path)))
    Endif
    
    iCurrentArg = Project.Config["/Debug/CurrentArgument", 0] - 1
    aArguments = Project.Config["/Debug/Arguments"]
    If aArguments And If iCurrentArg >= 0 And iCurrentArg < aArguments.Count Then
      If Not $bRemote Then aExec.Add("--")
      aExec.Insert(aArguments[iCurrentArg])
    Endif
    
    If bValgrind Then 
      aExec.Insert(["valgrind", "--tool=memcheck", "--num-callers=50"], 0)
    Endif
  
    If $bRemote Then
      
      aRemoteCmd = ["ssh", "-q", "-t", "-o", "StrictHostKeyChecking=no", sRemoteUser & "@" & sRemoteServer]
      aRemoteCmd.Insert(aEnv)
      aEnv.Clear
      aExec.Insert(aRemoteCmd, 0)
      
    Endif

    If UseTerminal Then 
    
      sTitle = Project.Name & " (" & ("Output terminal") & ")"
    
      sProg = Settings["/Debug/Terminal"]
      
      If Not sProg Or If Project.CheckProgram(sProg) Then
      
        Select Case Desktop.Type
              
          Case "KDE", "KDE4", "KDE5"
            aProg = ["konsole"]
          Case "GNOME", "UNITY", "UBUNTU:GNOME"
            aProg = ["gnome-terminal"]
          Case "MATE", "CINNAMON"
            aProg = ["mate-terminal", "gnome-terminal"]
          Case "XFCE"
            aProg = ["xfce4-terminal", "gnome-terminal"]
          Case "LXDE"
            aProg = ["lxterminal"]
          ' Case "CYGWIN"
          '   aProg = ["mintty.exe"] 
          Case Else
            aProg = New String[]
            
        End Select
        
        aProg.Add("xterm")
        
        For Each sProg In aProg
          If System.Exist(sProg) Then Break
          sProg = ""
        Next
        
        If Not sProg Then 
          Message.Warning(("Unable to start project."), ("No terminal emulator found."))
          Return
        Endif
        
      Endif
      
      If RedirectStderr Then
        $sPipeError = Temp$()
        aExec.Add("-r")
        aExec.Add($sPipeError)
      Endif
      
    Endif
    
    $bGnomeTerminal = False
      
    Select Case sProg
    
      Case "konsole"
        aExec = ["konsole", "--nofork", "--hold", "--name", Project.Name & "-output", "-e", aExec.Join(" ")]
        
      Case "gnome-terminal"
        aExec.Insert([sProg, "--hide-menubar", "--maximize", "--wait", "--title=" & sTitle, "--"], 0)
        $aCmdStart.Add("h", 0)
        $bGnomeTerminal = True
        
      Case "xfce4-terminal", "mate-terminal"
        aExec.Insert([sProg, "--hide-menubar", "--maximize", "--title=" & sTitle, "-x"], 0)
        $aCmdStart.Add("h", 0)
        $bGnomeTerminal = True
        
      Case "lxterminal"
        aExec.Insert(["lxterminal", "-t", sTitle, "-e"], 0)
        
      Case "xterm"
        aExec.Insert(["xterm", "-title", sTitle, "-hold", "-e"], 0)
        
    End Select
    
    If $bRemote Then
      
      $bRemoteFifoAuth = False
      $hRemoteFifo = Exec ["ssh", "-q", "-t", "-o", "StrictHostKeyChecking=no", sRemoteUser & "@" & sRemoteServer, sRemoteFolder &/ File.Name($sRemoteDebuggerHelper)] For Input Output As "RemoteFifo"
      $hRemoteFifo.Term.Echo = False
      $hRemoteFifo.Expect("password:", sRemotePassword)
      
      If DEBUG_REMOTE Then Debug aExec.Join(" ")
      $bProcessAuth = False
      $hProcess = Exec aExec With aEnv For Input Output As "Process" 
      $hProcess.Expect("password:", sRemotePassword)

    Else If sProg Then
      
      $hProcess = Exec aExec With aEnv As "Process"
      
    Else
      
      '$hProcess = Exec aExec With aEnv For Input Output As "Process"
      $hProcess = FOutput.GetTerminal().Exec(aExec, aEnv)
      $hProcess.Term.Echo = True
      $hObserver = New Observer($hProcess, True) As "Process"
      
    Endif
    
    If $sPipeError Then
      $hPipeError = Pipe $sPipeError For Read Watch
    Endif
  
  Endif
  
  SetState(STATE_STARTING)
  Balloon.Hide

  Try Debug.Start

  If Error Then 
    'sArg = Error.Text
    Try $hProcess.Kill
    'FMain.ShowError(("Unable to start debugger.") & "\n\n" & sArg)
    Return
  Endif

  'UpdateMinimize

  $bCloseWebBrowser = False
  If sOpenWebBrowser Then
    sBrowser = Project.Config["/Debug/Browser"]
    If sBrowser Then
      $hBrowser = Project.OpenBrowser(sBrowser, sOpenWebBrowser, True)
      If $hBrowser Then
        Object.Attach($hBrowser, Me, "Browser")
      Else
        sBrowser = ""
      Endif
    Endif
    If Not sBrowser Then
      FBrowser.Run(sOpenWebBrowser)
      $bCloseWebBrowser = True
    Endif
  Endif

  If $bTest Then
    FDebugInfo.ShowTest
  Else
    FMain.ShowDebugging
  Endif

  'PRINT "Debug.Start"
  'PRINT "Debug.Start -> OK"

  'Signal
  
End

Public Sub RunExtern()

  Dim hForm As Object
  Dim sDir As String
  Dim hStat As Stat

  If Project.Running Then Return
  
  sDir = File.Dir(Temp$())
  
  $sExternLink = "/tmp/gambas-" & Project.Name & ".debug"
  
  ' FIXME: race condition here!
  
  Try hStat = Stat($sExternLink)
  If hStat And If IsDir(hStat.Link) Then
    FMain.ShowErrorWith(("Unable to start extern debugging."), ("Another extern debugging is active."))
    Return
  Endif
  
  File.Save($sExternLink & ".conf", Breakpoints.GetCommands().Join())

  Try Kill $sExternLink
  Try Link sDir To $sExternLink
  If Error Then 
    FMain.ShowErrorWith(("Unable to start extern debugging."))
    Return
  Endif
  
  'SetExternIgnore(0)

  $bConsole = Action["console"].Value  

  ClearOutput

  $bExtern = True

  ProcessId = 0
  MakeInitialCommands()

  $bStart = True
  Project.Running = True

  For Each hForm In Project.Workspace.Windows
    Try hForm.OnProjectDebug
  Next
  FOutput.OnProjectDebug
  FMain.OnProjectDebug
  FSearch.OnProjectDebug

  FMain.ShowBottomPanel(True, True)
  
  FDebugInfo.UpdateView
  FDebugInfo.OnProjectDebug
  FDebugger.OnProjectDebug
  FDebugger.StartWaitForRemote
  FMain.ShowDebugging
  
  SetState(STATE_STARTING)
  
  Try Debug.Start
  If Error Then 
    FMain.ShowErrorWith(("Unable to start debugger."))
    Return
  Endif

  $hTimerExtern = New Timer As "TimerExtern"
  $hTimerExtern.Delay = 500
  $hTimerExtern.Start

End

Public Sub TimerExtern_Timer()
  
  Dim sPath As String
  Dim hLock As File
  ' Dim I As Integer
  
  ' For I = 1 To MAX_IGNORE_EXTERN
  '   sPath = $sExternLink & "." & CStr(I)
  '   If Not Exist(sPath) Then Break
  ' Next
  
  'FDebugger.SetExternIgnore(I - 1)

  If ProcessId Then
    sPath = File.SetExt($sFifo, "lock")
    Try hLock = Lock sPath
    If Not Error Then 
      Stop()
      Unlock hLock
      Try Kill sPath
    Endif
  Endif
  
End

' Private Sub UpdateMinimize()
'   
'   'Debug Project.Running;; $iState;; $bLastCommandMayBeLong
'   
'   If Project.Running And If Settings["/MinimizeOnRun"] Then
'     If $iState = STATE_STARTING Then
'       If $bLastCommandMayBeLong Then
'         'Debug "MINIMIZE"
'         FMain.Minimized = True
'       Endif
'     Else 
'       'Debug "RESTORE"
'       FMain.Minimized = False
'     Endif
'   Endif
'   
' End

Public Sub Command(sCmd As String, Optional iNewState As Integer = STATE_LAST)

  'IF Start(sCmd) THEN RETURN

  'IF NOT Project.Running THEN RETURN

  'WAIT
  If Not $hProcess And If Not $bExtern Then Return

  'IF bResult THEN
  '  $cCommand.Add(sPrefix & sCmd)
  'ENDIF

  WriteCommand(sCmd)
  
  If iNewState <> STATE_LAST Then
    If iNewState <> $iState Then
      SetState(iNewState)
      SetCurrentLine("", "", 0)
    Endif
  Endif
  
End

' Private Sub BreakOnError()
' 
'   WriteCommand("b" & If(Project.BreakOnError, "+", "-"))
' 
' End

Private Sub Signal()

  'PRINT "Signal"

  If $bStart Then
    If $bExtern Then FDebugger.StopWaitForRemote
    'PRINT "START Debug.Write: "; $sCmdStart
    If $aCmdStart.Count Then WriteCommand($aCmdStart.Join("\n"))
    $bStart = False
    'Command("w", TRUE)
    'Command($sCmdStartRun, FALSE, STATE_RUNNING)
    SetState(STATE_RUNNING)
    $bIgnoreNextInfo = True
    Return
  Endif

  If $hTimerWatch Then TimerWatch_Timer
  
  ' If $bUpdateBreakOnError Then
  '   $bUpdateBreakOnError = False
  '   BreakOnError
  '   Command("", STATE_RUNNING)
  '   Return
  ' Endif

  'IF $iState = STATE_DEBUG THEN RETURN

  ' Entering debug

  If $sRemoveBreakpoint Then
    WriteCommand($sRemoveBreakpoint)
    $sRemoveBreakpoint = ""
  Endif

  'Command("w", TRUE)
  'Command("l", TRUE)

  FDebugger.RefreshAllWatch

  Try FDebugExpr.RefreshAll
  
  FMain.Show

  'Command("e", TRUE)

End


' PUBLIC SUB Stop()
'
'   IF $iState = STATE_STOP THEN RETURN
'
'   'SendCommand("q", TRUE)
'   $hProcess.Kill
'
' END

Public Sub ClearOutput()

  If $bTest Then
    FDebugInfo.GetTestTerminal().Clear
  Else
    FOutput.Clear
  Endif

End

Public Function IsRunning() As Boolean

  Return $iState = STATE_RUNNING Or $iState = STATE_STARTING

End

Public Sub IsStarting() As Boolean
  
  Return $iState = STATE_STARTING
  
End


Public Function IsStop() As Boolean

  Return $iState = STATE_STOP

End

Public Sub GetProcess() As Process
  
  Return $hProcess
  
End

Public Sub Send(sStr As String)

  If $iState <> STATE_RUNNING Then Return
  If Not $hProcess Then Return
  
  Write #$hProcess, sStr
  Flush #$hProcess

End


' PUBLIC SUB btnFrom_Click()
' 
'   IF $iState = STATE_STOP THEN
'     Project.Run(FALSE, 1)
'   ELSE
'     ME.ReturnFrom
'   ENDIF
' 
' END

Private Sub ReadSettings()
  
  UseTerminal = Project.Config["/Debug/UseTerminal"]
  RedirectStderr = Project.Config["/Debug/RedirectStderr"]
  UseHttpServer = Project.Config["/Debug/UseHttpServer"]
  Profiling = Project.Config["/Debug/Profiling"]
  DebugInside = Project.Config["/Debug/DebugInside"]
  
  $cProject = Project.Config["/Debug/Projects"]
  If Not $cProject Then $cProject = New Collection
  
  $cIgnoreProject = New Collection

End

Public Sub ReadDebugSettings()
  
  ReadSettings
  FDebugger.ReadWatchSettings()
  FDebugExpr.ReadSettings()

End

' Public Sub WriteSettings()
'   
'   Project.Config["/Debug/UseTerminal"] = UseTerminal
'   Project.Config["/Debug/RedirectStderr"] = RedirectStderr
'   Project.Config["/Debug/UseHttpServer"] = UseHttpServer 
'   Project.Config["/Debug/Profiling"] = Profiling
'   Project.Config["/Debug/DebugInside"] = DebugInside
' 
' End

Public Sub WriteDebugSettings()
  
  FDebugger.WriteWatchSettings()
  FDebugExpr.WriteSettings()

End

Public Sub OnProjectChange()
  
  ReadSettings
  
End

Public Sub SetBreakpoint(sClass As String, iLine As Integer, bOn As Boolean) 
  
  If Project.Running And If Not IsRunning() Then
    WriteCommand(If(bOn, "+", "-") & sClass & "." & CStr(iLine + 1))
  Endif

End

Public Sub SetBalloon(hCtrl As Control, Optional X As Integer = -1, Y As Integer = -1)
  
  BalloonControl = hCtrl
  BalloonX = X
  BalloonY = Y  
  
End

Public Sub IsExtern() As Boolean
  
  Return $bExtern
  
End

Public Sub TimerWatch_Timer()

  Dim sDir As String
  Dim aMaps As String[]
  Dim sMap As String
  Dim iPos As Integer
  
  If $iState <> STATE_RUNNING Then 
    Stop Event
    Return
  Endif
  
  If $bRemote Then
    
    WriteCommand("i" & CStr(ProcessId))
    Return
    
  Endif
    
  sDir = "/proc" &/ CStr(ProcessId)
  If Not IsDir(sDir) Then
    $hTimerWatch.Stop
    Return
  Endif
  
  Try ProcessFiles = Dir(sDir &/ "fd").Count
  If Error Then ProcessFiles = -1
  
  ProcessMemory = 0
  Try aMaps = Split(Trim(File.Load(sDir &/ "smaps_rollup")), "\n")
  If aMaps Then
    For Each sMap In aMaps
      If sMap Begins "Pss:" Then
        sMap = LTrim(Mid$(sMap, 5))
        iPos = InStr(sMap, " ")
        ProcessMemory = CLong(Left(sMap, iPos - 1)) * 1024
        Break
      Endif
    Next
  Endif
  
  FDebugger.UpdateProcessInfo(-1, ProcessFiles, ProcessMemory)
  
End

Public Sub RemoteProcess_Read()

  Dim sData As String

  sData = Read #Last, Lof(Last)  
  If DEBUG_REMOTE Then Debug Quote(sData)
  $sRemoteBuffer &= sData
  
End

Public Sub RemoteProcess_Error(sData As String)

  If DEBUG_REMOTE Then Debug Quote(sData)
  $sRemoteBuffer &= sData
  
End

Public Sub RemoteProcess_Kill()
  
  If DEBUG_REMOTE Then Debug
  
End


Public Sub RemoteFifo_Prompt((Prompt) As String, (Answer) As String)
  
  $bRemoteFifoAuth = True
  
End

Public Sub RemoteFifo_Read()

  Dim sData As String
  Dim iPos As Integer
  
  sData = Read #Last, Lof(Last)
  If Not $bRemoteFifoAuth Then Return
  
  sData = Replace(sData, "\r", "")
  If DEBUG_REMOTE Then Debug Quote(sData)
  
  Do
    iPos = InStr(sData, "\n")
    If iPos Then
      Debug_Read(Left(sData, iPos))
      sData = Mid$(sData, iPos + 1)
    Else
      Debug_Read(sData)
      Break
    Endif
  Loop
  
End

Public Sub RemoteFifo_Kill()
  
  If DEBUG_REMOTE Then Debug
  $hRemoteFifo = Null
  
End

Private Sub CreateDebuggerHelper() As String

  Dim sDir As String
  Dim sOutput As String
  Dim sPath As String
  
  If Not $sRemoteDebuggerHelper Then
  
    sDir = File.Dir(Temp$()) &/ "remote-debugger-helper"
    Mkdir sDir
    Mkdir sDir &/ ".src"
    Copy "debugger/Main.module" To sDir &/ ".src/Main.module"
    Copy "debugger/_project" To sDir &/ ".project"
    Shell "cd " & Shell(sDir) & " && gbc3 -gt -j1 2>&1 && gba3 2>&1" To sOutput
    
    sPath = sDir &/ "remote-debugger-helper.gambas"
    If Not Exist(sPath) Then
      FMain.ShowErrorWithOutput(("Unable to generate the remote debugger helper."), sOutput)
      Return
    Endif
    
    $sRemoteDebuggerHelper = sPath
  
  Endif
  
  Return $sRemoteDebuggerHelper

End

Public Sub SetDebugInside(bVal As Boolean)
  
  DebugInside = bVal
  Project.Config["/Debug/DebugInside"] = bVal
  If Project.Running Then WriteCommand("og" & If(bVal, "+", "-"))
  
End

Private Sub MakeInitialCommands(Optional sCmd As String)

  $aCmdStart = New String[]

  ReadDebugSettings()

  If DebugInside Then $aCmdStart.Add("og+")

  If Not $bExtern Then 
  
    $aCmdStart.Insert(Breakpoints.GetCommands())
    
    If $sAddBreakpoint Then
      $aCmdStart.Add($sAddBreakpoint)
      $sAddBreakpoint = ""
    Endif
    
  Endif
  
  If Not $bRemote Then $aCmdStart.Add("?$\tApplication.TempDir")
  
  $aCmdStart.Insert(FDebugger.GetWatches())

  'If Project.BreakOnError Then $aCmdStart.Add("o+")
  
  If sCmd Then $aCmdStart.Add(sCmd)

End

Public Sub Browser_Kill()

  If $hBrowser Then
    $hBrowser = Null
    {Stop}
  Endif
  
End

Private Sub FindProject(sComp As String) As String

  Dim sPath As String
  
  sPath = $cProject[sComp]
  If Not sPath Then Return
  Return Project.FindProjectPath(sPath)
  
End

Private Sub SetProject(sComp As String, sPath As String)
  
  $cProject[sComp] = sPath
  Project.Config["/Debug/Projects"] = $cProject
  
End

Private Sub FindClassPath(sDir As String, sClass As String) As String

  Dim sFile As String
  Dim sExt As String
  
  sDir &/= ".src"
  For Each sFile In RDir(sDir)
    If File.BaseName(sFile) == sClass Then
      sExt = File.Ext(sFile)
      If sExt = "class" Or If sExt = "module" Then Return sDir &/ sFile
    Endif
  Next
  
End

Private Sub FindEditor(sComp As String, sClass As String, bLocate As Boolean) As FEditor
  
  Dim sPath As String
  
  If sComp = "$" Or If Not sComp Then
    
    sPath = Project.FindPath(sClass)
    
  Else
    
    sPath = FindProject(sComp)
    
    If Not sPath And If bLocate And If Not $cIgnoreProject.Exist(sComp) Then
      Do
        sPath = FOpenProject.Locate(sComp)
        If Not sPath Then Break
        If File.Name(sPath) = sComp Then Break
        If Message.Warning(("Project name does not match component.\n\nDo you want to use it anyway?"), ("Use this project"), ("Cancel")) = 1 Then Break
      Loop
      If sPath Then
        SetProject(sComp, sPath)
      Else
        $cIgnoreProject[sComp] = True
      Endif
    Endif
    
    If sPath Then
      Project.Tree.AddOtherProject(sPath)
      sPath = FindClassPath(sPath, sClass)
    Endif
    
  Endif
  
  If sPath Then Return Project.LoadFile(sPath)
  
End

Public Sub Goto(sLoc As String) As FEditor

  Dim hForm As FEditor

  Dim sComp As String
  Dim iLine As Integer
  Dim sClass As String
  Dim aBreak As String[]

  aBreak = Breakpoints.FromString(sLoc)
  sComp = aBreak[0]
  sClass = aBreak[1]
  Try iLine = CInt(aBreak.Last)
  
  hForm = FindEditor(sComp, sClass, True)
  If hForm Then Project.OpenFile(hForm.Path, iLine)
  Return hForm
  
End

Public Sub ClearBreakpoints()

  Breakpoints.Clear()
  
  If Project.Running And If Not IsRunning() Then 
    WriteCommand("-*")
  Endif
  
End
