共用方式為


逐步解說︰實作程式碼片段

您可以建立程式碼片段並將其包含在編輯器擴充功能中,讓擴充功能的使用者可以將它們新增至自己的程式碼。

程式碼片段是可併入檔案的程式碼或其他文字的片段。 若要檢視已註冊給特定程式語言的所有程式碼片段,請在工具 功能表上,按一下程式碼片段管理員。 若要在檔案中插入程式碼片段,請以滑鼠右鍵按下想要插入片段的位置,按一下 [插入程式碼片段] 或 範圍陳述式,找出想要的片段,然後將其按兩下。 按 TabShift+Tab 鍵以修改片段的相關部分,然後按 EnterEsc 鍵接受。 如需詳細資訊,請參閱程式碼片段

程式碼片段包含在副檔名為 .snippet* 的 XML 檔案中。 片段可以包含插入之後反白顯示的欄位,讓使用者可以找到並加以變更。 片段檔案也提供程式碼片段管理員的資訊,因此可以在正確的類別中顯示片段名稱。 有關片段結構的資訊,請參閱程式碼片段結構參考

本逐步解說教導如何完成以下任務:

  1. 建立並註冊特定語言的程式碼片段。

  2. 插入程式碼片段命令,新增至捷徑功能表。

  3. 實作片段擴充功能。

    本逐步解說是以逐步解說:顯示陳述式完成為基礎。

建立和註冊程式碼片段

一般而言,程式碼片段會與已註冊的語言服務相關聯。 不過,您不需要實作 LanguageService 來註冊程式碼片段。 相反地,只要在片段索引檔案中指定 GUID,然後在新增至專案的 ProvideLanguageCodeExpansionAttribute 中使用相同的 GUID。

下列步驟示範如何建立程式碼片段,並將其與特定 GUID 建立關聯。

  1. 建立以下目錄結構:

    %InstallDir%\TestSnippets\Snippets\1033\

    此目錄結構中的 %InstallDir%,是 Visual Studio 安裝資料夾。 (雖然此路徑通常用於安裝程式碼片段,但您可以指定任何路徑。)

  2. 在 \1033\ 資料夾中,建立 .xml 檔案並將其命名為 TestSnippets.xml。 (雖然此名稱通常用於程式碼片段索引檔案,但只要其副檔名為 .xml,就可以指定任何名稱。新增下列文字,然後刪除預留位置 GUID 並新增您自己的 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. TestCompletionHandler 新增至 ProvideLanguageCodeExpansionAttribute 類別。 這個屬性可以新增至專案中的任何公用或內部 (非靜態) 類別。 (您可能必須為 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 方法中啟動插入程式碼片段命令。 啟用命令之前,請檢查自動化函式內未呼叫這個方法,因為按一下插入程式碼片段命令時,它會顯示片段選擇器使用者介面 (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 的檔案,然後在其中的任何位置上按一下滑鼠右鍵。 插入程式碼片段命令應該會出現在捷徑功能表上。

在片段選擇器 UI 中實作片段擴充功能

本節說明如何實作程式碼片段擴充功能,以便在捷徑功能表上按一下插入程式碼片段時,顯示片段選擇器 UI。 當使用者輸入程式碼片段捷徑然後按 Tab 鍵時,也會展開程式碼片段。

若要顯示片段選擇器 UI,並啟用瀏覽和插入後片段接受,請使用 Exec 方法。 插入本身是由 OnItemChosen 方法處理。

程式碼片段擴充功能的實作會使用舊版 Microsoft.VisualStudio.TextManager.Interop 介面。 當您從目前的編輯器類別轉譯為舊版程式碼時,請記住,舊版介面會使用行號和欄號的組合來指定文字緩衝區中的位置,但目前的類別會使用一個索引。 因此,如果緩衝區內有三行,而每行有 10 個字元 (加上新行,也計算為一個字元) ,則第三行的第四個字元是位於目前實作中的位置 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. 若要在使用者按一下插入程式碼片段命令時顯示片段選擇器,請將下列程式碼新增至 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 程式碼之後,新增下列程式碼來處理片段瀏覽 (當使用者在片段插入後按 TabShift+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");
    

    請勿按 EnterEsc 鍵。

  5. TabShift+Tab 鍵,以在 “first” 和 “second” 之間切換。

  6. EnterEsc 鍵,以接受插入。

  7. 在文字的不同部分中,輸入 "test",然後按 Tab 鍵。因為 "test" 是程式碼片段捷徑,因此應該再次插入片段。