' Gambas module file

Private $cOpen As Collection
Private $cClose As Collection

Private $iLine As Integer
Private $iLevel As Integer
Private $iOpen As Integer

Private $bLastNewLine As Boolean
Private $bNeedNewLine As Boolean
Private $iLastDim As Integer
Private $aSwitch As Integer[]
Private $bNextIndent As Boolean

Private $bIndentDim As Boolean
Private $bRemoveSpace As Boolean
Private $bKeepVoidLine As Boolean
Private $iIndentSize As Integer
Private $bLocalDeclDone As Boolean

Private STATE_COMMENT As Integer
Private STATE_DOCUMENTATION As Integer
Private STATE_STRING As Integer

Public Sub _init()
  
  Dim sStr As String
  
  $cOpen = New Collection(gb.IgnoreCase)
  For Each sStr In ["DO", "FOR", "ELSE", "SELECT", "CASE", "DEFAULT", "WHILE", "REPEAT", "WITH", "CATCH", "FINALLY"]
    $cOpen[sStr] = True
  Next
  $cClose = New Collection(gb.IgnoreCase)
  For Each sStr In ["LOOP", "NEXT", "ENDIF", "END IF", "ELSE", "CASE", "DEFAULT", "WEND", "UNTIL", "END WITH", "CATCH", "FINALLY"]
    $cClose[sStr] = True
  Next
  ' IF and END SELECT are special
  
End

Private Sub RemoveLine(aPos As Integer[], iLine As Integer)

  Dim I As Integer
  
  While I < aPos.Count
    If aPos[I] = iLine Then
      aPos.Remove(I)
    Else
      If aPos[I] > iLine Then Dec aPos[I]
      Inc I
    Endif
  Wend
  
End

Private Sub InsertLine(aPos As Integer[], iLine As Integer, Optional iCount As Integer = 1)

  Dim I As Integer
  
  While I < aPos.Count
    If aPos[I] >= iLine Then aPos[I] += iCount
    Inc I
  Wend
  
End

Private Sub InsertText(hEditor As TextEditor, aText As String[])

  Dim sOld As String
  Dim sNew As String
  Dim sResult As String
  Dim sLine As String
  Dim sAction As String
  Dim iStart As Integer
  Dim iCount As Integer
  Dim iStartRemove As Integer
  Dim iCountRemove As Integer

  sOld = Temp$()
  sNew = Temp$()
  
  File.Save(sOld, hEditor.Text)
  File.Save(sNew, aText.Join("\n"))
  
  Exec ["diff", "-U0", sOld, sNew] To sResult
  
  For Each sLine In Split(sResult, "\n")
    
    If sLine Not Begins "@@" Then Continue
    
    For Each sLine In Split(sLine, " @", "", True)
      
      sAction = Left(sLine)
      With Split(Mid$(sLine, 2), ",")
        iStart = CInt(.[0]) - 1
        If .Count = 2 Then
          iCount = CInt(.[1])
        Else
          iCount = 1
        Endif
      End With
      
      Select Case sAction
        Case "-"
          iStartRemove = iStart
          iCountRemove = iCount
        Case "+"
          If iCountRemove Then
            If iCount = 0 Then Inc iStart
            hEditor.Remove(0, iStart, 0, iStart + iCountRemove)
          Endif
          If iCount Then 
            hEditor.Goto(0, iStart)
            hEditor.Insert(aText.Copy(iStart, iCount).Join("\n") & "\n")
          Endif
      End Select
      
    Next
    
  Next
  
End


