I would like to correctly indent some VB.NET code contained within a text file. Is there some way to do this?
e.g. Start with this:
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
finish up with this:
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
Everything I have searched for has so far led me to the IndentedTextWriter Class but the only examples I have found are to manually indent lines like this: .NET Console TextWriter that Understands Indent/Unindent/IndentLevel
Extra credit: I would also like to add the correct spacing as well if possible:
e.g Dim i As String="Hello"+"GoodBye"
-> Dim i As String = "Hello" + "GoodBye"
I decided to have a crack at rolling by own. There are some edge cases that this doesn't work 100% for, but it is pretty reliable:
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
Usage:
Put some code into a TextBox (TextBox1
), then call the indenter like this:
Dim id As New VBIndenter
id.Indent(TextBox1)