跳转到内容

Visual Basic for Applications/VBA 代码缩进器

来自维基教科书,开放的书籍,开放的世界
  • 这个公认的较长代码模块用于缩进或格式化剪贴板中找到的任何 VBA 代码。 它使用相当标准的代码缩进规则,可以在此页面代码的格式中看到示例。现在,用于 64 位 Windows 中 VBA 编辑器的商业格式化实用程序已经可用,但下面的代码不是作为加载项运行,而是作为简单的 VBA 代码模块运行。在一定范围内,它适用于所有最新的 Windows 和 Excel 版本,以及可以运行 VBA 的其他Microsoft Office应用程序。对于即使是最挑剔的用户来说,它也足够快。
  • 它将缩进后的文本放回剪贴板以替换原始文本。 用户可以随意粘贴它。
  • 第一次尝试只有很少的选项.
    • 可以设置缩进深度。 这是通过设置每个制表符的空格数来完成的。
    • 可以设置连续空白行的数量。 这限制为无、一个或按发现的原样保留。
    • 可以保留或删除现有的行号,尽管没有提供重新编号的功能。
    • 共享的标签行可以拆分到它们自己的行上。
    • 代码通常将作为续行的注释替换为它们自己行上的注释,尽管用户可以选择完全避免对注释进行更改。如果允许更改注释
      • Rem 注释样式可选地替换为单引号样式。
      • 用户可以选择在注释单引号后插入一个空格,或者在单引号已经存在的地方将其删除。

VBA 代码缩进器 - 格式化模块

[编辑 | 编辑源代码]

几点值得一提

  • 代码作为单个完整的标准模块运行。 运行过程Indenter() 以缩进剪贴板内容。
  • 用户也可以考虑使用可选的用户窗体 来预览缩进后的输出字符串(过程Indenter 中的sRet)然后进行粘贴
  • 选择具有完整配对的代码 以最大程度地利用缩进器。例如,这样就没有明显的没有End IfIf。也就是说,可以缩进代码片段、过程或整个模块。实际上,任何具有识别出的 VBA 关键字和换行符的文本都将被缩进。用户应将 VBA 编辑器错误设置设置为中断未处理的错误 以避免来自有意引发错误的不必要中断。
  • 发现此事有趣 的读者 可以在讨论中添加任何错误报告,我会在有空时查看它们。

使用的工作方法

[编辑 | 编辑源代码]

所有工作都在模块级字符串数组中完成。过程是

  • 首先获取剪贴板字符串。 用于获取字符串的方法与本系列中其他地方列出的方法相同。有关DataObject 剪贴板方法,请参阅剪贴板 VBA。此方法取代了先前使用虚拟用户窗体的方法。
  • 使用字符串作为一组行加载数组。 只加载代码部分,不加载可能存在的任何行号。然后从每一行删除现有的缩进。这意味着任何前导和尾随空格和制表符。
  • 重新连接用延续标记断开的行。 此过程避免了许多行识别问题,尤其是来自没有自己延续标记的折叠的后续注释行。
  • 识别并使用行类型标记数组。 出现在封闭结构(如For...NextSub...End Sub 对)中的行类型对于缩进特别重要。这些分别标记为开始结束 行类型。最初,哪些 结构并不重要,重要的是它们是否是开始或结束。中间 行(如Else)也需要识别,以及注释空白 行,以及一大组所谓的其他 行。
  • 匹配相应的开始结束 行。 它是这样工作的:从代码的顶部开始;选择第一个开始 行并将其计为一个,然后向下移动,增加开始结束 计数器,直到两个计数器相等;然后找到匹配的结束 行。在重置计数器后,向下移动到第二个开始,并重复此过程,直到所有开始 行都已匹配。数组开始 行用相应结束 匹配的行号标记。
  • 检查配对计数。 如果代码不包含至少一个开始-结束结构,或者开始结束 总数不匹配,则会邀请用户继续退出。会引发用户分配的错误来实现退出。
  • 为主要结构分配缩进和缩出计数。 从代码行的顶部开始,转到标记为开始 的第一行。为位于该开始 行与其相应结束 行之间的所有行添加一个缩进计数。向下移动到下一个开始 行,并重复此过程,直到完成。现在,对于数组中任何标记为中间 的行,缩出,即减去一个缩进计数。缩进计数使用缩进选项转换为空格。
  • 将缩进空格加入代码行并从中制作一个字符串。 尽管用户可以设置间距选项,但每个缩进单元四个空格似乎最有用,就像 VBA 编辑器本身一样。
  • 将缩进后的字符串上传到剪贴板,然后提示它已存在,准备粘贴。

代码模块修改

[编辑 | 编辑源代码]

2018 年 12 月 15 日:代码修改为在剪贴板不是文本时添加错误。
2018 年 12 月 14 日:代码修改为使用DataObject 复制和粘贴方法。
2017 年 3 月 29 日:对 GetClipboard 函数注释进行了轻微编辑。

Option Explicit
Private sW() As String

