제목 그대로 선택된 라인들에서 중복된 라인들은 삭제하고 나머지를 정렬하기 위한 매크로입니다.

Visual Assist에 선택된 라인 정렬 기능이 있기는 한데, 중복 라인 삭제는 없어서, 어쩔 수 없이 만들었습니다. 주 목적은 아무래도 C++/include 구문 정리가 되겠습니다.

Collection 객체에 정렬 함수가 따로 없다는 것이 좀 의외군요.

Function Strip(ByVal strLine As String)
    If Len(strLine) > 0 Then       
        nBegin = 1
        nEnd = Len(strLine)
        For i = 1 To Len(strLine)
            c = Mid(strLine, i, 1)
            If c <> " " And c <> Tab And c <> Lf And c <> Cr Then
                nBegin = i
                Exit For
            End If
        Next
        For i = 1 To Len(strLine)
            c = Mid(strLine, Len(strLine) - i + 1, 1)
            If c <> " " And c <> Tab And c <> Lf And c <> Cr Then
                nEnd = Len(strLine) - i + 1
                Exit For
            End If
        Next
        Return Mid(strLine, nBegin, nEnd - nBegin + 1)
    Else
        Return ""
    End If
End Function

Sub SortCollection(ByRef oCollection As Collection, Optional ByVal bSortAscending As Boolean = True)
    Dim lSort1 As Integer
    Dim lSort2 As Integer
    Dim vTempItem1 As Object
    Dim vTempItem2 As Object
    Dim bSwap As Boolean

    For lSort1 = 1 To oCollection.Count - 1
        For lSort2 = lSort1 + 1 To oCollection.Count
            If bSortAscending Then
                If oCollection(lSort1) > oCollection(lSort2) Then
                    bSwap = True
                Else
                    bSwap = False
                End If
            Else
                If oCollection(lSort1) < oCollection(lSort2) Then
                    bSwap = True
                Else
                    bSwap = False
                End If
            End If
            If bSwap Then
                vTempItem1 = oCollection(lSort1)
                vTempItem2 = oCollection(lSort2)
                oCollection.Add(vTempItem1, Nothing, lSort2)
                oCollection.Add(vTempItem2, Nothing, lSort1)
                oCollection.Remove(lSort1 + 1)
                oCollection.Remove(lSort2 + 1)
            End If
        Next
    Next
End Sub

Sub SortAndRemoveDuplicatedLine()
    Dim objLines As New Collection
    Dim objSel As TextSelection = ActiveDocument().Selection
    Dim objRanges As TextRanges = objSel.TextRanges
    Dim objStartPt As EditPoint = objRanges.Item(1).StartPoint.CreateEditPoint()
    Dim objStream As New StringBuilder

    For Each strLine In objSel.Text.Split(Lf)
        strLine = Strip(strLine)
        If objLines.Contains(strLine) = False Then
            objLines.Add(strLine, strLine)
        End If
    Next

    SortCollection(objLines)

    For Each strLine In objLines
        objStream.AppendLine(strLine)
    Next

    objSel.Text = ""
    objStartPt.Insert(objStream.ToString())
End Sub

이 포스트 내용에 대한 이후 업데이트는 http://serious-code.net/moin.cgi/VisualStudioMacro 페이지에서만 이루어질 예정입니다. :)
2009/11/04 16:33 2009/11/04 16:33
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/17

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/17

Visual Studio 2005 매크로 버그

개발 2009/09/03 09:10 김성민
갑자기 매크로가 실행되지 않습니다.

마치 실행되는 것처럼 모래시계 커서가 잠시 떴다가 금방 사라집니다. 하지만 실제로는 아무 것도 실행되지 않습니다.


KB928365 패치가 뭔가 문제가 되는 모양입니다. 패치를 따로 설치하지는 않아서 .NET 프레임웍 2.0 서비스팩을 날린 후, .NET 프레임웍 2.0을 새로 설치하니 잘 돌아갑니다. 구글 만세.
2009/09/03 09:10 2009/09/03 09:10
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/14

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/14

VSMacro/ 주석 정렬 매크로

개발 2009/09/03 09:08 김성민

Before
//////////////////////////////////////////////////////////////////////////////////////////

