为代码段 (托管包结构) 支持

代码段是插入到源文件中的代码节。 该代码段是一个基于 XML 的模板字段集。 这些字段显示,在插入代码段并根据此代码段插入的上下文后具有不同的值。 在插入之后此代码段,语言服务通过设置此代码段。

此代码段在特定插入使用 tab 键,允许该代码段的字段导航的编辑模式。 字段可以支持 IntelliSense 样式的下拉菜单。 用户进行此代码段到源文件通过键入 enter 或 ESC 键。 若要了解有关代码段请参见 代码段

托管包框架为代码段支持

托管包框架 (MPF)支持大多数 snippets 功能,读取模板以插入代码段,从而特定编辑模式。 support 通过 ExpansionProvider 类管理。

Source 实例化类时,在 LanguageService 类的 CreateExpansionProvider 方法调用获取 ExpansionProvider 对象 (请注意基本 LanguageService 类始终返回每 Source 对象的新 ExpansionProvider 对象)。

MPF 不支持扩展功能。 扩展功能是在模板代码段嵌入并返回该字段上的一个或多个值的命名功能。 值按语言服务返回通过 ExpansionFunction 对象。 必须由语言服务实现 ExpansionFunction 对象支持扩展功能。

提供代码段支持

若要启用对代码段支持,您必须提供或安装代码段和必须为用户提供了插入这些代码段。 有三个步骤以启用对代码段支持:

  1. 安装代码段文件。

  2. 启用语言服务的代码段。

  3. 调用 ExpansionProvider 对象。

安装代码段文件

语言的所有代码段存储为模板 XML 文件,通常每个文件一个代码段模板。 有关使用代码段模板的 XML 架构的详细信息,请参见 代码段架构参考。 每个代码段模板确定与语言 ID. 此语言 ID 在注册表中指定和放置到标记的 Language 模板中的属性 Code 的。

通常有代码段存储模板文件的两个位置:1) 该语言在用户的文件夹进行安装和 2)。 这些位置添加到注册表,以便 Visual Studio 代码段管理器 能找到代码段。 用户的文件夹是存储位置用户创建的代码段。

安装的代码段模板文件中的典型文件夹格式如下所示: [InstallRoot]\[TestLanguage]\Snippets \[] LCID\Snippets。

[InstallRoot] 是安装该语言的文件夹。

[TestLanguage] 是该语言的名称作为文件夹的名称。

[] LCID 是区域设置 ID. 这是如何存储代码段的本地化版本。 例如,英语区域设置 ID 为 1033,因此, [] LCID 在 1033 之前替换。

必须提供一个其他文件,这就是索引文件,通常会调用 SnippetsIndex.xml 或 ExpansionsIndex.xml (在 .xml 可以使用任何有效的文件名关闭)。 此文件位于 [InstallRoot]\[TestLanguage] 文件夹通常存储并指定代码段文件夹以及语言 ID 的确切使用代码段的语言服务位置和 GUID。 索引文件的确切的路径在 “安装放置到注册表如后面所述注册表项”。 这是 SnippetsIndex.xml 文件的示例:

<?xml version="1.0" encoding="utf-8" ?>
<SnippetCollection>
    <Language Lang="Testlanguage" Guid="{b614a40a-80d9-4fac-a6ad-fc2868fff7cd}">
        <SnippetDir>
            <OnOff>On</OnOff>
            <Installed>true</Installed>
            <Locale>1033</Locale>
            <DirPath>%InstallRoot%\TestLanguage\Snippets\%LCID%\Snippets\</DirPath>
            <LocalizedName>Snippets</LocalizedName>
        </SnippetDir>
    </Language>
</SnippetCollection>

Language 标记指定语言 ID ( Lang 属性) 和语言服务的 GUID。

此示例假定您在 Visual Studio 安装文件夹安装的语言服务。 %LCID% 在用户的当前区域设置 ID. 替换 多 SnippetDir 个标记可以添加,每个不同的目录和区域设置。 此外,代码段文件夹可以包含子文件夹,每个都具有标记的索引文件 SnippetSubDir 中标识在标记 SnippetDir 中。