Sub Indenter()
    ' ===============================================================================
    ' Run the sub "Indenter" to format any VBA code text that is on the clipboard.
    ' The indented version will be found on the clipboard, replacing the original.
    ' ===============================================================================
    
    Dim sClip As String, msg As String
    Dim vT As Variant, vU As Variant, vS As Variant, sRet As String
    Dim bModifyComments As Boolean, bMC As Boolean, bOnlyAposComments As Boolean
    Dim bOAC As Boolean, bApostropheSpaced As Boolean, bAS As Boolean
    Dim bSplitLabelLines As Boolean, bSL As Boolean, bKeepLineNumbers As Boolean
    Dim bKLN As Boolean, nSpacesPerIndentTab As Long, nSPIT As Long
    Dim nMaxAdjacentBlankLines As Long, nMABL As Long
    
    ' ===============================================================================
    '                          SET USER OPTIONS HERE
    ' ===============================================================================
    nSpacesPerIndentTab = 4    ' Sets number of spaces per tab - depth of indenting,
    '                          ' best settings found to be 2, 3, or 4.
    '---------------------------------------------------------------------------------
    nMaxAdjacentBlankLines = 7 ' Sets number of adjacent blank lines in output
    '                          ' 0 for none, or 1. Set > 1 to leave as found.
    '---------------------------------------------------------------------------------
    bModifyComments = False    ' True to allow other modifications to comments, and
    '                          ' changing of continuation comment groups into own-line
    '                          ' comments.  False, for no changes to comments.
    '       'set bModifyComments to true for these to have any effect;
    bOnlyAposComments = True   ' True to change any r e m style comments to
    '                          ' to apostrophe style, else false to leave as found.
    bApostropheSpaced = False  ' True to place spaces after apostrophies in
    '                          ' comments, else False to remove any single space.
    '---------------------------------------------------------------------------------
    bSplitLabelLines = False   ' True to split label lines onto own lines if they
    '                          ' are shared, else False to leave as they are found.
    '---------------------------------------------------------------------------------
    bKeepLineNumbers = True    ' True to preserve existing line numbers, if any,
    '                          ' else False to remove any numbers during indent.
    '---------------------------------------------------------------------------------
    '
    ' ================================================================================
    
    nSPIT = nSpacesPerIndentTab: nMABL = nMaxAdjacentBlankLines
    bMC = bModifyComments: bOAC = bOnlyAposComments: bAS = bApostropheSpaced
    bSL = bSplitLabelLines: bKLN = bKeepLineNumbers
    
    On Error GoTo Err_Handler
    Erase sW()                 ' erase work array
    ' ---------------------------------------------------------------------------------
    sClip = GetFromClip        '  GETS CLIPBOARD STRING
    ProjStrTo1DArr sClip, vS   '  String of lines to 1D array of lines. Base zero.
    ModifyComments vS, vT, bOAC, bAS, bMC '  Modifies comments; removes continuation
    LabelLineSplit vT, vU, bSL '  1D array to 1D array. Splits shared label lines.
    ClpToArray vU              '  1D array to 2D module array. Separates line numbers.
    JoinBrokenLines            '  2D array. Joins-up continuation lines.
    GetLineTypes               '  2D array. Marks array with line types.
    MatchPairs                 '  2D array. Matches-up starts and ends.
    CheckPairs                 '  2D array. Crude checking by pair counts.
    Indents                    '  2D array. Adds tab counts for indents
    Outdent                    '  2D array. Subtracts tab count for outdents.
    SpacePlusStr nSPIT, bKLN   '  2D array. Adds indent spaces to line strings.
    MaxBlanks sRet, nMABL      '  2D array to STRING. Also limits blank lines.
    CopyToClip sRet            ' INDENTED STRING TO CLIPBOARD
    MsgBox "The indented string is now on the clipboard."
    
    ' ---------------------------------------------------------------------------------
    Exit Sub
    
Err_Handler:
    If Err.Number <> 0 Then
        Select Case Err.Number
        Case 12345 ' raised in CheckPairs
            ' optional exit - user selected exit
            ' partial selection has mismatched structure bounds
            ' or only trivial text without structures at all
            Err.Clear
            Exit Sub
        Case 12346 ' raised in JoinBrokenLines
            ' compulsory exit
            ' partial selection breaks a statement continuation group
            Err.Clear
            Exit Sub
        Case 12347 ' raised in ModifyComments
            ' compulsory exit
            ' partial selection breaks a comment continuation group
            Err.Clear
            Exit Sub
        Case -2147221404 'clipboard data object not text
            MsgBox "Clipboard does not contain text - closing"
            Err.Clear
            Exit Sub
        Case Else
            ' all other errors
            msg = "Error # " & str(Err.Number) & " was generated by " _
            & Err.Source & Chr(13) & Err.Description
            Err.Clear
            MsgBox msg, vbCritical, "Error"
            Exit Sub
        End Select
    End If
    
End Sub

Function CopyToClip(sIn As String) As Boolean
    'passes the parameter string to the clipboard
    'set reference to Microsoft Forms 2.0 Object Library.
    'Clipboard cleared when launch application closes.
    
    Dim DataOut As DataObject
    
    Set DataOut = New DataObject
    
    'first pass textbox text to dataobject
    DataOut.SetText sIn
    
    'then pass dataobject text to clipboard
    DataOut.PutInClipboard
    
    'release object variable
    Set DataOut = Nothing
    
    CopyToClip = True
    
End Function

Function GetFromClip() As String
    'passes clipboard text to function name
    'If clipboard not text, an error results
    'set reference to Microsoft Forms 2.0 Object Library.
    'Clipboard cleared when launch application closes.
    
    Dim DataIn As DataObject
    
    Set DataIn = New DataObject
    
    'clipboard text to dataobject
    DataIn.GetFromClipboard
    
    'dataobject text to function string
    GetFromClip = DataIn.GetText
    
    'release object variable
    Set DataIn = Nothing
    
End Function

Sub ProjStrTo1DArr(sIn As String, vR As Variant)
    ' Input is a string of code lines that are newline separated
    ' Output is a 1D array containing the set of lines
    'vR IS ZERO BASED
    
    Dim LB As Long, UB As Long
    
    ' split clipboard string into lines
    If sIn <> "" Then
        vR = Split(sIn, vbNewLine)
        LB = LBound(vR): UB = UBound(vR)
    Else
        Exit Sub
    End If
    
End Sub

Sub ModifyComments(vA As Variant, vR As Variant, _
    Optional bOnlyAposComments As Boolean = True, _
    Optional bApostropheSpaced As Boolean = True, _
    Optional bEnable As Boolean = True)
    'Input 1D array vA; Output 1D array vR
    'Changes all comment continuation groups into
    'stand-alone comments, and modifies comments.
    'Comments are modified in ApostropheSpaces().
    'When bDisable is true, the input array is returned intact
    'vR IS BASE ZERO
    
    Dim vB As Variant, bHasMarker As Boolean
    Dim m As Long, n As Long, LB1 As Long, UB1 As Long
    Dim sL As String, sFP As String, sT As String
    Dim sCom1 As String, sCom As String, sComR As String
    Dim sR1 As String, sR2 As String, sR4 As String, sR5 As String
    Dim bOAC As Boolean, bAS As Boolean
    
    bOAC = bOnlyAposComments
    bAS = bApostropheSpaced
    
    'use a work array
    LB1 = LBound(vA): UB1 = UBound(vA)
    
    'enable or disable proc
    If bEnable = False Then
        ReDim vR(LB1 To UB1)
        vR = vA
        Exit Sub
    Else
        ReDim vB(LB1 To UB1)
        vB = vA
    End If
    
    'misc string definitions
    sR1 = Chr(82) & Chr(101) & Chr(109) & Chr(32) 'R e m + spc
    sR2 = Chr(82) & Chr(101) & Chr(109) & Chr(58) 'R e m + colon
    sR4 = Chr(39)                                 'apost
    sR5 = Chr(39) & Chr(32)                       'apost + spc
    
    'LOOP THROUGH CODE LINES
    For n = LB1 To UB1
        m = n      ' use internal loop counter
        sL = vB(m) ' get line string
        If sL = "" Then GoTo NextArrayLine
        ' test whether line string qualifies at all
        SplitStrAndComment sL, sFP, sCom
        
        ' FIND IF LINE HAS COMMENT
        If sCom <> "" Then    'line contains a comment
            
            ' FIND FIRST LINE OF CONTINUATION GROUP
            If Right$(sL, 2) = " _" Then 'found first of group
                ' remove comment's continuation markings
                sCom1 = Left$(sCom, Len(sCom) - 2)
                ' do the modifications
                ApostropheSpaces sCom1, sComR, bOAC, bAS
                vB(m) = sFP & sComR ' update with remake
                m = m + 1 'increment group counter
                ' catch exception for incomplete group
                If m > UB1 Then
                    MsgBox "Broken continuation group detected." & vbCrLf & _
                    "Please make a more complete selection."
                    Err.Raise 12347
                    Exit Sub
                Else
                    ' do other parts of continuation group
                    GoTo DoRestOfGroup
                End If
            Else
                ' HAS COMMENT BUT NO CONTINUATION
                sCom1 = sCom
                ' do the modifications
                ApostropheSpaces sCom1, sComR, bOAC, bAS
                vB(m) = sFP & sComR ' update with remake
                ' go to next array line
                GoTo NextArrayLine
            End If
        Else
            ' HAS NO COMMENT AT ALL
            GoTo NextArrayLine
        End If
        