/// \brief 글자가
/// 처음으로
/// 나타나는 위치를
/// 반환한다.
/// \param caseSensitive 대소문자 구별을 하는가?
/// \return size_t 음 이것은 그냥 붙여보는 주석이란다. abcdefg hijklmn opqrstu vwxyz 음 이것은 그냥 붙여보는 주석이란다. abcdefg hijklmn opqrstu vwxyz
//////////////////////////////////////////////////////////////////////////////////////////

음 "그냥 붙여보는 주석이란다..." 부분이 원래 한 줄인데, 워드랩되어버리는구나. 어쨌든 원래는 매우 긴 라인이다.

After
//////////////////////////////////////////////////////////////////////////////////////////
/// \brief 글자가 처음으로 나타나는 위치를 반환한다.
/// \param text 검색 대상 문자열
/// \return size_t 음 이것은 그냥 붙여보는 주석이란다. abcdefg hijklmn opqrstu vwxyz 음
/// 이것은 그냥 붙여보는 주석이란다. abcdefg hijklmn opqrstu vwxyz
//////////////////////////////////////////////////////////////////////////////////////////

Function Strip(ByVal strLine As String)
  ' 문자열 좌우의 공백을 제거한다.
  If Len(strLine) > 0 Then
       nBegin = 1
       nEnd = Len(strLine)
       For i = 1 To Len(strLine)
           c = Mid(strLine, i, 1)
           If c <> " " And c <> Tab And c <> Lf And c <> Cr Then
               nBegin = i
               Exit For
           End If
       Next
       For i = 1 To Len(strLine)
           c = Mid(strLine, Len(strLine) - i + 1, 1)
           If c <> " " And c <> Tab And c <> Lf And c <> Cr Then
               nEnd = Len(strLine) - i + 1
               Exit For
           End If
       Next
       Return Mid(strLine, nBegin, nEnd - nBegin + 1)
  Else
       Return ""
  End If
End Function

Function GetAsciiLength(ByVal str As String)
  Return Encoding.Default.GetBytes(str).Length
End Function

