다음을 통해 공유


연습: 코드 조각 구현

코드 조각을 만들고 편집기 확장에 포함할 수 있으므로 확장의 사용자가 코드 조각을 자신의 코드에 추가할 수 있습니다.

코드 조각은 파일에 통합할 수 있는 코드 조각 또는 기타 텍스트입니다. 특정 프로그래밍 언어에 등록된 모든 코드 조각을 보려면 도구 메뉴에서 코드 조각 관리자를 클릭합니다. 파일에 코드 조각을 삽입하려면 코드 조각을 원하는 위치를 마우스 오른쪽 단추로 클릭하고, 코드 조각 삽입 또는 코드 감싸기를 클릭하고 원하는 코드 조각을 찾아서 두 번 클릭합니다. Tab 또는 Shift+Tab 키를 눌러 코드 조각의 관련 부분을 수정한 다음, Enter 키 또는 Esc 키를 눌러 적용합니다. 자세한 내용은 코드 조각을 참조하세요.

코드 조각은 .snippet* 파일 이름 확장명을 가진 XML 파일에 포함되어 있습니다. 코드 조각은 코드 조각이 삽입된 후 강조 표시된 필드를 포함할 수 있으므로 사용자가 해당 필드를 찾아서 변경할 수 있습니다. 코드 조각 파일은 코드 조각 관리자에 대한 정보도 제공하므로 코드 조각 이름을 올바른 범주에 표시할 수 있습니다. 코드 조각 스키마에 대한 자세한 내용은 코드 조각 스키마 참조를 참조하세요.

이 연습에서는 다음 작업을 수행하는 방법을 설명합니다.

  1. 특정 언어에 대한 코드 조각을 만들고 등록합니다.

  2. 바로 가기 메뉴에 코드 조각 삽입 명령을 추가합니다.

  3. 코드 조각 확장을 구현합니다.

    이 연습은 연습: 문 완성 표시를 기반으로 합니다.

코드 조각 만들기 및 등록

일반적으로 코드 조각은 등록된 언어 서비스와 연결됩니다. 그러나 코드 조각을 등록하기 위해 LanguageService를 구현할 필요는 없습니다. 대신에 코드 조각 인덱스 파일에 GUID를 지정한 다음, 프로젝트에 추가하는 것과 동일한 GUID를 ProvideLanguageCodeExpansionAttribute에 사용합니다.

다음 단계에서는 코드 조각을 만들고 특정 GUID와 연결하는 방법을 보여 줍니다.

  1. 다음과 같은 디렉터리 구조를 만듭니다.

    %InstallDir%\TestSnippets\Snippets\1033\

    여기서, %InstallDir%는 Visual Studio 설치 폴더입니다. (이 경로는 일반적으로 코드 조각을 설치하는 데 사용되지만 모든 경로를 지정할 수 있습니다.)

  2. \1033\ 폴더에서 .xml 파일을 만들고 이름을 TestSnippets.xml로 지정합니다. (이 이름은 일반적으로 코드 조각 인덱스 파일에 사용되지만 파일 이름 확장명이 .xml인 경우에는 어떤 이름이든 지정할 수 있습니다.) 다음 텍스트를 추가한 다음 자리 표시자 GUID를 삭제하고 직접 추가합니다.

    <?xml version="1.0" encoding="utf-8" ?>
    <SnippetCollection>
        <Language Lang="TestSnippets" Guid="{00000000-0000-0000-0000-000000000000}">
            <SnippetDir>
                <OnOff>On</OnOff>
                <Installed>true</Installed>
                <Locale>1033</Locale>
                <DirPath>%InstallRoot%\TestSnippets\Snippets\%LCID%\</DirPath>
                <LocalizedName>Snippets</LocalizedName>
            </SnippetDir>
        </Language>
    </SnippetCollection>
    
  3. 코드 조각 폴더에 파일을 만들고 이름을 테스트.snippet로 지정한 후 다음 텍스트를 추가합니다.

    <?xml version="1.0" encoding="utf-8" ?>
    <CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
        <CodeSnippet Format="1.0.0">
            <Header>
                <Title>Test replacement fields</Title>
                <Shortcut>test</Shortcut>
                <Description>Code snippet for testing replacement fields</Description>
                <Author>MSIT</Author>
                <SnippetTypes>
                    <SnippetType>Expansion</SnippetType>
                </SnippetTypes>
            </Header>
            <Snippet>
                <Declarations>
                    <Literal>
                      <ID>param1</ID>
                        <ToolTip>First field</ToolTip>
                        <Default>first</Default>
                    </Literal>
                    <Literal>
                        <ID>param2</ID>
                        <ToolTip>Second field</ToolTip>
                        <Default>second</Default>
                    </Literal>
                </Declarations>
                <References>
                   <Reference>
                       <Assembly>System.Windows.Forms.dll</Assembly>
                   </Reference>
                </References>
                <Code Language="TestSnippets">
                    <![CDATA[MessageBox.Show("$param1$");
         MessageBox.Show("$param2$");]]>
                </Code>
            </Snippet>
        </CodeSnippet>
    </CodeSnippets>
    

    다음 단계에서는 코드 조각을 등록하는 방법을 보여 줍니다.