DoRestOfGroup:
        'PROCESS SECOND GROUP LINE UP TO LAST
        Do Until m > UB1
            sL = Trim(vB(m))                ' get line string
            bHasMarker = sL Like sR1 & "*" Or sL Like sR2 & "*" _
            Or sL Like sR4 & "*" Or sL Like sR5 & "*"
            If bHasMarker = False Then
                sL = sR5 & sL               ' add comment mark
            End If
            
            ' modify and exit for line group last
            If Right$(sL, 2) <> " _" Then
                ApostropheSpaces sL, sComR, bOAC, bAS ' modify comment
                vB(m) = sComR               ' update array
                n = m - 1              ' update loop counter
                Exit Do              'group ending complete
            End If
            
            ' modify and go to next if not group last
            sL = Left$(sL, Len(sL) - 2) 'remove cont mark
            ApostropheSpaces sL, sComR, bOAC, bAS     ' modify comment
            vB(m) = sComR                  ' update array
            m = m + 1               'increment group counter
            If m > UB1 Then
                MsgBox "Broken continuation group detected." & vbCrLf & _
                "Please make a more complete selection."
                Err.Raise 12347
                Exit Sub
            End If
        Loop
        ' go to next array line
        GoTo NextArrayLine
        
NextArrayLine:
        
        ' resets
        bHasMarker = False
        sCom = "": sCom1 = "": sComR = ""
        m = 0: sL = "": sFP = "": sT = ""
    Next n
    
Transfers:
    
    ReDim vR(LB1 To UB1)
    vR = vB
    
End Sub

Function ApostropheSpaces(sIn As String, sOut As String, _
    Optional bOnlyAposComments As Boolean = True, _
    Optional bApostropheSpaced As Boolean = False) As Boolean
    ' Comment string in, modified comment string out
    ' These always start with one of two comment marker styles;
    ' r e m style or apostrophe style. Each has variations.
    ' At present, sIn broken line parts arrive apostrophied.
    
    ' ASCI values of work characters
    ' asterisk; chr(42), apostrophe; chr(39), double-quote; chr(34)
    ' R: chr(82),e: chr(101),m: chr(109),colon: chr(58)
    
    Dim sR3 As String, sL As String, bModComments As Boolean
    Dim sR1 As String, sR2 As String, sR4 As String, sR5 As String, bHasMarker As Boolean
    
    ' String definitions
    sR1 = Chr(82) & Chr(101) & Chr(109) & Chr(32) 'R e m + spc
    sR2 = Chr(82) & Chr(101) & Chr(109) & Chr(58) 'R e m + colon
    sR4 = Chr(39)                                 'apost
    sR5 = Chr(39) & Chr(32)                       'apost + spc
    
    bModComments = True ' true to apply local changes, else false to return sIn.
    
    If bModComments = False Then
        sOut = sL
        Exit Function
    End If
    
    'get line string
    sL = sIn
    
    ' Find if line fits any comment pattern
    bHasMarker = sL Like sR1 & "*" Or sL Like sR2 & "*" _
    Or sL Like sR4 & "*" Or sL Like sR5 & "*"
    If bHasMarker = True Then
        
        ' REPLACE REM STYLE WITH APOSTROPHE
        If bOnlyAposComments = True Then
            ' take first four charas of comment...
            sR3 = Left$(sL, 4)
            'if they fit r e m pattern...
            If sR3 = sR1 Or sR3 = sR2 Then
                'change the first four to an apostrophe
                sR3 = Replace(sL, sR3, sR4, 1, 1)
                sL = sR3
                sR3 = ""
            End If
        End If
        
        ' SET SPACE BEFORE APOSTROPHE
        If bApostropheSpaced = True Then
            ' take first two charas of comment...
            sR3 = Left$(sL, 2)
            'if they fit apostrophe pattern...
            If sR3 <> sR5 Then
                'change the first two to an apostrophe
                sR3 = Replace(sL, sR4, sR5, 1, 1)
                sL = sR3
                sR3 = ""
            End If
        Else
            ' bApostropheSpaced is false so remove short space.
            ' provided that no more than one existing space,
            ' replace first instance of apos + spc with just apos.
            If Left$(sL, 3) <> sR5 & Chr(32) And Left$(sL, 2) = sR5 Then
                sR3 = Replace(sL, sR5, sR4, 1, 1)
                sL = sR3
                sR3 = ""
            End If
        End If
        
    Else
        MsgBox "Pattern failure in ApostropheSpaces"
        Exit Function
    End If
    
    sOut = sL
    
    ApostropheSpaces = True
    
End Function

