可以创建代码片段并将其包含在编辑器扩展中,以便扩展的用户可以将它们添加到自己的代码中。
代码片段是可以合并到文件中的代码片段或其他文本。 若要查看已注册特定编程语言的所有代码段,请在 “工具” 菜单上,单击“ 代码段管理器”。 若要在文件中插入代码段,请右键单击所需代码段的位置,单击“插入代码段”或 “环绕”,找到所需的代码段,然后双击它。 按 Tab 或 Shift+Tab 可修改代码片段的相关部分,然后按 Enter 或 Esc 接受它。 有关详细信息,请参阅 代码片段。
代码片段包含在扩展名为 .snippet* 的 XML 文件中。 代码片段可以包含插入代码段后突出显示的字段,以便用户可以查找和更改它们。 代码段文件还提供代码片段管理器的信息,以便它可以在正确的类别中显示代码段名称。 有关代码段架构的信息,请参阅 代码片段架构参考。
本演练介绍如何完成以下任务:
- 创建并注册特定语言的代码片段。 
- 将 “插入代码段 ”命令添加到快捷菜单。 
- 实现代码段扩展。 - 本演练基于 Walkthrough: Display 语句完成。 
创建和注册代码片段
通常,代码片段与已注册的语言服务相关联。 但是,无需实现注册 LanguageService 代码片段。 相反,只需在代码段索引文件中指定 GUID,然后在添加到项目时 ProvideLanguageCodeExpansionAttribute 使用相同的 GUID。
以下步骤演示如何创建代码片段并将其与特定的 GUID 相关联。
- 创建以下目录结构: - %InstallDir%\TestSnippets\Snippets\1033\ - 其中 %InstallDir% 是 Visual Studio 安装文件夹。 (虽然此路径通常用于安装代码片段,但可以指定任何路径。 
- 在 \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>
- 在代码片段文件夹中创建文件,将其命名为 测试 - .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 内容类型,并将 Project 设置为项目的名称。 
- 选择“完成测试”项目,然后在属性窗口将“生成 Pkgdef 文件”设置为 true。 保存项目。 
- 向项目添加静态 - SnippetUtilities类。
- 在 SnippetUtilities 类中,定义 GUID 并为其提供在 SnippetsIndex.xml 文件中使用的值。 
- 将类 ProvideLanguageCodeExpansionAttribute 添加到该 - TestCompletionHandler类。 可以将此属性添加到项目中的任何公共或内部(非静态)类。 (可能需要为 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激活 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); }
- 生成并运行该项目。 在实验实例中,打开扩展名 为 .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”是代码片段快捷方式,因此应再次插入代码片段。