逐步解說︰實作程式碼片段
您可以建立程式碼片段並將其包含在編輯器擴充功能中,讓擴充功能的使用者可以將它們新增至自己的程式碼。
程式碼片段是可併入檔案的程式碼或其他文字的片段。 若要檢視已註冊給特定程式語言的所有程式碼片段,請在工具 功能表上,按一下程式碼片段管理員。 若要在檔案中插入程式碼片段,請以滑鼠右鍵按下想要插入片段的位置,按一下 [插入程式碼片段] 或 範圍陳述式,找出想要的片段,然後將其按兩下。 按 Tab 或 Shift+Tab 鍵以修改片段的相關部分,然後按 Enter 或 Esc 鍵接受。 如需詳細資訊,請參閱程式碼片段。
程式碼片段包含在副檔名為 .snippet* 的 XML 檔案中。 片段可以包含插入之後反白顯示的欄位,讓使用者可以找到並加以變更。 片段檔案也提供程式碼片段管理員的資訊,因此可以在正確的類別中顯示片段名稱。 有關片段結構的資訊,請參閱程式碼片段結構參考。
本逐步解說教導如何完成以下任務:
建立並註冊特定語言的程式碼片段。
將插入程式碼片段命令,新增至捷徑功能表。
實作片段擴充功能。
本逐步解說是以逐步解說:顯示陳述式完成為基礎。
建立和註冊程式碼片段
一般而言,程式碼片段會與已註冊的語言服務相關聯。 不過,您不需要實作 LanguageService 來註冊程式碼片段。 相反地,只要在片段索引檔案中指定 GUID,然後在新增至專案的 ProvideLanguageCodeExpansionAttribute 中使用相同的 GUID。
下列步驟示範如何建立程式碼片段,並將其與特定 GUID 建立關聯。
建立以下目錄結構:
%InstallDir%\TestSnippets\Snippets\1033\
此目錄結構中的 %InstallDir%,是 Visual Studio 安裝資料夾。 (雖然此路徑通常用於安裝程式碼片段,但您可以指定任何路徑。)
在 \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>
在片段資料夾中建立檔案、將其命名為測試
.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 的程式碼片段
開啟 CompletionTest 專案。 如需如何建立此專案的詳細資訊,請參閱逐步解說:顯示陳述式完成。
在專案中,新增參考至下列組件的:
Microsoft.VisualStudio.TextManager.Interop
Microsoft.VisualStudio.TextManager.Interop.8.0
microsoft.msxml
在專案中,開啟 source.extension.vsixmanifest 檔案。
請確定資產索引標籤包含 VsPackage 內容類型,且專案已設定為專案的名稱。
選取 CompletionTest 專案,然後在 屬性視窗將產生 Pkgdef 檔案設定為 true。 儲存專案。
新增靜態
SnippetUtilities
類別至專案。在 SnippetUtilities 類別中,定義 GUID,並將您在 SnippetsIndex.xml 檔案中使用的值提供給它。
將
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
建置並執行專案。 在執行專案時所啟動的 Visual Studio 實驗執行個體中,剛才註冊的片段應該會顯示在 TestSnippets 語言下的程式碼片段管理員中。
將 [插入程式碼片段] 命令,新增至捷徑功能表
文字檔案的捷徑功能表上不包含插入程式碼片段命令。 因此,您必須啟用該命令。
將 [插入程式碼片段] 命令,新增至捷徑功能表
開啟
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); }
建置並執行專案。 在實驗執行個體中,開啟副檔名為 .zzz 的檔案,然後在其中的任何位置上按一下滑鼠右鍵。 插入程式碼片段命令應該會出現在捷徑功能表上。
在片段選擇器 UI 中實作片段擴充功能
本節說明如何實作程式碼片段擴充功能,以便在捷徑功能表上按一下插入程式碼片段時,顯示片段選擇器 UI。 當使用者輸入程式碼片段捷徑然後按 Tab 鍵時,也會展開程式碼片段。
若要顯示片段選擇器 UI,並啟用瀏覽和插入後片段接受,請使用 Exec 方法。 插入本身是由 OnItemChosen 方法處理。
程式碼片段擴充功能的實作會使用舊版 Microsoft.VisualStudio.TextManager.Interop 介面。 當您從目前的編輯器類別轉譯為舊版程式碼時,請記住,舊版介面會使用行號和欄號的組合來指定文字緩衝區中的位置,但目前的類別會使用一個索引。 因此,如果緩衝區內有三行,而每行有 10 個字元 (加上新行,也計算為一個字元) ,則第三行的第四個字元是位於目前實作中的位置 27,但位於舊實作的第 2 行,位置 3。
實作片段擴充功能
在包含
TestCompletionCommandHandler
類別的檔案中,新增下列using
指示詞。使
TestCompletionCommandHandler
類別實作 IVsExpansionClient 介面。在
TestCompletionCommandHandlerProvider
類別,匯入 ITextStructureNavigatorSelectorService。為程式碼擴充功能介面和 IVsTextView 新增一些私用欄位。
在類別
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); }
若要在使用者按一下插入程式碼片段命令時顯示片段選擇器,請將下列程式碼新增至 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; }
如果片段具有可瀏覽的欄位,擴充功能工作階段會保持開啟直到確定被接受為止;如果片段沒有欄位,則該工作階段會關閉,並由 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; } } }
若要在使用者輸入對應的捷徑方式,然後按 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; }
實作 IVsExpansionClient 介面的方法 。 在此實作中,唯一重要的方法是 EndExpansion 和 OnItemChosen。 其他方法應該只會傳回 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; }
實作 OnItemChosen 方法。 實際插入擴充功能的協助程式方法,將在後續步驟中說明。 TextSpan 提供行和欄資訊,請參閱 IVsTextView。
下列私用方法會根據捷徑或標題和路徑,插入程式碼片段。 然後,它會使用片段呼叫 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; }
建置和測試程式碼片段擴充功能
您可以測試專案中的片段擴充功能是否運作。
建置方案。 當您在偵錯工具中執行這個專案時,會啟動第二個 Visual Studio 執行個體。
開啟文字檔,並輸入一些文字。
以滑鼠右鍵按一下文字中的某個位置,然後按一下插入程式碼片段。
片段選擇器 UI 應該會出現一個快顯示窗,上面寫著測試取代欄位。 按兩下快顯視窗。
應該插入下列片段。
MessageBox.Show("first"); MessageBox.Show("second");
請勿按 Enter 或 Esc 鍵。
按 Tab 和 Shift+Tab 鍵,以在 “first” 和 “second” 之間切換。
按 Enter 或 Esc 鍵,以接受插入。
在文字的不同部分中,輸入 "test",然後按 Tab 鍵。因為 "test" 是程式碼片段捷徑,因此應該再次插入片段。