Function LabelLineSplit(vA As Variant, vR As Variant, Optional bEnable As Boolean = True) As Boolean
    'Input vA, 1D array with block of code lines.
    'Output vR, 1D array with label lines split.
    'Increases line count when if splitting is done
    'Takes account of line continuations in decision making.
    'When bDisable is true, the input array is returned intact
    'vR IS BASE ZERO
    
    Dim n As Long, sRC As String, vC As Variant
    Dim sLN As String, sLL As String
    Dim sL As String, sS As String, bPrevIsBroken As Boolean
    Dim LBvA As Long, UBvA As Long, UB As Long
    
    LBvA = LBound(vA): UBvA = UBound(vA)
    
    'enable or disable proc
    If bEnable = False Then
        ReDim vR(LBvA To UBvA)
        vR = vA
        Exit Function
    Else
        ReDim vR(LBvA To 0)
    End If
    
    sRC = Chr(82) & Chr(101) & Chr(109) 'r e m
    
    'Conditional transfer of lines
    For n = LBvA To UBvA
        
        'get full line string
        sL = Trim(vA(n))
        
        'exclusions
        'tranfer intact if line blank or
        'either kind of comment
        If sL = "" Or Left$(sL, 1) = Chr(39) Or _
            Left$(sL, 3) = sRC Then
            ReDim Preserve vR(LBound(vR) To UBound(vR) + 1)
            UB = UBound(vR)
            vR(UB) = Trim(sL)
            GoTo SkipThisOne
        End If
        
        ' find if it has a label
        If n = LBvA Then
            ' for first line only
            ' assume worth splitting
            SplitLineParts sL, sLN, sLL, sS
        Else ' for all lines after first
            ' test to see if prev line continued
            bPrevIsBroken = Trim(vA(n - 1)) Like "* _"
            If Not bPrevIsBroken Then 'test for label
                SplitLineParts sL, sLN, sLL, sS
            Else
                ' CONTINUATION SO TRANSFER IT INTACT
                ReDim Preserve vR(LBound(vR) To UBound(vR) + 1)
                UB = UBound(vR)
                vR(UB) = Trim(sL)
                GoTo SkipThisOne
            End If
        End If
        
        ' LABEL ACTION
        If sLL <> "" Then
            If Trim(sS) <> "" Then
                ' THERE IS A SHARED LABEL LINE TO SPLIT
                
                ReDim Preserve vR(0 To UBound(vR) + 1)
                UB = UBound(vR)
                vR(UB) = Trim(sLL) ' label onto line
                
                ReDim Preserve vR(0 To UBound(vR) + 1)
                UB = UBound(vR)
                vR(UB) = Trim(sS)
                
            Else ' ALREADY ON ITS OWN LINE
                ' so transfer label to array
                ReDim Preserve vR(LBound(vR) To UBound(vR) + 1)
                UB = UBound(vR)
                vR(UB) = Trim(sLL)
            End If
        Else   ' NOT A LABEL AT ALL SO TRANSFER IT INTACT
            ReDim Preserve vR(LBound(vR) To UBound(vR) + 1)
            UB = UBound(vR)
            vR(UB) = Trim(sL)
        End If
        
SkipThisOne:
        sL = "": sLN = "": sLL = "": sS = ""
        
    Next n
    
Transfers:
    
    ReDim vC(0 To UB - 1)
    For n = LBound(vC) To UBound(vC)
        vC(n) = vR(n + 1)
    Next n
    
    'exit as zero based array
    ReDim vR(LBound(vC) To UBound(vC))
    vR = vC
    
End Function

Function SplitStrAndComment(sIn As String, sFirstPart As String, sComment As String) As Boolean
    '==============================================================================================
    ' Returns input-less-comment in sFirstPart and any comment string in sComment.
    ' Input sIn supplies one VBA code line string, and the two parts are returned untrimmed.
    ' Has good immunity to suffering and causing corruption from comment and quote text.
    ' For no comment found, sFirstPart is sIn, and sComment is empty.
    ' Method:  Makes two runs; one search for apostrophe comments, and next for r e m comments;
    ' Removes any double quote pairs until relevant comment mark is located before any double quote.
    ' If any results are found, the one without search error has the comment that is longest.
    ' String stripped of quotes and position of comment mark are available but not returned here.
    '==============================================================================================
    
    Dim nPos As Long, sNoQuotes As String, sCmMrk As String
    Dim nPos1 As Long, nPos2 As Long, sNoQuotes1 As String
    Dim str1 As String, Str2 As String, m As Long
    Dim vM As Variant, nLA As Long, nLR As Long, sNoQuotes2 As String
    Dim q1 As Long, q2 As Long, a As Long, s1 As String, s2 As String
    Dim bQuote As Boolean, bComment As Boolean, sA As String
    Dim bACFound As Boolean, bRCFound As Boolean
    
    ' ASCI values of work characters
    ' asterisk; chr(42), apostrophe; chr(39), double-quote; chr(34)
    ' R: chr(82),e: chr(101),m: chr(109),colon: chr(58)
    
    'two runs; first for apos, then r e m comments
    vM = Array(Chr(39), Chr(82) & Chr(101) & Chr(109))
    str1 = sIn
    
    'run loop for each of two searches
    For m = 1 To 2
        'select one of two comment marks to search for
        sCmMrk = vM(m - 1) 'zero based
        
        ' check the line string patterns
        ' asterisk; chr(42), apostrophe; chr(39), double-quote; chr(34)
        bComment = str1 Like Chr(42) & sCmMrk & Chr(42) ' for an apostrophe
        bQuote = str1 Like Chr(42) & Chr(34) & Chr(42)   ' for a double quote
        
        If bComment = True And bQuote = True Then
            'has comment mark and has double quote
            ' set initial value
            q2 = 1
            Do
                ' get postion of first comment mark
                a = InStr(q2, str1 & sCmMrk, sCmMrk)
                ' get postion of first double quote
                q1 = InStr(q2, str1 & Chr(34), Chr(34))
                
                If a <= q1 Then
                    'found comment
                    sA = Right$(str1, Len(str1) - a + 1)
                    nPos = a
                    sNoQuotes = str1
                    GoTo Output
                ElseIf a > q1 Then
                    'find next quote
                    q2 = InStr(q1 + 1, str1 & Chr(34), Chr(34))
                    'if next quote is found
                    If q2 <> 0 Then
                        'remove charas from q1 to q2 inclusive
                        Str2 = Left$(str1, q1 - 1) & Right$(str1, Len(str1) - q2)
                        'set new start position for search
                        q2 = q2 + 1
                    End If
                End If
            Loop Until (a = q1)
            
        ElseIf bComment = True And bQuote = False Then
            ' has comment mark but has no double quote
            ' so return original str and comment details
            str1 = str1
            a = InStr(1, str1 & sCmMrk, sCmMrk)    ' position of first comment mark
            sA = Right$(str1, Len(str1) - a + 1) ' comment string
            nPos = a                            ' return position of comment
            sNoQuotes = str1                    ' return string without quotes
            GoTo Output
        Else
            ' no comment mark but has double quote, or
            ' no comment mark and no double quote.
            ' so return original string
            sA = ""
            nPos = 0
            sNoQuotes = str1
            GoTo Output
        End If
        
Output:
        'get details for each of two searches.
        If m = 1 Then              'for apostrophe comment search
            nLA = Len(sA) 'apos
            s1 = sA
            nPos1 = nPos
            sNoQuotes1 = sNoQuotes
        Else                       'for r e m comment search
            nLR = Len(sA) 'r e m
            s2 = sA
            nPos2 = nPos
            sNoQuotes2 = sNoQuotes
        End If
        
        'select and return details for longest comment
        If nLA > nLR Then
            bACFound = True                      'apos comment flag
            nPos = nPos1                         'position of comment
            sNoQuotes = sNoQuotes1               'de-quoted original
            sFirstPart = Left$(str1, nPos - 1)    'str before comment
            sComment = s1                        'comment string
        ElseIf nLR > nLA Then
            bRCFound = True                      'r e m comment flag
            nPos = nPos2 'de-quoted original     'position of comment
            sNoQuotes = sNoQuotes2               'de-quoted original
            sFirstPart = Left$(str1, nPos - 1)    'str before comment
            sComment = s2                        'comment string
        Else
            'no comments found
            sFirstPart = str1                     'str before comment
            sComment = ""                        'comment string
        End If
    Next m
    
    SplitStrAndComment = True
    
