有没有办法在文本文件中自动缩进VB.NET代码

时间:2014-02-11 09:27:55

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

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

e.g。 从这开始:

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"

4 个答案:

答案 0 :(得分:2)

如果您正在使用Visual Studio(我现在正在关注VS 2010;我不知道早期版本的功能),那么您可以转到Edit-> Advanced-> Format Document and它应该照顾你的压痕和间距。

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

答案 1 :(得分:1)

如果您对使用预发布软件感到满意,可以使用Roslyn

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

答案 2 :(得分:0)

我决定自己动手。有一些边缘情况,这不是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)

答案 3 :(得分:-3)

实现此目的的一种方法是构建解析器和prettyprinter。 解析器读取源并构建捕获精华的AST 程序结构。漂亮的打印机取树, 并根据结构重新生成输出;因此它“很容易” 得到结构化的输出。作为关键提示,适用于每个级别 语言结构(类,方法,块,循环,条件), prettyprinter可以缩进漂亮的文本 良好的压痕结构。

解析和漂亮都是非常复杂的主题。 你可以看到,而不是重复所有这一切  我的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解析器,  和我们自己的漂亮印刷机器。它解析 精确的源文本(包括奇怪的字符 编码)。因为它使用了可靠的解析器和prettyprinter,所以它不会破坏代码。

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

我提供了一个链接但是SO似乎不喜欢这样。你可以通过我的生物找到这个。