Public Sub Run(hModule As Object)
  
  Dim sLine As String
  Dim aLine As String[]
  Dim iLine As Integer
  Dim aBookmark As Integer[]
  Dim bBookmark As Boolean
  Dim aBreakpoint As Integer[]
  Dim bBreakpoint As Boolean
  Dim X As Integer
  Dim Y As Integer
  Dim aText As String[]
  Dim bModified As Boolean
  Dim aSymbols As String[]
  Dim aTypes As Integer[]
  Dim iStart, iEnd As Integer
  Dim hEditor As TextEditor
  Dim sPath As String
  
  Inc Application.Busy
  
  STATE_COMMENT = TextHighlighterTheme.GetStateIndex("Comment")
  STATE_DOCUMENTATION = TextHighlighterTheme.GetStateIndex("Documentation")
  STATE_STRING = TextHighlighterTheme.GetStateIndex("String")
  
  hEditor = hModule.GetEditor()
  sPath = hModule.Path
  
  If hEditor.Selected Then
    iStart = Min(hEditor.Line, hEditor.SelectionLine)
    iEnd = Max(hEditor.Line, hEditor.SelectionLine)
  Else
    iStart = 0
    iEnd = hEditor.Count - 1
  Endif

  aBreakpoint = hEditor.Breakpoints
  aBookmark = hEditor.Bookmarks
    
  $bIndentDim = Settings["/FormatCode/IndentLocal", 1]
  $bRemoveSpace = Settings["/FormatCode/RemoveSpaces", 0]
  $bKeepVoidLine = Settings["/FormatCode/KeepVoidLines", 0]
  
  If LCase(File.Ext(sPath)) = "md" Then $bRemoveSpace = False
  
  $iOpen = 0
  $iLevel = 0
  If hEditor.TabIndent Then
    $iIndentSize = 0
    $iLevel = Len(hEditor.GetIndent(iStart))
  Else
    $iIndentSize = hEditor.TabSize
    $iLevel = Len(hEditor.GetIndent(iStart)) \ hEditor.TabSize
  Endif
  
  $bLastNewLine = True
  $iLastDim = -1
  $bNextIndent = False
  $aSwitch = New Integer[]
  
  aText = Split(hEditor.Text, "\n")
  iEnd = Min(aText.Max, iEnd)
  
  X = hEditor.Column
  Y = hEditor.Line
  
  iLine = iStart
  While iLine <= iEnd
    
    $iLine = iLine
    sLine = aText[iLine]

    If Not LTrim(sLine) Then
      $bLastNewLine = True
      sLine = GetIndent($iLevel)
    Else
      hEditor.GetHighlight(iLine, ByRef aSymbols, ByRef aTypes)
      Select Case hEditor.Mode
        Case "gambas"
          sLine = IndentLineGambas(sLine)
        Case "css"
          sLine = IndentLineBraces(sLine, aSymbols, aTypes, False)
        Case "c", "c++", "javascript"
          sLine = IndentLineBraces(sLine, aSymbols, aTypes, True)
      End Select
    Endif
    
    If iLine < iStart Then
      Inc iLine
      Continue
    Endif
    
    If $bRemoveSpace Then sLine = RTrim(sLine)
    aLine = Split(sLine, "\n")
    
    If aLine.Count <= 1 Then
      
      If aText[iLine] <> sLine Then
        aText[iLine] = sLine
        bModified = True
      Endif
      
    Else
      
      aText[iLine] = aLine[0]
      aLine.Remove(0)
      aText.Insert(aLine, iLine + 1)
      
      If aBookmark.Count Then
        InsertLine(aBookmark, iLine + 1, aLine.Max)
        bBookmark = True
      Endif
      If aBreakpoint.Count Then
        InsertLine(aBreakpoint, iLine + 1, aLine.Max)
        bBreakpoint = True
      Endif
      
      If Y >= (iLine + 1) Then Y += aLine.Max
      
      iLine += aLine.Count
      iEnd += aLine.Count
      bModified = True
      
    Endif
    
    Inc iLine
  Wend
  
  If Not $bKeepVoidLine Then
    
    iLine = Max(1, iStart)
    While iLine <= iEnd
      
      If Not Trim(aText[iLine]) And If Not Trim$(aText[iLine - 1]) Then
        
        aText.Remove(iLine - 1)
        bModified = True
        'hEditor.Remove(0, iLine - 1, 0, iLine)
        Dec iEnd
        
        If aBookmark.Count Then
          RemoveLine(aBookmark, iLine)
          bBookmark = True
        Endif
        If aBreakpoint.Count Then
          RemoveLine(aBreakpoint, iLine)
          bBreakpoint = True
        Endif
        
        If Y > iLine Then Dec Y
        
      Else
        
        Inc iLine
        
      Endif
      
    Wend
    
  Endif
  
  If bModified Then
    
    hEditor.Begin
    
    InsertText(hEditor, aText)
    
    'hEditor.SelectAll
    'hEditor.Insert(aText.Join("\n"))
    
    hEditor.Bookmarks = aBookmark
    hEditor.Breakpoints = aBreakpoint
    
    hEditor.End
    hEditor.Goto(X, Y)

    If hModule Is FEditor Then FDebugInfo.RefreshTabs(hModule.Path, hModule.Name)
    
  Endif
  
  Dec Application.Busy
  