End Function

Sub ClpToArray(vA As Variant)
    ' loads array with lines of clipboard
    'vA IS 1D BASE ZERO ARRAY
    'sW() IS 2D BASE ONE MODULE LEVEL ARRAY
    
    Dim n As Long, m As Long, Num As Long
    Dim sLN As String, sLS As String
    Dim sLN1 As String, sLS1 As String
    Dim LBvA As Long, UBvA As Long
    Dim bPrevIsBroken As Boolean
    Dim sL As String, sLP As String
    
    'get bounds of vA
    LBvA = LBound(vA): UBvA = UBound(vA)
    
    ' count lines in vA clipboard sample
    Num = UBvA - LBvA + 1
    ' redim array
    ReDim sW(1 To 12, 1 To Num)
    
    ' load lines
    For n = LBvA To UBvA
        
        sL = Trim(vA(n))
        If n <> LBvA Then sLP = Trim(vA(n - 1))
        
        ' LINE NUMBER SPLIT DEPENDS ON CONTINUED LINES
        ' split line into line numbers and line strings
        ' find if it has a line number
        If n = LBvA Then ' for first vA line only
            ' attempt split anyway
            SplitLineNums sL, sLN1, sLS1
            sLN = sLN1: sLS = sLS1
        Else ' for all lines after first
            ' test to see if prev line continued
            bPrevIsBroken = sLP Like "* _"
            If Not bPrevIsBroken Then
                ' LOAD BOTH LINE NUMBER AND LINE STRING
                SplitLineNums sL, sLN1, sLS1
                sLN = sLN1: sLS = sLS1
            Else
                ' CONTINUATION - LOAD LINE STRING ONLY
                ' any leading number is not a line number
                sLN = "": sLS = sL
            End If
        End If
        
        m = n + 1
        ' LOAD MODULE LEVEL STRING ARRAY sW()
        sW(1, m) = m                  ' project line numbers
        sW(2, m) = sLS                ' trimmed line strings
        sW(3, m) = "other"            ' code line types
        sW(4, m) = 0                  ' structure end numbers
        sW(5, m) = 0                  ' record tab count
        sW(6, m) = ""                 ' indented strings
        sW(7, m) = ""                 ' continuations marking
        sW(8, m) = ""                 ' continuation group strings
        sW(9, m) = sLN                ' user code line numbers
        sW(10, m) = ""                ' optional output renumbering
        sW(11, m) = ""                ' marked if "Proc" or "End Proc"
        sW(12, m) = ""                ' marked if "Label"
    Next n
    
End Sub

Sub JoinBrokenLines()
    ' Identifies lines with continuation marks
    ' Joins these broken lines into one line
    ' Marks newly redundant lines as "other"
    
    Dim vA As Variant, IsContinuation As Boolean
    Dim str As String, saccum As String
    Dim n As Long, s As Long, nS As Long, nE As Long
    
    ' mark all lines that have a continuation chara
    For n = LBound(sW(), 2) To UBound(sW(), 2)
        str = sW(2, n) ' line string
        IsContinuation = str Like "* _"
        If IsContinuation Then sW(7, n) = "continuation"
    Next n
    ' mark the start and end of every continuation group
    For n = LBound(sW(), 2) To (UBound(sW(), 2) - 1)
        If n = 1 Then ' for the first line only
            If sW(7, n) = "continuation" Then sW(8, n) = "SC"
            If sW(7, n) = "continuation" And sW(7, n + 1) <> "continuation" _
            Then sW(8, n + 1) = "EC"
        Else          ' for all lines after the first
            ' find ends
            If sW(7, n) = "continuation" And sW(7, n + 1) <> "continuation" Then
                sW(8, n + 1) = "EC"
            End If
            ' find starts
            If sW(7, n) = "continuation" And sW(7, n - 1) <> "continuation" Then
                ' If sW(7, n) <> "continuation" And sW(7, n + 1) = "continuation" Then
                sW(8, n) = "SC"
            End If
        End If
    Next n
    
    ' Count continuation group starts and ends
    For n = LBound(sW(), 2) To UBound(sW(), 2)
        If sW(8, n) = "SC" Then nS = nS + 1
        If sW(8, n) = "EC" Then nE = nE + 1
    Next n
    If nS <> nE Then
        ' Error.  Means there is an incomplete continuation selection
        ' Advise, raise error and exit
        MsgBox "The selection made is not sufficiently complete." & vbCrLf & _
        "A line that is continued has parts missing." & vbCrLf & _
        "Please make a another selection."
        Err.Raise 12346
        Exit Sub
    End If
    
    ' make single strings from each continuation group
    For n = LBound(sW(), 2) To (UBound(sW(), 2) - 1)
        If sW(8, n) = "SC" Then ' group starts
            ' join strings to make one string per continuation group
            s = n
            vA = Split(CStr(sW(2, n)), "_")
            str = CStr(vA(0))
            saccum = str
            Do Until sW(8, s) = "EC"
                s = s + 1
                sW(3, s) = "other" ' mark all but first line in group as "other"
                vA = Split(CStr(sW(2, s)), "_")
                str = CStr(vA(0))
                saccum = saccum & str
            Loop
            sW(8, n) = saccum ' place at first line level in array
        End If
        str = ""
        saccum = ""
        s = 0
    Next n
    
End Sub

