Prime Programming Proficiency, Part 3: Lines-of-code Counter, Page 5
Implementing the Line Counter
The LineCounter is a completely separate class. As a result, I can stub it out with an imperfect implementation and improve it at a later date or outsource it to someone who may specialize in the counting of source lines of code. Because the LineCounter (see Listing 8) does not know about the visitor and hosts, another developer will not need my entire implementation to do his or her job, implementing the LineCounter.
Listing 8: The LineCounter class sub-divides lines of a separate utility.
Imports EnvDTE
Imports System
Imports System.IO
Imports System.Diagnostics
Imports System.Text.RegularExpressions
Public Module LineCounter
Public Function GetLineCount(ByVal Item As ProjectItem)
If (Not IsValidItem(Item)) Then Return 0
Return CountLines(Item)
End Function
Private Function IsValidItem(ByVal item As ProjectItem) As Boolean
Return IsValidItem(item.Name)
End Function
Private Function IsValidItem(ByVal FileName As String) As Boolean
Return Regex.IsMatch(FileName, "^\w+.cs$") Or _
Regex.IsMatch(FileName, "^\w+.ascx.cs$") _
Or Regex.IsMatch(FileName, "^\w+.aspx.cs$") Or _
Regex.IsMatch(FileName, "^\w+.aspx$") _
Or Regex.IsMatch(FileName, "^\w+.ascx")
End Function
Private Function CountLines(ByVal item As ProjectItem) As Long
Try
Return DoCountLines(item)
Catch ex As Exception
Return DoManualCount(item.FileNames(1))
End Try
End Function
Private Function DoManualCount(ByVal FileName As String) As Long
Dim reader As TextReader = New StreamReader(FileName)
Dim all As String = reader.ReadToEnd()
Return Regex.Matches(all, vbCrLf).Count()
End Function
Private Function DoCountLines(ByVal item As ProjectItem) As Long
Debug.Assert(IsValidItem(item))
Dim Count As Long = 0
Open(item)
Try
Dim s As TextSelection = item.Document.Selection()
StoreOffset(s)
s.EndOfDocument()
Count = s.ActivePoint.Line()
RestoreOffset(s)
Finally
Close(item)
End Try
Return Count
End Function
Private WasOpen As Boolean = False
Private Current As Long = 0
Private Sub Open(ByVal item As ProjectItem)
WasOpen = item.IsOpen
If (Not WasOpen) Then item.Open()
End Sub
Private Sub Close(ByVal item As ProjectItem)
If (Not WasOpen) Then
item.Document.Close()
End If
End Sub
Private Sub StoreOffset(ByVal selection As TextSelection)
Current = selection.ActivePoint.Line
End Sub
Private Sub RestoreOffset(ByVal selection As TextSelection)
If (WasOpen) Then
selection.MoveToLineAndOffset(Current, 0)
End If
End Sub
End Module
Two methods in this class try to count the number of lines: DoCountLines and DoManualCount. DoCountLines uses the ProjectItem passed to the constructor, opens the file represented by ProjectItem, stores the current position in the file, moves the cursor to the end of the document, and then asks what the active line is. Finally, it restores the offset and closes the ProjectItem. Both Open and Close take into account whether the ProjectItem was already opened or not and close only ProjectItems that the class opened.
If an exception occurs in DoCountLines, the caller, CountLines, catches the exception and calls DoManualCount. DoManualCount opens the ProjectItem and attempts to count carriage return and line feed pairs. DoManualCount is a lot slower then DoCountLines, but together they seem to form a resilient pair.
Note: Kevin McFarlane sent me an e-mail that mentions Oz Solomnovich's Project Line Counter add-in. You should be able to find the source here: http://wndtabs.com/plc/. I haven't looked at the source, but the GUI looks good.