특정 GUID에 대한 코드 조각을 등록하려면

  1. CompletionTest 프로젝트를 엽니다. 이 프로젝트를 만드는 방법에 대한 자세한 내용은 연습: 문 완성 표시를 참조하세요.

  2. 프로젝트에서 다음 어셈블리에 대한 참조를 추가합니다.

    • Microsoft.VisualStudio.TextManager.Interop

    • Microsoft.VisualStudio.TextManager.Interop.8.0

    • microsoft.msxml

  3. 프로젝트에서 source.extension.vsixmanifest 파일을 엽니다.

  4. 자산 탭에 VsPackage 콘텐츠 형식이 포함되어 있고 프로젝트가 프로젝트 이름을 설정되어 있어야 합니다.

  5. CompletionTest 프로젝트를 선택하고 속성 창에서 Pkgdef 파일 생성true로 설정합니다. 프로젝트를 저장합니다.

  6. 프로젝트에 정적 SnippetUtilities 클래스를 추가합니다.

    static class SnippetUtilities
    
  7. SnippetUtilities 클래스에서 GUID를 정의하고 SnippetsIndex.xml 파일에 사용한 값을 제공합니다.

    internal const string LanguageServiceGuidStr = "00000000-0000-0000-0000-00000000";
    
  8. ProvideLanguageCodeExpansionAttributeTestCompletionHandler 클래스에 추가합니다. 이 특성은 프로젝트의 공용 또는 내부(비정적) 클래스에 추가할 수 있습니다. (Microsoft.VisualStudio.Shell 네임스페이스에 대해 using 지시문을 추가해야 할 수 있습니다.)

    [ProvideLanguageCodeExpansion(
    SnippetUtilities.LanguageServiceGuidStr,
    "TestSnippets", //the language name
    0,              //the resource id of the language
    "TestSnippets", //the language ID used in the .snippet files
    @"%InstallRoot%\TestSnippets\Snippets\%LCID%\TestSnippets.xml",
        //the path of the index file
    SearchPaths = @"%InstallRoot%\TestSnippets\Snippets\%LCID%\",
    ForceCreateDirs = @"%InstallRoot%\TestSnippets\Snippets\%LCID%\")]
    internal class TestCompletionCommandHandler : IOleCommandTarget
    
  9. 프로젝트를 빌드하고 실행합니다. 프로젝트가 실행될 때 시작되는 Visual Studio의 실험적 인스턴스에서 방금 등록한 코드 조각은 TestSnippets 언어로 코드 조각 관리자에 표시되어야 합니다.

바로 가기 메뉴에 코드 조각 삽입 명령 추가

텍스트 파일의 바로 가기 메뉴에는 코드 조각 삽입 명령이 포함되지 않습니다. 따라서 명령을 사용하도록 설정해야 합니다.

바로 가기 메뉴에 코드 조각 삽입 명령을 추가하려면

  1. TestCompletionCommandHandler 클래스 파일을 엽니다.

    이 클래스는 IOleCommandTarget을 구현하므로 QueryStatus 메소드에서 Insert Snippet 명령을 활성화할 수 있습니다. Insert Snippet 명령을 클릭하면 코드 조각 선택기 UI(사용자 인터페이스)가 표시되므로 명령을 사용하도록 설정하기 전에 이 메서드가 자동화 함수 내에서 호출되지 않는지 확인합니다.

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        if (!VsShellUtilities.IsInAutomationFunction(m_provider.ServiceProvider))
        {
            if (pguidCmdGroup == VSConstants.VSStd2K && cCmds > 0)
            {
                // make the Insert Snippet command appear on the context menu 
                if ((uint)prgCmds[0].cmdID == (uint)VSConstants.VSStd2KCmdID.INSERTSNIPPET)
                {
                    prgCmds[0].cmdf = (int)Constants.MSOCMDF_ENABLED | (int)Constants.MSOCMDF_SUPPORTED;
                    return VSConstants.S_OK;
                }
            }
        }
    
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  2. 프로젝트를 빌드하고 실행합니다. 실험적 인스턴스에서 .zzz 파일 이름 확장명을 가진 파일을 연 다음, 해당 파일의 아무 곳이나 마우스 오른쪽 단추로 클릭합니다. Insert Snippet 명령이 바로 가기 메뉴에 나타납니다.

코드 조각 선택기 UI에서 코드 조각 확장 구현

이 섹션에서는 바로 가기 메뉴에서 코드 조각 삽입을 클릭할 때 코드 조각 선택기 UI가 표시되도록 코드 조각 확장을 구현하는 방법을 보여 줍니다. 코드 조각은 사용자가 코드 조각 바로 가기를 입력한 다음, Tab 키를 누르면 확장됩니다.

코드 조각 선택기 UI를 표시하고 탐색 및 삽입 후 코드 조각 수용을 사용하도록 설정하려면 Exec 메서드를 사용합니다. 삽입 자체는 OnItemChosen 메서드에 의해 처리됩니다.

코드 조각 확장의 구현은 레거시 Microsoft.VisualStudio.TextManager.Interop 인터페이스를 사용합니다. 현재 편집기 클래스에서 레거시 코드로 변환하는 경우 때 레거시 인터페이스는 줄 번호와 열 번호의 조합을 사용하여 텍스트 버퍼의 위치를 지정하지만 현재 클래스는 하나의 인덱스를 사용한다는 점에 유의하세요. 따라서 버퍼에 각각 10개의 문자(1개의 문자로 계산되는 줄 바꿈)가 있는 3개의 줄이 있는 경우 세 번째 줄의 네 번째 문자는 현재 구현에서 위치 27에 있지만 이전 구현에서는 줄 2, 위치 3에 있습니다.

코드 조각 확장을 구현하려면

  1. TestCompletionCommandHandler 클래스가 포함된 파일에 다음 using 지시문을 추가합니다.

    using Microsoft.VisualStudio.Text.Operations;
    using MSXML;
    using System.ComponentModel.Composition;
    
  2. TestCompletionCommandHandler 클래스가 IVsExpansionClient 인터페이스를 구현하도록 합니다.

    internal class TestCompletionCommandHandler : IOleCommandTarget, IVsExpansionClient
    
  3. TestCompletionCommandHandlerProvider 클래스에서 ITextStructureNavigatorSelectorService를 가져옵니다.

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  4. 코드 확장 인터페이스 및 IVsTextView에 대한 일부 프라이빗 필드를 추가합니다.

    IVsTextView m_vsTextView;
    IVsExpansionManager m_exManager;
    IVsExpansionSession m_exSession;
    
  5. TestCompletionCommandHandler 클래스의 생성자에서 다음 필드를 설정합니다.

    internal TestCompletionCommandHandler(IVsTextView textViewAdapter, ITextView textView, TestCompletionHandlerProvider provider)
    {
        this.m_textView = textView;
        m_vsTextView = textViewAdapter;
        m_provider = provider;
        //get the text manager from the service provider
        IVsTextManager2 textManager = (IVsTextManager2)m_provider.ServiceProvider.GetService(typeof(SVsTextManager));
        textManager.GetExpansionManager(out m_exManager);
        m_exSession = null;
    
        //add the command to the command chain
        textViewAdapter.AddCommandFilter(this, out m_nextCommandHandler);
    }
    
  6. 사용자가 Insert Snippet 명령을 클릭할 때 코드 조각 선택기를 표시하려면 Exec 메서드에 다음 코드를 추가합니다. (이 설명을 더 읽기 쉽도록 하기 위해 문 완성에 사용되는 Exec()코드는 표시되지 않습니다. 대신 코드 블록이 기존 메서드에 추가됩니다.) 문자를 확인하는 코드 뒤에 다음 코드 블록을 추가합니다.

    //code previously written for Exec
    if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
    {
        typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
    }
    //the snippet picker code starts here
    if (nCmdID == (uint)VSConstants.VSStd2KCmdID.INSERTSNIPPET)
    {
        IVsTextManager2 textManager = (IVsTextManager2)m_provider.ServiceProvider.GetService(typeof(SVsTextManager));
    
        textManager.GetExpansionManager(out m_exManager);
    
        m_exManager.InvokeInsertionUI(
            m_vsTextView,
            this,      //the expansion client
            new Guid(SnippetUtilities.LanguageServiceGuidStr),
            null,       //use all snippet types
            0,          //number of types (0 for all)
            0,          //ignored if iCountTypes == 0
            null,       //use all snippet kinds
            0,          //use all snippet kinds
            0,          //ignored if iCountTypes == 0
            "TestSnippets", //the text to show in the prompt
            string.Empty);  //only the ENTER key causes insert 
    
        return VSConstants.S_OK;
    }
    
  7. 코드 조각에 탐색할 수 있는 필드가 있는 경우 확장이 명시적으로 수락될 때까지 확장 세션이 열린 상태로 유지됩니다. 코드 조각에 필드가 없으면 세션이 닫히고 InvokeInsertionUI 메서드에 의해 null로 반환됩니다. Exec 메서드에서 이전 단계에서 추가한 코드 조각 선택기 UI 코드 뒤에 다음 코드를 추가하여 코드 조각 탐색을 처리합니다(사용자가 코드 조각 삽입 후 Tab 키 또는 Shift+Tab을 누를 때).

    //the expansion insertion is handled in OnItemChosen
    //if the expansion session is still active, handle tab/backtab/return/cancel
    if (m_exSession != null)
    {
        if (nCmdID == (uint)VSConstants.VSStd2KCmdID.BACKTAB)
        {
            m_exSession.GoToPreviousExpansionField();
            return VSConstants.S_OK;
        }
        else if (nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB)
        {
    
            m_exSession.GoToNextExpansionField(0); //false to support cycling through all the fields
            return VSConstants.S_OK;
        }
        else if (nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN || nCmdID == (uint)VSConstants.VSStd2KCmdID.CANCEL)
        {
            if (m_exSession.EndCurrentExpansion(0) == VSConstants.S_OK)
            {
                m_exSession = null;
                return VSConstants.S_OK;
            }
        }
    }
    
  8. 사용자가 해당 바로 가기를 입력한 다음 Tab 키를 누를 때 코드 조각을 삽입하려면 Exec 메서드에 코드를 추가합니다. 코드 조각을 삽입하는 전용 메서드는 이후 단계에서 표시됩니다. 이전 단계에서 추가한 코드 뒤에 탐색 코드를 추가합니다.

    //neither an expansion session nor a completion session is open, but we got a tab, so check whether the last word typed is a snippet shortcut 
    if (m_session == null && m_exSession == null && nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB)
    {
        //get the word that was just added 
        CaretPosition pos = m_textView.Caret.Position;
        TextExtent word = m_provider.NavigatorService.GetTextStructureNavigator(m_textView.TextBuffer).GetExtentOfWord(pos.BufferPosition - 1); //use the position 1 space back
        string textString = word.Span.GetText(); //the word that was just added
        //if it is a code snippet, insert it, otherwise carry on
        if (InsertAnyExpansion(textString, null, null))
            return VSConstants.S_OK;
    }
    
  9. IVsExpansionClient 인터페이스의 메서드를 구현합니다. 이 구현에서 유일한 관심 메서드는 EndExpansionOnItemChosen입니다. 다른 메서드는 S_OK만 반환해야 합니다.

    public int EndExpansion()
    {
        m_exSession = null;
        return VSConstants.S_OK;
    }
    
    public int FormatSpan(IVsTextLines pBuffer, TextSpan[] ts)
    {
        return VSConstants.S_OK;
    }
    
    public int GetExpansionFunction(IXMLDOMNode xmlFunctionNode, string bstrFieldName, out IVsExpansionFunction pFunc)
    {
        pFunc = null;
        return VSConstants.S_OK;
    }
    
    public int IsValidKind(IVsTextLines pBuffer, TextSpan[] ts, string bstrKind, out int pfIsValidKind)
    {
        pfIsValidKind = 1;
        return VSConstants.S_OK;
    }
    
    public int IsValidType(IVsTextLines pBuffer, TextSpan[] ts, string[] rgTypes, int iCountTypes, out int pfIsValidType)
    {
        pfIsValidType = 1;
        return VSConstants.S_OK;
    }
    
    public int OnAfterInsertion(IVsExpansionSession pSession)
    {
        return VSConstants.S_OK;
    }
    
    public int OnBeforeInsertion(IVsExpansionSession pSession)
    {
        return VSConstants.S_OK;
    }
    
    public int PositionCaretForEditing(IVsTextLines pBuffer, TextSpan[] ts)
    {
        return VSConstants.S_OK;
    }
    
  10. OnItemChosen 메서드를 구현합니다. 실제로 확장을 삽입하는 도우미 메서드는 이후 단계에서 설명합니다. TextSpan에서는 IVsTextView에서 가져올 수 있는 행 및 열 정보를 제공합니다.

    public int OnItemChosen(string pszTitle, string pszPath)
    {
        InsertAnyExpansion(null, pszTitle, pszPath);
        return VSConstants.S_OK;
    }
    
  11. 다음 전용 메서드는 바로 가기 또는 제목 및 경로에 따라 코드 조각을 삽입합니다. 그런 다음, 코드 조각을 사용하여 InsertNamedExpansion 메서드를 호출합니다.

    private bool InsertAnyExpansion(string shortcut, string title, string path)
    {
        //first get the location of the caret, and set up a TextSpan
        int endColumn, startLine;
        //get the column number from  the IVsTextView, not the ITextView
        m_vsTextView.GetCaretPos(out startLine, out endColumn);
    
        TextSpan addSpan = new TextSpan();
        addSpan.iStartIndex = endColumn;
        addSpan.iEndIndex = endColumn;
        addSpan.iStartLine = startLine;
        addSpan.iEndLine = startLine;
    
        if (shortcut != null) //get the expansion from the shortcut
        {
            //reset the TextSpan to the width of the shortcut, 
            //because we're going to replace the shortcut with the expansion
            addSpan.iStartIndex = addSpan.iEndIndex - shortcut.Length;
    
            m_exManager.GetExpansionByShortcut(
                this,
                new Guid(SnippetUtilities.LanguageServiceGuidStr),
                shortcut,
                m_vsTextView,
                new TextSpan[] { addSpan },
                0,
                out path,
                out title);
    
        }
        if (title != null && path != null)
        {
            IVsTextLines textLines;
            m_vsTextView.GetBuffer(out textLines);
            IVsExpansion bufferExpansion = (IVsExpansion)textLines;
    
            if (bufferExpansion != null)
            {
                int hr = bufferExpansion.InsertNamedExpansion(
                    title,
                    path,
                    addSpan,
                    this,
                    new Guid(SnippetUtilities.LanguageServiceGuidStr),
                    0,
                   out m_exSession);
                if (VSConstants.S_OK == hr)
                {
                    return true;
                }
            }
        }
        return false;
    }
    

코드 조각 확장 빌드 및 테스트

코드 조각 확장이 프로젝트에서 작동하는지 테스트할 수 있습니다.

  1. 솔루션을 빌드합니다. 디버거에서 이 프로젝트를 실행하면 Visual Studio의 두 번째 인스턴스가 시작됩니다.

  2. 텍스트 파일을 열고 일부 텍스트를 입력합니다.

  3. 텍스트의 아무 곳이나 마우스 오른쪽 단추로 클릭한 다음, 코드 조각 삽입을 클릭합니다.

  4. 코드 조각 선택기 UI는 테스트 대체 필드를 나타내는 팝업과 함께 표시되어야 합니다. 팝업을 두 번 클릭합니다.

    다음 코드 조각을 삽입해야 합니다.

    MessageBox.Show("first");
    MessageBox.Show("second");
    

    Enter 키 또는 Esc 키를 누르지 마세요.

  5. Tab 키 및 Shift+Tab을 눌러 "첫 번째"와 "두 번째" 간에 전환합니다.

  6. Enter 키 또는 Esc 키를 눌러 삽입을 허용합니다.

  7. 텍스트의 다른 부분에 "테스트"를 입력한 다음 Tab 키를 누릅니다. "테스트"는 코드 조각 바로 가기이므로 코드 조각을 다시 삽입해야 합니다.