Sub GetLineTypes()
    ' Marks array with the indentable closed structures
    
    Dim n As Long, m As Long, str As String
    Dim bProc As Boolean
    Dim Outdents, StructureStarts, StructureEnds, bEndProc As Boolean
    Dim IsComment As Boolean, IsBlank As Boolean
    Dim IsContinuation As Boolean, IsOK As Boolean
    
    ' THESE PATTERNS DECIDE HOW STRUCTURES ARE INDENTED - (revised Oct. 2016)
    ' ================================================================================
    ' STARTS LIST - starts of structures that contain lines to indent
    
    StructureStarts = Array( _
    "Do", "Do *", "Do: *", _
    "For *", _
    "If * Then", "If * Then: *", "If * Then [!A-Z,!a-z]*", _
    "Select Case *", _
    "Type *", "Private Type *", "Public Type *", _
    "While *", _
    "With *", _
    "Sub *", "Static Sub *", "Private Sub *", "Public Sub *", "Friend Sub *", _
    "Private Static Sub *", "Public Static Sub *", "Friend Static Sub *", _
    "Function *", "Static Function *", "Private Function *", _
    "Public Function *", "Friend Function *", "Private Static Function *", _
    "Public Static Function *", "Friend Static Function, *", _
    "Property Get *", "Static Property Get *", "Private Property Get *", _
    "Public Property Get *", "Friend Property Get *", _
    "Private Static Property Get *", "Public Static Property Get *", _
    "Friend Static Property Get *", _
    "Property Let *", "Static Property Let *", "Private Property Let *", _
    "Public Property Let *", "Friend Property Let *", _
    "Private Static Property Let *", "Public Static Property Let *", _
    "Friend Static Property Let *", _
    "Property Set *", "Static Property Set *", "Private Property Set *", _
    "Public Property Set *", "Friend Property Set *", _
    "Private Static Property Set *", "Public Static Property Set *", _
    "Friend Static Property Set *")
    
    ' ENDS LIST - ends of structures that contain lines to indent
    StructureEnds = Array( _
    "Loop", "Loop *", "Loop: *", _
    "Next", "Next *", "Next: *", _
    "End If", "End If *", "End If: *", _
    "End Select", "End Select *", "End Select: *", _
    "End Type", "End Type *", "End Type: *", _
    "Wend", "Wend *", "Wend: *", _
    "End With", "End With *", "End With: *", _
    "End Sub", "End Sub *", _
    "End Function", "End Function *", _
    "End Property", "End Property *", "End Property: *")
    
    ' OUTDENTS LIST - exceptions that need re-aligned with respective start elements
    Outdents = Array( _
    "Else", "Else *", "Else: *", "Else:", _
    "ElseIf * Then", "ElseIf * Then*", _
    "Case", "Case *", _
    "Case Else", "Case Else:", "Case Else *", "Case Else:*")
    ' ================================================================================
    
    ' mark array with line types - step through each line
    For n = LBound(sW(), 2) To UBound(sW(), 2)
        str = sW(2, n)
        
        ' mark each line if a blank
        ' mark each line if a blank
        If Len(str) = 0 Then ' note blanks
            sW(3, n) = "blank"
            IsBlank = True
            GoTo RoundAgain:            ' comment
        End If
        
        ' mark each line if an own-line comment or first of folded comment parts
        IsComment = str Like Chr(39) & " *" Or str Like "'  *" ' note comment lines
        If IsComment Then
            sW(3, n) = "comment"
            GoTo RoundAgain
        End If
        
        ' mark each line if a start, end, or middle
        ' and also if a proc start or proc end
        bProc = str Like "*Sub *" Or str Like "*Function *" Or str Like "*Property *"
        bEndProc = str Like "End Sub*" Or str Like "End Function*" Or str Like "End Property*"
        
        ' mark each line if a start element
        For m = LBound(StructureStarts) To UBound(StructureStarts)
            If sW(7, n) = "continuation" And sW(8, n) <> "" Then
                IsOK = sW(8, n) Like StructureStarts(m)
            Else
                IsOK = str Like StructureStarts(m)
            End If
            
            If IsOK Then
                sW(3, n) = "start"
                If bProc Then sW(11, n) = "Proc"
                Exit For
            End If
        Next m
        If IsOK Then GoTo RoundAgain
        
        ' mark each line if an end element
        For m = LBound(StructureEnds) To UBound(StructureEnds)
            If sW(7, n) = "continuation" And sW(8, n) <> "" Then
                IsOK = sW(8, n) Like StructureEnds(m)
            Else
                IsOK = str Like StructureEnds(m)
            End If
            
            If IsOK Then
                sW(3, n) = "end"
                If bEndProc Then sW(11, n) = "End Proc"
                Exit For
            End If
        Next m
        If IsOK Then GoTo RoundAgain
        
        ' mark each line if a middle element
        For m = LBound(Outdents) To UBound(Outdents)
            If sW(7, n) = "continuation" And sW(8, n) <> "" Then
                IsOK = sW(8, n) Like Outdents(m)
            Else
                IsOK = str Like Outdents(m)
            End If
            
            If IsOK Then
                sW(3, n) = "middle"
                Exit For
            End If
        Next m
        If IsOK Then GoTo RoundAgain
        
RoundAgain:
        ' reset loop variables
        IsBlank = False
        IsComment = False
        IsContinuation = False
        IsOK = False
        bProc = False
        bEndProc = False
    Next n
    
End Sub

Sub MatchPairs()
    ' matches up the structure starts with their ends
    
    Dim n As Long, q As Long, LB As Long, UB As Long
    Dim CountStarts As Long, CountEnds As Long
    Dim IsPastEnd As Boolean, IsAPair As Boolean
    
    LB = LBound(sW(), 2): UB = UBound(sW(), 2)
    
    ' find start lines
    For n = LB To UB
        If sW(3, n) = "start" Then
            q = n    ' pass it to q for the loop
            Do
                If sW(3, q) = "start" Then
                    CountStarts = CountStarts + 1
                ElseIf sW(3, q) = "end" Then
                    CountEnds = CountEnds + 1
                End If
                ' exit condition is a pair found
                If CountStarts = CountEnds Then ' this is match-found point
                    IsAPair = True
                    Exit Do
                Else:
                    IsAPair = False
                End If
                ' increment counter while accumulating
                q = q + 1
                ' avoid access beyond upper limit of array
                If q > UB Then
                    IsPastEnd = True
                    Exit Do
                End If
            Loop
            ' evaluate the loop exit causes
            If IsAPair And IsPastEnd Then
                ' suggests that there is an unpaired structure
                MsgBox "Unpaired structure for some element: " & n
            ElseIf IsAPair And Not IsPastEnd Then
                ' found a matching structure closer for line at n
                sW(4, n) = q
            End If
        End If
        ' reset loop variables
        CountStarts = 0
        CountEnds = 0
        IsAPair = False
        IsPastEnd = False
        
    Next n
    
End Sub

Sub CheckPairs()
    ' counts structure starts and ends
    ' advises if code trivial or unpaired
    
    Dim n As Long, CountStarts As Long, CountEnds As Long
    Dim str As String, LB As Long, UB As Long, sM1 As String
    Dim sM2 As String, Reply As String
    
    LB = LBound(sW(), 2): UB = UBound(sW(), 2)
    sM2 = "Continue with indent?" & vbNewLine & _
    "Select YES to continue, or NO to exit"
    
    ' count start and end markings
    For n = 1 To UB
        str = sW(3, n)
        If str = "start" Then CountStarts = CountStarts + 1
        If str = "end" Then CountEnds = CountEnds + 1
    Next n
    
    ' check for unmatched pairs and trivial text
    If CountStarts > 0 And CountEnds > 0 Then
        ' maybe worth indenting
        If CountStarts <> CountEnds Then
            ' possible code layout error
            sM1 = "Mismatched structure pairing." & vbCrLf & _
            "This will produce some indent error."
            GoTo Notify
        Else    ' worth indenting and paired
            Exit Sub
        End If
    Else
        sM1 = "Only trivial text found" & vbCrLf & _
        "No structures were found to indent."
        GoTo Notify
    End If
    
