'----------------------------------------------------------
' Copyright (c) 2011 William Garrison
' http://www.mobydisk.com
'
' Macro to line-up variable declarations into neat columns.
' Release 0: 2011/09/11
'----------------------------------------------------------
'
' This macro converts this:
'
' public int Foo { get; set; } // Foo
' public string Whatever { get; set; } // Whatever
'
' to this:
'
' public int    Foo      { get; set; } // Foo
' public string Whatever { get; set; } // Whatever


Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports EnvDTE90a
Imports EnvDTE100
Imports System.Diagnostics

Imports System.IO
Imports System.Collections.Generic

Public Module Tidying

    Sub TidyVariableDeclarations()
        Dim selection As TextSelection
        selection = DTE.ActiveDocument.Selection

        ' Concept:
        ' - Each line consists of words separated by spaces
        ' - Treat each word as a column
        ' - Line-up the columns
        ' - Exception: Comments - the entire comment is treated as one entire word
        ' - Exception: Attributes <-- TODO
        ' Question:
        ' - Use the code DOM?
        ' - Could parse out the comments
        ' - But not worth it.
        ' Approach:
        ' - Determine the indent level
        ' - Split text into lines
        ' - Strip comments
        ' - Split lines into columns
        ' - Add comments as a final column
        ' - Loop through columns to find the largest of each column
        ' - Output columns padded right according to the largest value

        ' Make sure entire lines are selected, not partial lines
        Dim startLine = selection.TopPoint.Line
        Dim endLine = selection.BottomPoint.Line
        selection.MoveToLineAndOffset(startLine, 1)
        selection.SelectLine()
        selection.LineDown(True, endLine - startLine)

        Dim sr As StringReader
        Dim lines As New List(Of String)

        sr = New StringReader(selection.Text)
        Do While sr.Peek() <> -1
            lines.Add(sr.ReadLine())
        Loop

        Dim initialIndent = ""

        Dim linesAndColumns As New List(Of List(Of String))
        Dim maxColumns As Integer

        ' Split each line into columns
        ' - Treat the comments and attributes as a single column
        For Each line As String In lines
            ' Figure out the indenting
            Dim indentChars = line.Length - line.TrimStart().Length
            If indentChars > initialIndent.Length Then
                initialIndent = line.Substring(0, indentChars)
            End If

            ' This is not a perfect way to find comments, but it is good enough for now
            Dim commentStart = line.IndexOf("//")
            Dim lineNoComment As String
            If commentStart >= 0 Then
                lineNoComment = line.Substring(0, commentStart)
            Else
                lineNoComment = line
            End If

            ' Split the line into columns
            Dim columns As List(Of String)
            columns = New List(Of String)(lineNoComment.Split(New Char() {" ", vbTab}, StringSplitOptions.RemoveEmptyEntries))

            ' Add the comments as a column too, if present
            If commentStart >= 0 Then
                columns.Add(line.Substring(commentStart))
            End If

            ' Finally add this to our list of lines and columns
            linesAndColumns.Add(columns)

            maxColumns = Math.Max(columns.Count, maxColumns)
        Next

        ' Size each column to fit the longest value
        Dim columnWidths(maxColumns - 1) As Integer
        For Each lineWithColumns As List(Of String) In linesAndColumns

            Dim colNum As Integer
            For colNum = 0 To lineWithColumns.Count - 1
                columnWidths(colNum) = Math.Max(columnWidths(colNum), lineWithColumns(colNum).Length)
            Next
        Next

        ' Format the final result
        Dim result = ""
        For Each lineWithColumn In linesAndColumns
            result = result + initialIndent

            Dim colNum As New Integer
            For Each column In lineWithColumn
                Dim colString = column.PadRight(columnWidths(colNum) + 1)
                result = result + colString
                colNum = colNum + 1
            Next

            result = result.TrimEnd() + System.Environment.NewLine
        Next

        ' Write it to the document
        Try
            DTE.UndoContext.Open("InsertSomeCode")
            selection.Insert(result)
        Finally
            DTE.UndoContext.Close()
        End Try

    End Sub

End Module
