【问题标题】:Is there some way to automatically indent VB.NET code in a text file有没有办法在文本文件中自动缩进 VB.NET 代码
【发布时间】:2014-03-09 00:03:02
【问题描述】:

我想正确缩进文本文件中包含的一些 VB.NET 代码。有没有办法做到这一点?

例如 从这个开始:

Public Shared Function CanReachPage(page As String) As Boolean
Try
Using client = New WebClient()
Using stream = client.OpenRead(page)
Return True
End Using
End Using
Catch
Return False
End Try
End Function

完成这个:

Public Shared Function CanReachPage(page As String) As Boolean
    Try
        Using client = New WebClient()
            Using stream = client.OpenRead(page)
                Return True
            End Using
        End Using
    Catch
        Return False
    End Try
End Function

到目前为止,我搜索的所有内容都将我带到了 IndentedTextWriter 类,但我发现的唯一示例是手动缩进这样的行:.NET Console TextWriter that Understands Indent/Unindent/IndentLevel

额外的功劳:如果可能的话,我还想添加正确的间距:

例如Dim i As String="Hello"+"GoodBye" -> Dim i As String = "Hello" + "GoodBye"

【问题讨论】:

  • 您发现的那个控制台示例不仅仅适用于控制台。它是一个 TextWriter,就像 StreamWriter 一样。所以就用同样的方法,通过构造函数传递一个 TextWriter。
  • @HansPassant - 是的,我收集到了,但它不知道缩进应该是什么?它依赖于我知道“如果行以Using 开头,那么我必须缩进一层”等等
  • 哦。是的,编写一个理解 vb.net 语法的软件是完全不同的球赛。过去很容易,但现在可以选择续行,这应该会让你严重偏头痛。
  • 您是从VB项目中复制文本还是直接在记事本或.txt中书写?
  • @KashishArora - 我让我们产品的客户能够编写 vb.net “脚本”,他们将使用记事本对其进行编辑。我希望能够为他们缩进这个文件。

标签: .net vb.net indentation code-formatting codedom


【解决方案1】:

如果您使用的是 Visual Studio(我目前正在查看 VS 2010;我不知道早期版本做了什么),那么您可以转到 Edit->Advanced->Format Document,它应该为您处理缩进和间距。

请注意,这适用于 Visual Studio 可以理解的任何类型的文档。我经常使用这个技巧将 XML 文档格式化为清晰易读的内容。

【讨论】:

  • 谢谢,但我想在没有安装 VS 的机器上执行此操作
  • 在不使用 IDE 的情况下格式化 VB 代码是一个相当大的限制。正如上面提到的@HansPassant,编写自己的 VB 解析器将是一项艰巨的任务。你并没有真正提到你为什么要这样做,但至少值得考虑在有问题的计算机上安装 free,Visual Studio 的入门级版本是否值得,如果只是用于格式化你的文件。
【解决方案2】:

如果您可以使用预发布软件,您可以使用Roslyn:

Dim parsed = Syntax.ParseCompilationUnit(text)
Dim normalized = parsed.NormalizeWhitespace()
Console.WriteLine(normalized)