Notify:
    Reply = MsgBox(sM1 & vbNewLine & sM2, vbYesNo + vbQuestion)
    Select Case Reply
    Case vbYes
        Exit Sub
    Case Else
        Err.Raise 12345 ' user error
        Exit Sub
    End Select
    
End Sub

Sub Indents()
    ' adds indents between starts and ends
    
    Dim n As Long, m As Long, sStr As String
    
    For n = 1 To UBound(sW(), 2)
        ' get the line string
        ' row 3 has start markings
        ' corresponding row 4 has structure end number
        sStr = sW(3, n)
        ' if string is a start element
        If sStr = "start" Then
            ' indent all between start and end
            For m = (n + 1) To sW(4, n) - 1
                ' indent one tab
                sW(5, m) = sW(5, m) + 1
            Next m
        End If
    Next n
    
End Sub

Sub Outdent()
    ' outdent keywords in middle of structures
    
    Dim n As Long, Ind As Long, UB As Long
    
    UB = UBound(sW(), 2)
    
    ' outdent loop
    For n = 1 To UB
        Ind = sW(5, n)
        ' if marked for outdent...
        If sW(3, n) = "middle" Then
            Ind = Ind - 1
            sW(5, n) = Ind
        End If
    Next n
    
End Sub

Sub SpacePlusStr(ByVal SpacesInTab As Integer, _
    Optional bKeepLineNums As Boolean = True)
    ' adds together line numbers, padding spaces, and
    ' line strings to make the indented line
    ' For bKeepLineNums true, line numbers kept as found,
    ' else false for their removal.
    
    Dim nSPT As Long, nASC As Long, nGSC As Long, nALNL As Long
    Dim p As Long, nMin As Long, nMax As Long, nTab As Long
    
    '===============================================================
    ' NOTES ABOUT SPACING FOR INDENTS AND LINE NUMBERS
    '===============================================================
    ' IN GENERAL;
    ' The general space count nGSC, the number of spaces
    ' to apply for the indent, is the prescribed number
    ' of tabs times the spaces-per-tab integer.
    
    ' BUT WITH LINE NUMBERS;
    ' For nMax < nSPT , then nASC = nGSC - nALNL
    ' For nMax >= nSPT, nASC = nGSC - nSPT + 1 + nMax - nALNL
    ' where,
    ' nMax is max line number length in the display set
    ' nSPT is the number of spaces per tab
    ' nASC is the number of actual spaces required as an indent
    ' nGSC is the general space count as described above
    ' nALNL is the number of digits in the current line number
    '================================================================
    
    ' get the min and max lengths of any line numbers
    LineNumMinMax nMax, nMin 'get min and max line numbers
    
    ' assign parameter
    nSPT = SpacesInTab
    
    ' Loop through main string array
    For p = 1 To UBound(sW(), 2)
        
        nALNL = Len(sW(9, p))
        
        ' work out the general indent to apply
        nTab = sW(5, p)
        nGSC = nSPT * nTab 'general spaces for indent
        
        ' work out actual indent, modified for line numbers
        Select Case nGSC
        Case Is > 0
            'for lines intended for indent at all
            Select Case nMax
            Case 0
                nASC = nGSC
            Case Is < nSPT
                nASC = nGSC - nALNL
            Case Is >= nSPT
                nASC = nGSC - nALNL + nMax - nSPT + 1
            End Select
            'for lines not intended for indent
        Case Is <= 0
            nASC = 0
        End Select
        
        If bKeepLineNums = True Then
            ' combine line number, padding, and line string
            sW(6, p) = sW(9, p) & Space(nASC) & sW(2, p)
        Else
            'combine padding and line string
            sW(6, p) = Space(nGSC) & sW(2, p)
        End If
    Next p
    
End Sub


Function LineNumMinMax(max As Long, min As Long) As Boolean
    'gets the minimum value of user line numbers from array
    
    Dim n As Long
    
    For n = LBound(sW, 2) To UBound(sW, 2)
        If Len(sW(9, n)) >= max Then
            max = Len(sW(9, n))
        End If
        If Len(sW(9, n)) <= min Then
            min = Len(sW(9, n))
        End If
    Next n
    
    LineNumMinMax = True
    
End Function

Sub MaxBlanks(sRet As String, Optional nMaxNumBlankLines As Long = 555)
    ' makes a single string from all code lines, indented, ready for display.
    ' and makes a single string from the original code lines as found.
    ' nMaxNumBlankLines; restricts number of contiguous blank lines.
    ' Values other than 0 or 1 leave blanks as found. (Default).
    
    Dim Str2 As String, n As Long, bOK As Boolean
    
    ' accumulate original lines as one string - not used here
    '    For p = 1 To UBound(sW(), 2)
    '        Str1 = Str1 & sW(2, p) & vbNewLine
    '    Next p
    
    ' accumulate indented lines as one string
    For n = 1 To UBound(sW(), 2)
        If n = 1 And TrimStr(CStr(sW(2, n))) = "" Then
            ' do not accum the line
            Exit For
        End If
        ' if any line string after the first is blank
        If TrimStr(CStr(sW(2, n))) = "" Then
            Select Case nMaxNumBlankLines
            Case 0
                ' do not accumulate the line
                bOK = False
            Case 1
                ' accum if only one
                If TrimStr(CStr(sW(2, n - 1))) = "" Then
                    bOK = False
                Else
                    bOK = True
                End If
            Case Else
                ' accumulate anyway
                bOK = True
            End Select
        Else
            ' if not blank - accumulate
            bOK = True
        End If
        If bOK Then
            ' accumulate line strings
            Str2 = Str2 & sW(6, n) & vbNewLine ' to display indent amounts
        End If
        bOK = False
    Next n
    
    sRet = Left(Str2, Len(Str2) - 2)
    
End Sub

Function TrimStr(ByVal str As String) As String
    ' trims leading and lagging spaces and tabs from strings
    
    Dim n As Long
    
    n = Len(str)
    
    Do ' delete tabs and spaces from left of string
        If Left(str, 1) = Chr(32) Or Left(str, 1) = Chr(9) Then
            n = Len(str)
            str = Right(str, n - 1)
        Else
            ' left is done
            Exit Do
        End If
    Loop
    Do ' delete tabs and spaces from right of string
        If Right(str, 1) = Chr(32) Or Right(str, 1) = Chr(9) Then
            n = Len(str)
            str = Left(str, n - 1)
        Else
            ' left is done
            Exit Do
        End If
    Loop
    
    TrimStr = str
    