End

Private Sub IsStruct() As Boolean
  
  Dim aSym As String[] = Highlight.Symbols
  Dim iInd As Integer
  
  If aSym.Count >= 3 Then
    If aSym[iInd] == "PUBLIC" Then Inc iInd
    Return aSym[iInd] == "STRUCT"
  Endif
  
End

Private Sub IsEndStruct() As Boolean
  
  Dim aSym As String[] = Highlight.Symbols
  
  If aSym.Count = 2 And If aSym[0] == "END" And If aSym[1] == "STRUCT" Then Return True
  
End

Private Sub GetIndent(iLevel As Integer) As String
  
  If $iIndentSize = 0 Then
    Return String$(iLevel, "\t")
  Else
    Return Space$(iLevel * $iIndentSize)
  Endif
  
End


Private Sub IndentLineGambas(sLine As String) As String
  
  Dim aSym As String[]
  Dim sAdd As String
  Dim iNextLevel As Integer
  Dim sSym As String
  Dim sFuncSym As String
  Dim iPos As Integer
  Dim iLastType As Integer
  Dim iLevel As Integer
  Dim bNextIndent As Boolean
  Dim I As Integer

  If sLine Begins "#!" Then Return sLine

  sLine = LTrim(sLine)
  
  If Left(sLine) = "'" Then
    Return GetIndent($iLevel) & sLine
  Endif
  
  aSym = Highlight.Analyze(Highlight.Purge(Trim(sLine)))
  While aSym.Count
    iLastType = Highlight.Types[aSym.Max]
    If iLastType <> Highlight.Comment And If iLastType <> Highlight.Help Then Break
    aSym.Remove(aSym.Max)
  Wend
  
  If aSym.Count Then
    
    bNextIndent = $bNextIndent
    $bNextIndent = False
    
    ' bLastString = False
    ' If $bLastString And If Highlight.Types[0] = Highlight.String Then
    '   bLastString = True
    ' Endif
    ' 
    ' $bLastString = iLastType = Highlight.String
    
    If iLastType = Highlight.Operator Then
      sSym = aSym.Last
      If InStr(":]);.!", sSym) = 0 Then 'And If aSym[0] <> "PRINT" Then 
      
        $bNextIndent = True
        
        If sSym = "," Then
          For I = aSym.Max - 1 DownTo 0
            sSym = aSym[I]
            If sSym = "PRINT" Then
              $bNextIndent = False
              Break
            Else If InStr("[(", sSym) Then
              Break
            Endif
          Next
        Endif
        
      Endif
    Else If iLastType = Highlight.String And If Highlight.Types[0] = Highlight.String Then
      bNextIndent = True
    Endif
    
    If $bNeedNewLine Then
      If Not $bLastNewLine Then sAdd = "\n"
      $bNeedNewLine = False
    Endif
    
    If aSym.Count = 2 And If aSym[1] = ":" Then
      iNextLevel = $iLevel
      Dec $iLevel
    Else If FEditor.IsCurrentProc() Then
      $iLevel = 0
      iNextLevel = 1
      $bNeedNewLine = True
      If Not $bLastNewLine Then sAdd = "\n"
      $bLocalDeclDone = False
    Else If FEditor.IsCurrentEndProc() Then
      $iLevel = 0
      iNextLevel = 0
      $bNeedNewLine = True
      If Not $bLastNewLine Then sAdd = "\n"
    Else If IsStruct() Then
      $iLevel = 0
      iNextLevel = 1
      If Not $bLastNewLine Then sAdd = "\n"
    Else If IsEndStruct() Then
      $iLevel = 0
      iNextLevel = 0
      $bNeedNewLine = True
    Else
      sSym = aSym[0]
      If aSym.Count > 2 Then sFuncSym = LCase(aSym[2])
      
      If sSym == "END" And If aSym.Count >= 2 Then sSym &= " " & aSym[1]
      
      If sSym == "DIM" And If Not $bLocalDeclDone Then
        
        $iLastDim = $iLine
        If Not $bIndentDim Then $iLevel = 0
        iNextLevel = 1
      
      Else
      
        $bLocalDeclDone = True
      
        If $iLastDim = ($iLine - 1) Then
          If Not bNextIndent Then 'Not bLastString And If Not bLastWrap Then
            If Not $bLastNewLine Then sAdd = "\n"
          Else
            Inc $iLastDim
          Endif
        Endif
      
        If sSym == "END SELECT" Then
          $iLevel -= 2
          iNextLevel = $iLevel
        Else If Right(aSym[0]) = ":" Then
          iNextLevel = $iLevel
          $iLevel = 0
        Else If sSym == "IF" Then
          iNextLevel = $iLevel
          iPos = aSym.Find("THEN", gb.IgnoreCase)
          If iPos < 0 Or If iPos = aSym.Max Then Inc iNextLevel
        Else If sSym == "SELECT" Then
          '$iLastSelect = $iLine
          iNextLevel = $iLevel + 2
        Else
          If Highlight.Types[0] = Highlight.Keyword And If $cClose.Exist(sSym) Then Dec $iLevel
          iNextLevel = $iLevel
          If Highlight.Types[0] = Highlight.Keyword And If $cOpen.Exist(sSym) Then Inc iNextLevel
        Endif
        
      Endif
      
    Endif
    
    $bLastNewLine = False 
    
    $iLevel = Max(0, $iLevel)
    iNextLevel = Max(0, iNextLevel)
    
    iLevel = $iLevel
    If bNextIndent Then Inc iLevel
    sLine = sAdd & GetIndent(iLevel) & sLine '& " '" & If(bLastWrap, "W", "") & If(bLastString, "S", "")
    $iLevel = iNextLevel
    
    Return sLine
    
  Endif
  