【讨论】:

    【解决方案3】:

    我决定自己翻滚。有一些边缘情况不能 100% 有效,但它非常可靠:

    Public Class VBIndenter
    
        Private _classIndents As New List(Of Integer)
        Private _moduleIndents As New List(Of Integer)
        Private _subIndents As New List(Of Integer)
        Private _functionIndents As New List(Of Integer)
        Private _propertyIndents As New List(Of Integer)
        Private _structureIndents As New List(Of Integer)
        Private _enumIndents As New List(Of Integer)
        Private _usingIndents As New List(Of Integer)
        Private _withIndents As New List(Of Integer)
        Private _ifIndents As New List(Of Integer)
        Private _tryIndents As New List(Of Integer)
        Private _getIndents As New List(Of Integer)
        Private _setIndents As New List(Of Integer)
        Private _forIndents As New List(Of Integer)
        Private _selectIndents As New List(Of Integer)
        Private _doIndents As New List(Of Integer)
        Private _whileIndents As New List(Of Integer)
    
        Public Property IndentWidth As Integer = 4
        Public Property IndentChar As Char = " "c
    
        Public Sub Indent(txt As TextBox)
    
            Dim lastLabelIndent As Integer = 0
            Dim lastRegionIndent As Integer = 0
            Dim currentIndent As Integer = 0
            Dim inProperty As Boolean = False
            Dim lineText As String
            Dim newLineIndent As Integer
            Dim lines As String() = txt.Lines
    
            For i As Integer = 0 To lines.Count - 1
    
                Dim line = lines(i)
    
                'get the trimmed line without any comments
                lineText = StripComments(line)
    
                'only change the indent on lines that are code
                If lineText.Length > 0 Then
    
                    'special case for regions and labels - they always have zero indent
                    If lineText.StartsWith("#") Then
                        lastRegionIndent = currentIndent
                        currentIndent = 0
                    ElseIf lineText.EndsWith(":") Then
                        lastLabelIndent = currentIndent
                        currentIndent = 0
                    End If
    
                    'if we are in a property and we see something 
                    If (_propertyIndents.Count > 0) Then
                        If Not lineText.StartsWith("End") Then
                            If lineText.StartsWith("Class ") OrElse lineText.Contains(" Class ") Then
                                _propertyIndents.RemoveAt(_propertyIndents.Count - 1)
                                currentIndent -= 1
                            ElseIf lineText.StartsWith("Module ") OrElse lineText.Contains(" Module ") Then
                                _propertyIndents.RemoveAt(_propertyIndents.Count - 1)
                                currentIndent -= 1
                            ElseIf lineText.StartsWith("Sub ") OrElse lineText.Contains(" Sub ") Then
                                _propertyIndents.RemoveAt(_propertyIndents.Count - 1)
                                currentIndent -= 1
                            ElseIf lineText.StartsWith("Function ") OrElse lineText.Contains(" Function ") Then
                                _propertyIndents.RemoveAt(_propertyIndents.Count - 1)
                                currentIndent -= 1
                            ElseIf lineText.StartsWith("Property ") OrElse lineText.Contains(" Property ") Then
                                _propertyIndents.RemoveAt(_propertyIndents.Count - 1)
                                currentIndent -= 1
                            ElseIf lineText.StartsWith("Structure ") OrElse lineText.Contains(" Structure ") Then
                                _propertyIndents.RemoveAt(_propertyIndents.Count - 1)
                                currentIndent -= 1
                            ElseIf lineText.StartsWith("Enum ") OrElse lineText.Contains(" Enum ") Then
                                _propertyIndents.RemoveAt(_propertyIndents.Count - 1)
                                currentIndent -= 1
                            End If
                        Else
                            If lineText = "End Class" Then
                                _propertyIndents.RemoveAt(_propertyIndents.Count - 1)
                            End If
                        End If
                    End If
    
                    If lineText = "End Class" Then
                        currentIndent = _classIndents.Item(_classIndents.Count - 1)
                        _classIndents.RemoveAt(_classIndents.Count - 1)
                    ElseIf lineText = "End Module" Then
                        currentIndent = _moduleIndents.Item(_moduleIndents.Count - 1)
                        _moduleIndents.RemoveAt(_moduleIndents.Count - 1)
                    ElseIf lineText = "End Sub" Then
                        currentIndent = _subIndents.Item(_subIndents.Count - 1)
                        _subIndents.RemoveAt(_subIndents.Count - 1)
                    ElseIf lineText = "End Function" Then
                        currentIndent = _functionIndents.Item(_functionIndents.Count - 1)
                        _functionIndents.RemoveAt(_functionIndents.Count - 1)
                    ElseIf lineText = "End Property" Then
                        currentIndent = _propertyIndents.Item(_propertyIndents.Count - 1)
                        _propertyIndents.RemoveAt(_propertyIndents.Count - 1)
                    ElseIf lineText = "End Try" Then
                        currentIndent = _tryIndents.Item(_tryIndents.Count - 1)
                        _tryIndents.RemoveAt(_tryIndents.Count - 1)
                    ElseIf lineText = "End With" Then
                        currentIndent = _withIndents.Item(_withIndents.Count - 1)
                        _withIndents.RemoveAt(_withIndents.Count - 1)
                    ElseIf lineText = "End Get" Then
                        currentIndent = _getIndents.Item(_getIndents.Count - 1)
                        _getIndents.RemoveAt(_getIndents.Count - 1)
                    ElseIf lineText = "End Set" Then
                        currentIndent = _setIndents.Item(_setIndents.Count - 1)
                        _setIndents.RemoveAt(_setIndents.Count - 1)
                    ElseIf lineText = "End If" Then
                        currentIndent = _ifIndents.Item(_ifIndents.Count - 1)
                        _ifIndents.RemoveAt(_ifIndents.Count - 1)
                    ElseIf lineText = "End Using" Then
                        currentIndent = _usingIndents.Item(_usingIndents.Count - 1)
                        _usingIndents.RemoveAt(_usingIndents.Count - 1)
                    ElseIf lineText = "End Structure" Then
                        currentIndent = _structureIndents.Item(_structureIndents.Count - 1)
                        _structureIndents.RemoveAt(_structureIndents.Count - 1)
                    ElseIf lineText = "End Select" Then
                        currentIndent = _selectIndents.Item(_selectIndents.Count - 1)
                        _selectIndents.RemoveAt(_selectIndents.Count - 1)
                    ElseIf lineText = "End Enum" Then
                        currentIndent = _enumIndents.Item(_enumIndents.Count - 1)
                        _enumIndents.RemoveAt(_enumIndents.Count - 1)
                    ElseIf lineText = "End While" OrElse lineText = "Wend" Then
                        currentIndent = _whileIndents.Item(_whileIndents.Count - 1)
                        _whileIndents.RemoveAt(_whileIndents.Count - 1)
                    ElseIf lineText = "Next" OrElse lineText.StartsWith("Next ") Then
                        currentIndent = _forIndents.Item(_forIndents.Count - 1)
                        _forIndents.RemoveAt(_forIndents.Count - 1)
                    ElseIf lineText = "Loop" OrElse lineText.StartsWith("Loop ") Then
                        currentIndent = _doIndents.Item(_doIndents.Count - 1)
                        _doIndents.RemoveAt(_doIndents.Count - 1)
                    ElseIf lineText.StartsWith("Else") Then
                        currentIndent = _ifIndents.Item(_ifIndents.Count - 1)
                    ElseIf lineText.StartsWith("Catch") Then
                        currentIndent = _tryIndents.Item(_tryIndents.Count - 1)
                    ElseIf lineText.StartsWith("Case") Then
                        currentIndent = _selectIndents.Item(_selectIndents.Count - 1) + 1
                    ElseIf lineText = "Finally" Then
                        currentIndent = _tryIndents.Item(_tryIndents.Count - 1)
                    End If
    
                End If
    
                'find the current indent
                newLineIndent = currentIndent * Me.IndentWidth
                'change the indent of the current line 
                line = New String(IndentChar, newLineIndent) & line.TrimStart
                lines(i) = line
    
                If lineText.Length > 0 Then
                    If lineText.StartsWith("#") Then
                        currentIndent = lastRegionIndent
                    ElseIf lineText.EndsWith(":") Then
                        currentIndent = lastLabelIndent
                    End If
    
                    If Not lineText.StartsWith("End") Then
                        If (lineText.StartsWith("Class ") OrElse lineText.Contains(" Class ")) Then
                            _classIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf (lineText.StartsWith("Module ") OrElse lineText.Contains(" Module ")) Then
                            _moduleIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf (lineText.StartsWith("Sub ") OrElse lineText.Contains(" Sub ")) Then
                            _subIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf (lineText.StartsWith("Function ") OrElse lineText.Contains(" Function ")) Then
                            _functionIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf (lineText.StartsWith("Property ") OrElse lineText.Contains(" Property ")) Then
                            _propertyIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf (lineText.StartsWith("Structure ") OrElse lineText.Contains(" Structure ")) Then
                            _structureIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf (lineText.StartsWith("Enum ") OrElse lineText.Contains(" Enum ")) Then
                            _enumIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText.Contains("Using ") Then
                            _usingIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText.StartsWith("Select Case") Then
                            _selectIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText = "Try" Then
                            _tryIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText = "Get" Then
                            _getIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText.StartsWith("Set") AndAlso Not lineText.Contains("=") Then
                            _setIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText.StartsWith("With") Then
                            _withIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText.StartsWith("If") AndAlso lineText.EndsWith("Then") Then
                            _ifIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText.StartsWith("For") Then
                            _forIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText.StartsWith("While") Then
                            _whileIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText.StartsWith("Do") Then
                            _doIndents.Add(currentIndent)
                            currentIndent += 1
                        ElseIf lineText.StartsWith("Case") Then
                            currentIndent += 1
                        ElseIf lineText.StartsWith("Else") Then
                            currentIndent = _ifIndents.Item(_ifIndents.Count - 1) + 1
                        ElseIf lineText.StartsWith("Catch") Then
                            currentIndent = _tryIndents.Item(_tryIndents.Count - 1) + 1
                        ElseIf lineText = "Finally" Then
                            currentIndent = _tryIndents.Item(_tryIndents.Count - 1) + 1
                        End If
                    End If
                End If
            Next
            'update the textbox
            txt.Lines = lines
        End Sub
    
        Private Function StripComments(ByVal code As String) As String
            If code.IndexOf("'"c) >= 0 Then
                code = code.Substring(0, code.IndexOf("'"c))
            End If
            Return code.Trim
        End Function
    End Class
    

    用法:

    将一些代码放入 TextBox (TextBox1),然后像这样调用缩进器:

    Dim id As New VBIndenter
    id.Indent(TextBox1)
    

    【讨论】:

      【解决方案4】:

      一种方法是构建一个解析器和一个漂亮的打印机。 解析器读取源代码并构建一个 AST,捕获其本质 程序结构。漂亮的打印机拿走了树, 并根据结构重新生成输出;因此它很“容易” 获得结构化输出。作为关键提示,对于每个级别 语言结构(类、方法、块、循环、条件), 漂亮的打印机可以缩进漂亮的文本给 良好的缩进结构。

      解析和漂亮打印都是相当复杂的主题。 而不是在这里重复所有这些,你可以看到 我的SO answer on how to parse, with follow on discussion on how to build an AST。 Prettyprinting不是那么有名, 但this SO answer of mine 提供了相当完整的描述 如何做到这一点。

      那么你就有了确定 VB.net 实际语法的复杂性。这需要从参考文档中提取大量工作......而且它并不完全正确,因此您需要针对大量代码验证解析器以说服自己正确。不幸的是,这部分只是汗水。

      给定一个漂亮的打印机程序,OP 可以简单地将其作为一个进程启动以格式化文件。

      如果你做了所有这些,那么是的,你可以格式化 VB.net 文本。我们的(独立)VB.net 格式化程序(“DMSFormat ...”)执行上述操作以实现漂亮的打印。

      给定文件“vb_example.net”:

      Module Test
      Public Shared Function CanReachPage(page As String) As Boolean
      Try
      Using client = New WebClient()
      Using stream = client.OpenRead(page)
      Return True
      End Using
      End Using
      Catch
      Return False
      End Try
      End Function
      End Module
      

      以下内容:

      C:>DMSFormat VisualBasic~VBdotNet  C:\temp\vb_example.net
      

      产生:

      VisualBasic~VBdotNet Formatter/Obfuscator Version 1.2.1
      Copyright (C) 2010 Semantic Designs, Inc
      Powered by DMS (R) Software Reengineering Toolkit
      Parsing C:\temp\vb_example.net [encoding ISO-8859-1]
      
      
      Module Test
        Public Shared Function CanReachPage(page As String) As Boolean
          Try
            Using client = New WebClient()
              Using stream = client.OpenRead(page)
                Return True
              End Using
            End Using
          Catch
            Return False
          End Try
        End Function
      End Module
      

      这与 OP 在他的示例中想要的相同。

      您可以轻松地将格式化的程序内容定向到文件。

      你可以给工具一个项目文件,它会格式化你指定的文件 立即在项目文件中。

      格式化程序集成了一个完整的VB.net解析器, 和我们自己的漂亮印刷机械。它解析 准确的源文本(包括奇怪的字符 编码)。因为它使用可靠的解析器和漂亮的打印机,所以不会破坏代码。

      eval 版本适用于几百行代码的文件。这可能正是您所需要的。

      我会提供一个链接,但 SO 似乎不喜欢这样。你可以通过我的简历找到这个。

      【讨论】:

      • 太棒了,但你没有提供这个工具在哪里的链接,谁提到了“我们的”是谁。
      • 那是因为 SO 政策对象,当我发布指向“我们的”工具的链接时,我受到了极大的抨击。 “我们的”就是这个意思;检查我的生物。如果您认为这些限制是错误的,请在 Meta 中接受。
      • 没有这些信息,这个答案完全没用。我不是应该接受元数据的人。你应该。事实上,你会发现这样的答案are perfectly acceptable,因为它与问题直接相关。
      • 正是我喜欢 SO 的地方。我提供了一个生产,工作的答案。它被否决了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-08
      • 2010-11-24
      相关资源
      最近更新 更多