End Function

Function SplitLineNums(sIn As String, sLN As String, sLS As String) As Boolean
    ' takes sIn and returns line number and line string parts both trimmed
    ' returns an empty string for any missing part.
    ' assumes that previous line string is not continued - handle in call proc
    
    Dim sL As String, sS As String
    Dim n As Long, sA As String, nL As Long
    Dim nLS As Long, nLN As Long, bOK As Boolean
    
    sL = Trim(sIn)
    nL = Len(sL)
    
    ' if first chara numeric...
    If IsNumeric(Left$(sL, 1)) Then
        ' separate at change to alpha
        For n = 1 To nL
            sS = Mid$(sL, n, 1)
            ' if an integer or colon...
            If Asc(sS) >= 48 And Asc(sS) <= 58 Then
                ' accumulate...
                sA = sA & sS
            Else
                ' change point found
                bOK = True
                Exit For
            End If
        Next n
        ' but for numbered blank lines...
        If Len(sA) = nL Then bOK = True
    End If
    
    ' if a line number was found...
    If bOK Then
        sLN = Trim(sA)
        nLN = Len(sA)
        sLS = Trim(Right$(sL, nL - nLN))
        nLS = Len(sLS)
    Else
        ' if no line number was found...
        sLN = "": nLN = 0: sLS = sL: nLS = nL
    End If
    
    ' MsgBox sLN: MsgBox nLN: MsgBox sLS: MsgBox nLS
    
    SplitLineNums = True
    
End Function

Function SplitLineParts(sIn As String, sLN As String, _
    sLL As String, sS As String) As Boolean
    ' sIn; input is one whole vba code line string
    ' sLN; returns line number if used, with colon if used
    ' sLL; returns label string if used, always with its colon
    ' sSS; returns split string parts if any, lead space intact
    
    Dim nPos As Long
    
    ' check for line number and labels
    If IsLineNumbered(sIn) = True Then
        sS = StrLessLineNumber(sIn, sLN) ' line number
    Else
        If IsLabelled(sIn) = True Then
            nPos = InStr(1, sIn, ":", vbTextCompare)
            sS = Right$(sIn, Len(sIn) - nPos) ' string part
            sLL = Left$(sIn, nPos)       ' line label
        Else
            sS = sIn
        End If
    End If
    
    SplitLineParts = True
    
End Function

Function IsLineNumbered(ByVal str As String) As Boolean
    ' assumes test done to exclude continuation from previous line
    ' Returns true if str starts with a vba line number format
    
    ' Line number range is 0 to 2147483647 with or without end colon
    If str Like "#*" Then
        IsLineNumbered = True
    End If
    
End Function

Function StrLessLineNumber(ByVal str As String, sLineNumber As String) As String
    ' assumes that possibility of number being a continuation is excluded.
    ' Returns with string separated from line number
    ' Includes any leading space
    ' Returns whole string if not
    
    Dim nPos As Long, sT As String
    
    ' default transfer
    StrLessLineNumber = str
    
    ' line numbers range is 0 to 2147483647
    ' if the line is some kind of line number line at all...
    If str Like "#*" Then
        
        ' specifically, if the line uses a colon separator...
        If str Like "#*: *" Then
            If InStr(str, ":") Then
                ' get colon position
                nPos = InStr(1, str, ":", vbTextCompare)
                GoTo Splits
            End If
        End If
        
        ' specifically, if the line uses a space separator
        If str Like "#* *" Then
            If InStr(str, " ") Then
                nPos = InStr(1, str, " ", vbTextCompare) - 1
                GoTo Splits
            End If
            ' default, if there is only a line number with nothing after...
        Else
            ' to return a line number but empty split string...
            nPos = Len(str)
            GoTo Splits
        End If
Splits:
        ' return string after separator
        StrLessLineNumber = Mid(str, 1 + nPos)
        sT = StrLessLineNumber
        sLineNumber = Left$(str, Len(str) - Len(sT))
        
    End If
    
End Function

Function IsLabelled(ByVal str As String) As Boolean
    ' assumes that possibility of being any kind of
    ' comment or a line number are first excluded
    ' Returns true if str starts with a vba label format
    
    Dim nPosColon As Long, nPosSpace As Long
    Dim sRC As String
    
    ' define r e m + colon
    sRC = Chr(82) & Chr(101) & Chr(109) & Chr(58)
    
    ' test for single colon exception and r e m colon exception
    If str Like ":*" Or str Like sRC & "*" Then Exit Function
    
    ' test position of first colon
    nPosColon = InStr(1, str & ":", ":")
    
    ' test position of first space
    nPosSpace = InStr(1, str & " ", " ")
    
    IsLabelled = nPosColon < nPosSpace
    
End Function

' INDENT NOTES

' =====================================================================================================
' *String Array sW() row details:
' *
' * Row 1: Integers:  Clipboard code line numbers.
' * Row 2: Strings:   Trimmed line strings.
' * Row 3: Strings:   Line type markings; one of blank, comment, start, end, or middle.
' * Row 4: Integers:  Line numbers for structure ends that match start markings.
' * Row 5: Integers:  Records sum of number of indents that are due for that line.
' * Row 6: Strings:   Line strings with their due indents added
' * Row 7: Strings:   Line type markings; continuation
' * Row 8: Strings:   Joined up continuation strings as single lines
' * Row 9: Strings:   User code line numbers
' * Row 10: Strings:  Renumbered line numbers
' * Row 11: Strings:  Proc or End Proc markings for format exceptions
' * row 12: Strings:  Marked "Label" for line label
' =====================================================================================================
' * Row 3 Markings:
' *
' * "other"           The most usual thing; the kind found within structures, eg a = 3*b
' * "start"           The start of a closed structure; eg, Sub, If, For, Select Case, etc.
' * "end"             The end of a closed structure; eg, End Sub, End If, End select, etc.
' * "middle"          The keywords that display level with starts; eg, Else, Case, etc.
' * "comment"         The line is a comment line with its own apostrophe at its start
' * "blank"           The line is completely blank
' *====================================================================================================
' * Row 7 Continuation Marks:
' *
'  * Every line that ends with a continuation mark is identified as ' continuation'  as well as the start
' * and end of each continuation grouping.
' *====================================================================================================
' * Row 8 Joined line strings:
' *
' * The start and end of each continuation grouping is marked.   These are used to construct a full
' * length line from the several parts of each grouping. Only then is there line type identification.
'  * To see the problem, a folded comment line with ' For'  or ' Do'   at the start of the second line would
'  * otherwise be taken as a ' start'  line. So too with some other line folds.
' * Joining allows better line typing
' *=====================================================================================================
[编辑 | 编辑源代码]
华夏公益教科书