End

Private Sub IndentLineBraces(sLine As String, aSymbols As String[], aTypes As String[], bLang As Boolean) As String

  Dim I As Integer
  Dim iNextLevel As Integer
  Dim iClose As Integer
  Dim iOpen As Integer
  Dim sSym As String
  Dim bNextIndent As Boolean
  Dim sFirst As String
  Dim sLast As String

  iNextLevel = $iLevel
  bNextIndent = $bNextIndent
  $bNextIndent = False
  
  For I = 0 To aSymbols.Max
    
    If aTypes[I] = STATE_COMMENT Then Continue
    If aTypes[I] = STATE_DOCUMENTATION Then Continue
    
    sSym = aSymbols[I]
    If Not Trim(sSym) Then Continue
    
    sLast = sSym
    
    If aTypes[I] = STATE_STRING Then Continue

    If Not sFirst Then sFirst = sSym
    
    iOpen += NInStr(sSym, "{")
    iClose += NInStr(sSym, "}")
    iOpen += NInStr(sSym, "(")
    iClose += NInStr(sSym, ")")
    iOpen += NInStr(sSym, "[")
    iClose += NInStr(sSym, "]")
    
  Next
  
  If Not sLast Then Return sLine
  
  If bLang Then
    
    If sFirst = "if" Or If sFirst = "else" Or If sFirst = "for" Or If sFirst = "while" Or If sFirst = "do" Then
      If InStr(";}{", Right(sLast)) = 0 Then
        $bNextIndent = True
      Endif
    Else If sFirst = "case" Or If sFirst = "default" Then
      Dec $iLevel
    Else If sFirst = "switch" Then
      $aSwitch.Push($iLevel)
    Else If sFirst = "{" Then
      If $aSwitch.Count And If $iLevel = $aSwitch[$aSwitch.Max] Then Inc iOpen
    Else If sFirst = "}" Then
      If $aSwitch.Count And If $iLevel = ($aSwitch[$aSwitch.Max] + 2) Then 
        Inc iClose
        $aSwitch.Pop()
      Endif
    Endif
  
    If bNextIndent Then 
      If sFirst <> "{" Then Inc $iLevel
    Endif
    
  Endif

  If iClose > iOpen Then $iLevel += iOpen - iClose
  iNextLevel += iOpen - iClose

  $iLevel = Max(0, $iLevel)
  iNextLevel = Max(0, iNextLevel)

  sLine = GetIndent($iLevel) & LTrim(sLine)
  $iLevel = iNextLevel
  Return sLine

End