用户还可以创建自己的语言来自己的代码段。 这些在用户的设置文件夹,例如 [TestDocs]\Code Snippets \[TestLanguage]\Test Code Snippets 通常存储,其中 [TestDocs] 是用户的设置文件夹的位置 Visual Studio 的。

以下替换元素可以在标记中存储的路径 DirPath 放在索引文件。

元素

说明

%LCID%

区域设置 ID。

%InstallRoot%

根 Visual Studio 的安装文件夹,例如, C: \Program Files\Microsoft Visual Studio 8。

%ProjDir%

包含此项目的文件夹。

%ProjItem%

包含当前项目项的文件夹。

%TestDocs%

在用户的设置文件夹的文件夹,例如, C: \Documents and Settings \[用户名]\My Documents\Visual Studio \ 8。

启用语言服务的代码段

可以通过添加 ProvideLanguageCodeExpansionAttribute 属性启用语言服务的代码段到 VSPackage (请参见 注册语言服务 (托管包结构) 有关详细信息)。 ShowRootsSearchPaths 参数是可选的,但是,您应包括命名参数的 SearchPaths 以通知 代码段管理器 代码段的位置。

下面是的示例演示如何使用此属性:

[ProvideLanguageCodeExpansion(
         typeof(TestSnippetLanguageService),
         "Test Snippet Language",          // Name of language used as registry key
         0,                               // Resource ID of localized name of language service
         "Test Snippet Language",        // Name of Language attribute in snippet template
         @"%InstallRoot%\Test Snippet Language\Snippets\%LCID%\SnippetsIndex.xml",  // Path to snippets index
         SearchPaths = @"%InstallRoot%\Test Snippet Language\Snippets\%LCID%\")]    // Path to snippets

调用外接程序

语言服务控制所有代码段中插入,以及方式插入调用。

调用代码段的外接程序

有两种扩展操作提供程序:使用一个菜单命令或通过使用完成的快捷方式列表。

插入使用菜单命令的代码段

若要使用菜单命令显示代码段浏览器中,您将添加一个菜单命令然后对 ExpansionProvider 接口的 DisplayExpansionBrowser 方法以响应该菜单命令。

  1. 添加一个命令和一个按钮添加到 .vsct 文件。 可以找到这样做的命令。 演练:使用 Visual Studio 创建包模板的菜单命令

  2. ViewFilter 类派生类并重写 QueryCommandStatus 方法指示为新菜单命令支持。 此示例始终启用菜单命令。

    using Microsoft.VisualStudio.Package;
    
    namespace TestLanguagePackage
    {
        class TestViewFilter : ViewFilter
        {
            public TestViewFilter(CodeWindowManager mgr, IVsTextView view)
                : base(mgr, view)
            {
            }
    
            protected override int QueryCommandStatus(ref Guid guidCmdGroup,
                                                      uint nCmdId)
            {
                int hr = base.QueryCommandStatus(ref guidCmdGroup, nCmdId);
                // If the base class did not recognize the command then
                // see if we can handle the command.
                if (hr == (int)Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_UNKNOWNGROUP)
                {
                    if (guidCmdGroup == GuidList.guidTestLanguagePackageCmdSet)
                    {
                        if (nCmdId == PkgCmdIDList.InvokeCodeSnippetsBrowser)
                        {
                            hr = (int)(OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_ENABLED);
                        }
                    }
                }
                return hr;
            }
        }
    }
    
  3. 重写在 ViewFilter 类的 HandlePreExec 方法获取 ExpansionProvider 对象并调用该对象的 DisplayExpansionBrowser 方法。

    using Microsoft.VisualStudio.Package;
    
    namespace TestLanguagePackage
    {
        class TestViewFilter : ViewFilter
        {
            public override bool HandlePreExec(ref Guid guidCmdGroup,
                                               uint nCmdId,
                                               uint nCmdexecopt,
                                               IntPtr pvaIn,
                                               IntPtr pvaOut)
            {
                if (base.HandlePreExec(ref guidCmdGroup,
                                       nCmdId,
                                       nCmdexecopt,
                                       pvaIn,
                                       pvaOut))
                {
                    // Base class handled the command.  Do nothing more here.
                    return true;
                }
    
                if (guidCmdGroup == GuidList.guidTestLanguagePackageCmdSet)
                {
                    if (nCmdId == PkgCmdIDList.InvokeCodeSnippetsBrowser)
                    {
                        ExpansionProvider ep = this.GetExpansionProvider();
                        if (this.TextView != null && ep != null)
                        {
                            bool bDisplayed = ep.DisplayExpansionBrowser(
                                this.TextView,
                                "TestLanguagePackage Snippet:",
                                null,
                                false,
                                null,
                                false);
                        }
                        return true;   // Handled the command.
                    }
                }
                return false;   // Did not handle the command.
            }
        }
    }
    

    在插入代码段的过程中, ExpansionProvider 类的以下方法按照在该发布顺序的 Visual Studio 调用:

  4. OnItemChosen

  5. IsValidKind

  6. OnBeforeInsertion

  7. FormatSpan

  8. OnAfterInsertion

    OnAfterInsertion 调用方法后,插入到此代码段,并 ExpansionProvider 对象在特定来修改插入的代码段使用的编辑模式。

插入使用快捷的代码段

一个快捷方式的实现从完成于实现菜单命令列表包含。 您必须先添加代码段的快捷方式。 IntelliSense 文字完成列表。 然后必须检测代码段的快捷方式名称由于完成时,插入了。 最后,您必须获取代码段标题和路径使用快捷方式名称和对 ExpansionProvider 方法的 InsertNamedExpansion 方法传递该信息。

若要添加代码段的快捷方式。运行完成列表,将其添加到 AuthoringScope 类的 Declarations 对象。 必须确定可以标识快捷方式为代码段的名称。 有关示例,请参见演练:获取列出已安装的代码段 (托管包结构)

可以检测代码段快捷方式的插入到 Declarations 类的 OnAutoComplete 方法的。 由于代码段的名称已插入到源文件,则必须移除,而插入时展开。 InsertNamedExpansion 方法采用描述问题的代码段插入的范围;如果范围源文件中包括整个代码段名称,该名称将此代码段替换。

这是 Declarations 类的版本命名的处理插入代码段的快捷方式名称。 在 Declarations 类的其他方法以便清楚省略。 请注意此类构造函数采用 LanguageService 对象。 这可以从 AuthoringScope 对象的版本。 (例如, AuthoringScope 类的实现可能对其构造函数的 LanguageService 对象和管该对象传递给 TestDeclarations 类构造函数)。

[C#]
using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;

namespace TestLanguagePackage
{
    internal class TestDeclarations : Declarations
    {
        private ArrayList       declarations;
        private LanguageService languageService;
        private TextSpan        commitSpan;

        public TestDeclarations(LanguageService langService)
            : base()
        {
            languageService = langService;
            declarations = new ArrayList();
        }

        // This method is used to add declarations to the internal list.
        public void AddDeclaration(TestDeclaration declaration)
        {
            declarations.Add(declaration);
        }

        

        // This method is called to get the string to commit to the source buffer.
        // Note that the initial extent is only what the user has typed so far.
        public override string OnCommit(IVsTextView textView,
                                        string textSoFar,
                                        char commitCharacter,
                                        int index,
                                        ref TextSpan initialExtent)
        {
            // We intercept this call only to get the initial extent
            // of what was committed to the source buffer.
            commitSpan = initialExtent;

            return base.OnCommit(textView,
                                 textSoFar,
                                 commitCharacter,
                                 index,
                                 ref initialExtent);
        }

        // This method is called after the string has been committed to the source buffer.
        public override char OnAutoComplete(IVsTextView textView,
                                            string committedText,
                                            char commitCharacter,
                                            int index)
        {
            TestDeclaration item = declarations[index] as TestDeclaration;
            if (item != null)
            {
                // In this example, TestDeclaration identifies types with a string.
                // You can choose a different approach.
                if (item.Type == "snippet")
                {
                    Source src = languageService.GetSource(textView);
                    if (src != null)
                    {
                        ExpansionProvider ep = src.GetExpansionProvider();
                        if (ep != null)
                        {
                            string title;
                            string path;
                            int commitLength = commitSpan.iEndIndex - commitSpan.iStartIndex;
                            if (commitLength < committedText.Length)
                            {
                                // Replace everything that was inserted
                                // so calculate the span of the full
                                // insertion, taking into account what
                                // was inserted when the commitSpan
                                // was obtained in the first place.
                                commitSpan.iEndIndex += (committedText.Length - commitLength);
                            }

                            if (ep.FindExpansionByShortcut(textView,
                                                           committedText,
                                                           commitSpan,
                                                           true,
                                                           out title,
                                                           out path))
                            {
                                ep.InsertNamedExpansion(textView,
                                                        title,
                                                        path,
                                                        commitSpan,
                                                        false);
                            }
                        }
                    }
                }
            }
            return '\0';
        }
    }
}

当语言服务获取快捷方式名称时,它调用 FindExpansionByShortcut 方法获取文件名和代码段前缀。 语言服务然后调用在 ExpansionProvider 类的 InsertNamedExpansion 方法插入代码段。 在插入代码段的过程中,以下方法由发布顺序的 Visual Studio 对 ExpansionProvider 类:

  1. IsValidKind

  2. OnBeforeInsertion

  3. FormatSpan

  4. OnAfterInsertion

有关获取安装的代码段列表的更多信息的语言服务,请参见 演练:获取列出已安装的代码段 (托管包结构)

实现 ExpansionFunction 类

扩展功能是在模板代码段嵌入并返回该字段上的一个或多个值的命名功能。 为了支持扩展功能在语言服务,您必须从 ExpansionFunction 类派生类并实现 GetCurrentValue 方法。 然后必须重写在 LanguageService 类的 CreateExpansionFunction 方法返回 ExpansionFunction 类的版本的新实例化所支持的每个扩展功能。 如果支持可能值列表从扩展功能,您还必须重写在 ExpansionFunction 类的 GetIntellisenseList 方法返回这些值列表。

使用参数或需要访问其他字段不应与一个可编辑字段的扩展功能,,因为扩展提供程序可能不完全初始化,展开函数调用的时间。 因此,展开函数无法获取其参数或其他字段的值。

示例

这是一个示例的调用 GetName 的简单扩展功能如何将实现。 此扩展功能追加号传递一个基类名称,每次扩展功能实例化 (对应于关联的代码段每次插入)。

using Microsoft.VisualStudio.Package;

namespace TestLanguagePackage
{
    public class TestLanguageService : LanguageService
    {
        private int classNameCounter = 0;

        public override ExpansionFunction CreateExpansionFunction(
            ExpansionProvider provider,
            string functionName)
        {
            ExpansionFunction function = null;
            if (functionName == "GetName")
            {
                ++classNameCounter;
                function = new TestGetNameExpansionFunction(provider, classNameCounter);
            }
            return function;
        }
    }

    internal class TestGetNameExpansionFunction : ExpansionFunction
    {
        private int nameCount;

        TestGetNameExpansionFunction(ExpansionProvider provider, int counter)
            : base(provider)
        {
            nameCount = counter;
        }

        public override string GetCurrentValue()
        {
            string name = "TestClass";
            name += nameCount.ToString();
            return name;
        }
    }
}

请参见

任务

演练:获取列出已安装的代码段 (托管包结构)

概念

注册语言服务 (托管包结构)

其他资源

语言服务功能 (托管包结构)

代码段