Sub WrapDoxygenComment()
  Dim nMaxLength As Integer = 90 - 1 ' -1은 딱 붙어있는 거 찝찝해서
  Dim nLineLength As Integer = 0
  Dim objRegex As RegularExpressions.Regex
  Dim objMatch As RegularExpressions.Match
  Dim objStream As New StringBuilder
  Dim objLines As New Collection
  Dim objSel As TextSelection = ActiveDocument().Selection
  Dim objRanges As TextRanges = objSel.TextRanges
  Dim objStartPt As EditPoint = objRanges.Item(1).StartPoint.CreateEditPoint()

  For Each strLine In objSel.Text.Split(CrLf)
       strLine = Strip(strLine)
       If objRegex.IsMatch(strLine, "^////+$") Then
       Else
           objMatch = objRegex.Match(strLine, "^///")
           If objMatch.Success Then
               strLine = Strip(strLine.SubString(objMatch.Length))
           End If
       End If
       objLines.Add(strLine)
  Next

  For i = 1 To objLines.Count
       strLine = objLines.Item(i)

      If strLine.Length = 0 Then
           objStream.Append(CrLf)
           objStream.Append("/// ")
           nLineLength = 0
       ElseIf objRegex.IsMatch(strLine, "^////+$") Then
           objStream.Append(CrLf)
           objStream.Append(strLine)
           nLineLength = 0
       Else
           For Each match As RegularExpressions.Match In RegularExpressions.Regex.Matches(strLine, "\S+\s*")
               If nLineLength = 0 Or match.Value.StartsWith("\") Or nLineLength + GetAsciiLength(match.Value) > nMaxLength Then
                   objStream.Append(CrLf)
                   objStream.Append("/// ")
                   nLineLength = 4
               End If

               objStream.Append(Strip(match.Value) + " ")
               nLineLength += GetAsciiLength(Strip(match.Value)) + 1
           Next
       End If
  Next
  objStream.Append(CrLf)

  ' 현재 선택된 문자열을 주어진 문자열로 치환한다.
  objSel.Text = ""
  objStartPt.Insert(objStream.ToString().Substring(2))
End Sub

정렬하고자 하는 주석을 선택한 다음에 실행할 것. 지속적인 업데이트는 아래의 주소에서...

http://serious-code.net/moin.cgi/VisualStudioMacro#head-0d4b04cbb9f3b4171409fd28d95661d243c6c74b
2009/09/03 09:08 2009/09/03 09:08
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/12

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/12

Dim objSel As TextSelection
objSel = ActiveDocument().Selection
objSel.Text = ","

TextSelection 안에 콤마를 포함한 문자열을 대입하려고 하면, 에러가 난다. 내가 뭔가 잘못한 건 줄 알았는데, 인텔리센스 관련 버그였다. 아직까지 버그 픽스가 되지 않았으므로, 해결하려면 TextSelection 대신에 EditPoint 객체를 이용해야 한다.

Dim objSel As TextSelection
Dim objRanges As TextRanges
Dim objStartPt As EditPoint

objSel = ActiveDocument().Selection
objRanges = objSel.TextRanges
objStartPt = objRanges.Item(1).StartPoint.CreateEditPoint()

objSel.Text = ""
objStartPt.Insert = ","
2009/09/03 09:06 2009/09/03 09:06
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/10

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/10

코딩하다가 마주치는 일 중에 정말로 귀찮은 일 중의 하나가, 멤버 변수에 대한 Get/Set accessor를 만들어주는 일이다.

Visual Studo 환경에서 작업한다면, 매크로를 통해 이 작업을 어느 정도 자동화시킬 수 있다. 아래에 있는 MakeSimpleGetterSetter 매크로가 이런 작업을 자동화하기 위해 사용하는 매크로다. 멤버 변수들을 복사해서 붙인 다음, 복사한 멤버 변수들을 선택하고, 매크로를 실행하면 된다.

EffectId_t  m_Id;        ///< 이펙트 ID
std::string m_Name;      ///< 이펙트 이름 (디버그용)
bool        m_Visible;   ///< 이펙트가 플레이어에게 보이는가의 여부
bool        m_Removable; ///< 디스펠 스킬로 인해 강제로 제거될 수 있는가의 여부

EffectId_t GetId() const { return m_Id; }
void SetId(EffectId_t value) { m_Id = value; }

const std::string& GetName() const { return m_Name; }
void SetName(const std::string& value) { m_Name = value; }

bool IsVisible() const { return m_Visible; }
void SetVisible(bool value) { m_Visible = value; }

bool IsRemovable() const { return m_Removable; }
void SetRemovable(bool value) { m_Removable = value; }

아 그러고보니, Visual Studio 2005 기준이다. 낮은 버전에는 돌아가지 않을 수도... 아마 돌아가지 않으려나? 옛날부터 조금씩 수정하며 써온 건데... 모르겠군.

멤버 변수 초기화 매크로도 하나 만들어야 한다고 생각은 하고 있는데, 타입에 따라 대입할 값이 틀리다 보니 "생각"만 하고 있다.

Function ParseVariableDeclarations( _
  ByVal strText As String, ByRef colTypes As Collection, ByRef colNames As Collection, ByRef colComments As Collection)
  Dim nSpacePos As Integer
  Dim nSemicolonPos As Integer
  Dim nLineFeedPos As Integer
  Dim bSuccess As Boolean
  Dim strLine As String
  Dim strVariableType As String
  Dim strVariableName As String
  Dim strVariableComment As String

  ' 2개 이상의 스페이스를 하나로 만든다.
  Do
       nSpacePos = InStr(strText, "  ")
       If nSpacePos <> 0 Then
           strText = Replace(strText, "  ", " ")
       Else
           Exit Do
       End If
  Loop

  Do
       ' 더 이상 파싱할 게 없다면 빠져나간다
       If Len(strText) = 0 Then
           Exit Do
       End If

       ' 라인 피드를 찾는다.
       nLineFeedPos = InStr(strText, CStr(CrLf))
       If nLineFeedPos <> 0 Then
           ' 라인 피드가 있다면 현재 라인을 얻어낸다.
           strLine = Trim(Replace(Left(strText, nLineFeedPos - 1), vbTab, " "))
       Else
           ' 라인 피드가 더 이상 없다면...
           strLine = Trim(Replace(strText, vbTab, " "))
           strText = ""
       End If

       ' 라인 제일 처음 나오는 ';' 글자와 ';' 글자 앞에서 제일 처음 나타나는 ' ' 글자를 찾는다.
       bSuccess = False
       nSemicolonPos = InStr(strLine, ";")
       If nSemicolonPos <> 0 Then
           nSpacePos = InStrRev(strLine, " ", nSemicolonPos)
           If nSpacePos <> 0 Then
               bSuccess = True
           End If
       End If

       ' 두 글자 모두를 찾았다면, 변수 선언문이라고 할 수 있다.
       If bSuccess Then
           strVariableType = Trim(Mid(strLine, 1, nSpacePos))
           strVariableName = Trim(Mid(strLine, nSpacePos, nSemicolonPos - nSpacePos))
           strVariableComment = ""

           If nSemicolonPos <> 0 Then
               strVariableComment = Trim(Mid(strLine, nSemicolonPos + 1, Len(strLine) - nSemicolonPos))
           End If

           colTypes.Add(Trim(Replace(strVariableType, vbTab, " ")))
           colNames.Add(Trim(Replace(strVariableName, vbTab, " ")))
           colComments.Add(Trim(Replace(strVariableComment, vbTab, " ")))

           'MsgBox("[" + vtype + "][" + vname + "][" + vcomment + "]")
       End If

       strText = Trim(Right(strText, Len(strText) - nLineFeedPos - 1))
  Loop
End Function

Function GenerateAccessor(ByVal strVarType As String, ByVal strVarName As String, ByVal strMode As String)
  Dim strResult As String

  If strVarType <> "" And strVarName <> "" Then
       Dim objRegex As New System.Text.RegularExpressions.Regex("")
       Dim colBasicTypes As New System.Collections.Generic.Dictionary(Of String, String)
       Dim objStream As New System.Text.StringBuilder

       ' 대문자로만 이루어졌지만, 구조체로 취급하지 않을 타입들 생성
       colBasicTypes.Add("DWORD", "")
       colBasicTypes.Add("BOOL", "")
       colBasicTypes.Add("BYTE", "")
       colBasicTypes.Add("WORD", "")
       colBasicTypes.Add("FLOAT", "")
       colBasicTypes.Add("PFLOAT", "")
       colBasicTypes.Add("PBOOL", "")
       colBasicTypes.Add("LPBOOL", "")
       colBasicTypes.Add("PBYTE", "")
       colBasicTypes.Add("LPBYTE", "")
       colBasicTypes.Add("PINT", "")
       colBasicTypes.Add("LPINT", "")
       colBasicTypes.Add("PWORD", "")
       colBasicTypes.Add("LPWORD", "")
       colBasicTypes.Add("LPLONG", "")
       colBasicTypes.Add("PDWORD", "")
       colBasicTypes.Add("LPDWORD", "")
       colBasicTypes.Add("LPVOID", "")
       colBasicTypes.Add("LPCVOID", "")
       colBasicTypes.Add("INT", "")
       colBasicTypes.Add("UINT", "")
       colBasicTypes.Add("PUINT", "")
       colBasicTypes.Add("WPARAM", "")
       colBasicTypes.Add("LPARAM", "")
       colBasicTypes.Add("LRESULT", "")

       ' 필드 이름 생성
       Dim strFieldName As String
       strFieldName = strVarName
       If Left(strFieldName, 2) = "m_" Then
           strFieldName = Right(strFieldName, Len(strFieldName) - 2)
       End If
       If objRegex.IsMatch(strFieldName, "^[a-z]+[^a-z].+$") Then
           strFieldName = objRegex.Replace(strFieldName, "^([a-z]+)([^a-z].+)$", "$2")
       End If

       ' POD인지 아닌지 체크
       Dim bComplexType = False
       If objRegex.IsMatch(strVarType, "^c[A-Z][A-Za-z0-9]+[^\*\&]$") Then
           ' cDateTime, cTokenizer
           bComplexType = True
       ElseIf objRegex.IsMatch(strVarType, "^[A-Z0-9_]+[^\*\&]$") And Not colBasicTypes.ContainsKey(strVarType) Then
           ' D3DXVECTOR, OVERLAPPED
           bComplexType = True
       ElseIf objRegex.IsMatch(strVarType, "^.+<.+>$") Then
           ' std::vector<int>, cMyTemplate<bool>
           bComplexType = True
       End If

       ' 몇 가지 타입은 따로 처리
       If strVarType = "std::string" Then strVarType = "const std::string&"
       If strVarType = "tstring" Then strVarType = "const tstring&"

       ' Getter? Setter?
       If strMode = "Getter" Then
           If bComplexType Then
               strResult += strVarType + "& Get" + strFieldName + "() { return " + strVarName + "; }" + CStr(Lf)
               strResult += "const " + strVarType + "& Get" + strFieldName + "() const { return " + strVarName + "; }" + CStr(Lf)
           ElseIf strVarType = "bool" Or strVarType = "BOOL" Then
               strResult += strVarType + " Is" + strFieldName + "() const { return " + strVarName + "; }" + CStr(Lf)
           Else
               strResult += strVarType + " Get" + strFieldName + "() const { return " + strVarName + "; }" + CStr(Lf)
           End If
       Else
           If bComplexType Then
               strResult += "void Set" + strFieldName + "(const " + strVarType + "& value) { " + strVarName + " = value; }" + CStr(Lf) + CStr(Lf)
           ElseIf strVarType = "bool" Or strVarType = "BOOL" Then
               strResult += "void Set" + strFieldName + "(" + strVarType + " value) { " + strVarName + " = value; }" + CStr(Lf) + CStr(Lf)
           Else
               strResult += "void Set" + strFieldName + "(" + strVarType + " value) { " + strVarName + " = value; }" + CStr(Lf) + CStr(Lf)
           End If
       End If
  End If

  Return strResult
End Function

Sub MakeSimpleGetterSetter()
  Dim objSel As TextSelection
  Dim colTypes As New Collection
  Dim colNames As New Collection
  Dim colComments As New Collection
  Dim objStream As New System.Text.StringBuilder

  objSel = ActiveDocument().Selection
  ParseVariableDeclarations(Trim(objSel.Text), colTypes, colNames, colComments)

  For i = 1 To colNames.Count
       objStream.Append(GenerateAccessor(colTypes.Item(i), colNames.Item(i), "Getter"))
       objStream.Append(GenerateAccessor(colTypes.Item(i), colNames.Item(i), "Setter"))
  Next

  objSel.Text = objStream.ToString()
End Sub
2009/09/03 09:06 2009/09/03 09:06
받은 트랙백이 없고, 댓글 하나가 달렸습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/9

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/9

VSMacro/ TCHAR 매크로

개발 2009/09/03 09:05 김성민

C 문자열 주위를 _T() 매크로로 감싸주는 Visual Studio 매크로다.

"Some String" ---> _T("Some String")

매크로 IDE(ALT+F11)에서 붙여넣고, 단축키 등록해 준 다음 쓰면 된다. 문자열을 선택한 다음 실행해도 되고, 문자열 가운데 아무데서나 그냥 실행해도 된다.

Sub WrapStringWithTcharMacro()
  Dim objRanges As TextRanges
  Dim objStartPt As EditPoint
  Dim objEndPt As EditPoint

  objRanges = ActiveDocument().Selection.TextRanges
  objStartPt = objRanges.Item(1).StartPoint.CreateEditPoint()
  objEndPt = objRanges.Item(objRanges.Count).EndPoint.CreateEditPoint()

  ' 문자열의 처음을 찾는다.
  While objStartPt.GetText(-1) <> """" Or objStartPt.GetText(-2) = "\"""
       objStartPt.CharLeft(1)
  End While
  objStartPt.CharLeft(1)

  ' 문자열의 끝을 찾는다.
  While objEndPt.GetText(1) <> """" Or objEndPt.GetText(-1) = "\"
       objEndPt.CharRight(1)
  End While
  objEndPt.CharRight(1)

  ' 이미 _T 매크로로 감싸여 있는 문자열이 아니라면 감싸준다.
  If objStartPt.GetText(-3) <> "_T(" Then
       objStartPt.Insert("_T(")
       objEndPt.Insert(")")
  End If
End Sub
2009/09/03 09:05 2009/09/03 09:05
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/8

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/8