演练:显示签名帮助

当用户键入参数列表起始字符(通常是左括号)时,签名帮助(也称为 参数信息)在工具提示中显示方法的签名。 作为参数和参数分隔符(通常为逗号),工具提示将更新为以粗体显示下一个参数。 可以通过以下方式定义签名帮助:在语言服务的上下文中,定义自己的文件扩展名和内容类型,并仅显示该类型的签名帮助,或显示现有内容类型的签名帮助(例如“text”)。 本演练演示如何显示“text”内容类型的签名帮助。

签名帮助通常通过键入特定字符(例如“(”)(左括号)触发,并通过键入另一个字符(例如“)”(右括号)来消除。 可以通过使用键击( IOleCommandTarget 接口)的命令处理程序和实现 IVsTextViewCreationListener 接口的处理程序提供程序来实现通过键入字符触发的 IntelliSense 功能。 若要创建签名帮助源,该源是参与签名帮助的签名列表,请实现 ISignatureHelpSource 该接口和运行接口的 ISignatureHelpSourceProvider 源提供程序。 提供程序是托管扩展性框架(MEF)组件部件,负责导出源和控制器类以及导入服务和中转站,例如 ITextStructureNavigatorSelectorService,允许在文本缓冲区中导航,以及 ISignatureHelpBroker触发签名帮助会话的提供程序。

本演练演示如何为硬编码的标识符集设置签名帮助。 在完整的实现中,语言负责提供该内容。

创建 MEF 项目

创建 MEF 项目

  1. 创建 C# VSIX 项目。 (在 “新建项目 ”对话框,选择 Visual C# /扩展性,然后选择 VSIX Project。)将解决方案 SignatureHelpTest命名为 。

  2. 向项目添加编辑器分类器项模板。 有关详细信息,请参阅使用编辑器项模板创建扩展

  3. 删除现有的类文件。

  4. 将以下引用添加到项目,并确保 CopyLocal 设置为 false

    Microsoft.VisualStudio.Editor

    Microsoft.VisualStudio.Language.Intellisense

    Microsoft.VisualStudio.OLE.Interop

    Microsoft.VisualStudio.Shell.14.0

    Microsoft.VisualStudio.TextManager.Interop

实现签名帮助签名和参数

签名帮助源基于实现 ISignature的签名,每个签名都包含实现 IParameter的参数。 在完整实现中,此信息将从语言文档获取,但在此示例中,签名是硬编码的。

实现签名帮助签名和参数

  1. 添加一个类文件并将其命名为 SignatureHelpSource

  2. 添加以下 import 语句。

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel.Composition;
    using System.Runtime.InteropServices;
    using Microsoft.VisualStudio.Language.Intellisense;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Utilities;
    using Microsoft.VisualStudio.Editor;
    using Microsoft.VisualStudio.Text.Operations;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.TextManager.Interop;
    using Microsoft.VisualStudio.OLE.Interop;
    
  3. 添加一个名为 TestParameter 实现的 IParameter类。

    internal class TestParameter : IParameter
    
  4. 添加设置所有属性的构造函数。

    public TestParameter(string documentation, Span locus, string name, ISignature signature)
    {
        Documentation = documentation;
        Locus = locus;
        Name = name;
        Signature = signature;
    }
    
  5. 添加 . IParameter的属性

    public string Documentation { get; private set; }
    public Span Locus { get; private set; }
    public string Name { get; private set; }
    public ISignature Signature { get; private set; }
    public Span PrettyPrintedLocus { get; private set; }
    
  6. 添加一个名为 TestSignature 实现的 ISignature类。

    internal class TestSignature : ISignature
    
  7. 添加一些专用字段。

    private ITextBuffer m_subjectBuffer;
    private IParameter m_currentParameter;
    private string m_content;
    private string m_documentation;
    private ITrackingSpan m_applicableToSpan;
    private ReadOnlyCollection<IParameter> m_parameters;
    private string m_printContent;
    
  8. 添加一个构造函数,用于设置字段并订阅事件 Changed

    internal TestSignature(ITextBuffer subjectBuffer, string content, string doc, ReadOnlyCollection<IParameter> parameters)
    {
        m_subjectBuffer = subjectBuffer;
        m_content = content;
        m_documentation = doc;
        m_parameters = parameters;
        m_subjectBuffer.Changed += new EventHandler<TextContentChangedEventArgs>(OnSubjectBufferChanged);
    }
    
  9. 声明事件 CurrentParameterChanged 。 当用户填写签名中的某个参数时,将引发此事件。

    public event EventHandler<CurrentParameterChangedEventArgs> CurrentParameterChanged;
    
  10. 实现该 CurrentParameter 属性,以便在属性值发生更改时引发 CurrentParameterChanged 事件。

    public IParameter CurrentParameter
    {
        get { return m_currentParameter; }
        internal set
        {
            if (m_currentParameter != value)
            {
                IParameter prevCurrentParameter = m_currentParameter;
                m_currentParameter = value;
                this.RaiseCurrentParameterChanged(prevCurrentParameter, m_currentParameter);
            }
        }
    }
    
  11. 添加引发 CurrentParameterChanged 事件的方法。

    private void RaiseCurrentParameterChanged(IParameter prevCurrentParameter, IParameter newCurrentParameter)
    {
        EventHandler<CurrentParameterChangedEventArgs> tempHandler = this.CurrentParameterChanged;
        if (tempHandler != null)
        {
            tempHandler(this, new CurrentParameterChangedEventArgs(prevCurrentParameter, newCurrentParameter));
        }
    }
    
  12. 添加一个方法,该方法通过将签名中的 ApplicableToSpan 逗号数与签名中的逗号数进行比较来计算当前参数。

    internal void ComputeCurrentParameter()
    {
        if (Parameters.Count == 0)
        {
            this.CurrentParameter = null;
            return;
        }
    
        //the number of commas in the string is the index of the current parameter
        string sigText = ApplicableToSpan.GetText(m_subjectBuffer.CurrentSnapshot);
    
        int currentIndex = 0;
        int commaCount = 0;
        while (currentIndex < sigText.Length)
        {
            int commaIndex = sigText.IndexOf(',', currentIndex);
            if (commaIndex == -1)
            {
                break;
            }
            commaCount++;
            currentIndex = commaIndex + 1;
        }
    
        if (commaCount < Parameters.Count)
        {
            this.CurrentParameter = Parameters[commaCount];
        }
        else
        {
            //too many commas, so use the last parameter as the current one.
            this.CurrentParameter = Parameters[Parameters.Count - 1];
        }
    }
    
  13. Changed 调用 ComputeCurrentParameter() 该方法的事件添加事件处理程序。

    internal void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e)
    {
        this.ComputeCurrentParameter();
    }
    
  14. 实现 ApplicableToSpan 属性。 此属性包含与 ITrackingSpan 签名应用到的缓冲区中的文本范围对应的属性。

    public ITrackingSpan ApplicableToSpan
    {
        get { return (m_applicableToSpan); }
        internal set { m_applicableToSpan = value; }
    }
    
  15. 实现其他参数。

    public string Content
    {
        get { return (m_content); }
        internal set { m_content = value; }
    }
    
    public string Documentation
    {
        get { return (m_documentation); }
        internal set { m_documentation = value; }
    }
    
    public ReadOnlyCollection<IParameter> Parameters
    {
        get { return (m_parameters); }
        internal set { m_parameters = value; }
    }
    
    public string PrettyPrintedContent
    {
        get { return (m_printContent); }
        internal set { m_printContent = value; }
    }
    

实现签名帮助源

签名帮助源是你为其提供信息的签名集。

实现签名帮助源

  1. 添加一个名为 TestSignatureHelpSource 实现的 ISignatureHelpSource类。

    internal class TestSignatureHelpSource : ISignatureHelpSource
    
  2. 添加对文本缓冲区的引用。

    private ITextBuffer m_textBuffer;
    
  3. 添加设置文本缓冲区和签名帮助源提供程序的构造函数。

    public TestSignatureHelpSource(ITextBuffer textBuffer)
    {
        m_textBuffer = textBuffer;
    }
    
  4. 实现 AugmentSignatureHelpSession 方法。 在此示例中,签名是硬编码的,但在完整实现中,你将从语言文档获取此信息。

    public void AugmentSignatureHelpSession(ISignatureHelpSession session, IList<ISignature> signatures)
    {
        ITextSnapshot snapshot = m_textBuffer.CurrentSnapshot;
        int position = session.GetTriggerPoint(m_textBuffer).GetPosition(snapshot);
    
        ITrackingSpan applicableToSpan = m_textBuffer.CurrentSnapshot.CreateTrackingSpan(
         new Span(position, 0), SpanTrackingMode.EdgeInclusive, 0);
    
        signatures.Add(CreateSignature(m_textBuffer, "add(int firstInt, int secondInt)", "Documentation for adding integers.", applicableToSpan));
        signatures.Add(CreateSignature(m_textBuffer, "add(double firstDouble, double secondDouble)", "Documentation for adding doubles.", applicableToSpan));
    
    }
    
  5. 仅为了说明提供帮助程序方法 CreateSignature()

    private TestSignature CreateSignature(ITextBuffer textBuffer, string methodSig, string methodDoc, ITrackingSpan span)
    {
        TestSignature sig = new TestSignature(textBuffer, methodSig, methodDoc, null);
        textBuffer.Changed += new EventHandler<TextContentChangedEventArgs>(sig.OnSubjectBufferChanged);
    
        //find the parameters in the method signature (expect methodname(one, two)
        string[] pars = methodSig.Split(new char[] { '(', ',', ')' });
        List<IParameter> paramList = new List<IParameter>();
    
        int locusSearchStart = 0;
        for (int i = 1; i < pars.Length; i++)
        {
            string param = pars[i].Trim();
    
            if (string.IsNullOrEmpty(param))
                continue;
    
            //find where this parameter is located in the method signature
            int locusStart = methodSig.IndexOf(param, locusSearchStart);
            if (locusStart >= 0)
            {
                Span locus = new Span(locusStart, param.Length);
                locusSearchStart = locusStart + param.Length;
                paramList.Add(new TestParameter("Documentation for the parameter.", locus, param, sig));
            }
        }
    
        sig.Parameters = new ReadOnlyCollection<IParameter>(paramList);
        sig.ApplicableToSpan = span;
        sig.ComputeCurrentParameter();
        return sig;
    }
    
  6. 实现 GetBestMatch 方法。 在此示例中,只有两个签名,每个签名都有两个参数。 因此,不需要此方法。 在提供多个签名帮助源的更完整的实现中,此方法用于确定最高优先级的签名帮助源是否可以提供匹配的签名。 如果无法,该方法将返回 null,并要求下一个优先级最高的源提供匹配项。

    public ISignature GetBestMatch(ISignatureHelpSession session)
    {
        if (session.Signatures.Count > 0)
        {
            ITrackingSpan applicableToSpan = session.Signatures[0].ApplicableToSpan;
            string text = applicableToSpan.GetText(applicableToSpan.TextBuffer.CurrentSnapshot);
    
            if (text.Trim().Equals("add"))  //get only "add" 
                return session.Signatures[0];
        }
        return null;
    }
    
  7. Dispose()实现方法:

    private bool m_isDisposed;
    public void Dispose()
    {
        if (!m_isDisposed)
        {
            GC.SuppressFinalize(this);
            m_isDisposed = true;
        }
    }
    

实现签名帮助源提供程序

签名帮助源提供程序负责导出托管扩展性框架 (MEF) 组件部件,以及实例化签名帮助源。

实现签名帮助源提供程序

  1. 添加一个名为 TestSignatureHelpSourceProvider 实现 ISignatureHelpSourceProvider的类,并使用“text”和 Before=“default”的 a NameAttribute、一个 ContentTypeAttribute “text”和 a OrderAttribute of Before=“default”导出它。

    [Export(typeof(ISignatureHelpSourceProvider))]
    [Name("Signature Help source")]
    [Order(Before = "default")]
    [ContentType("text")]
    internal class TestSignatureHelpSourceProvider : ISignatureHelpSourceProvider
    
  2. TryCreateSignatureHelpSource通过实例化 TestSignatureHelpSource.

    public ISignatureHelpSource TryCreateSignatureHelpSource(ITextBuffer textBuffer)
    {
        return new TestSignatureHelpSource(textBuffer);
    }
    

实现命令处理程序

签名帮助通常由左括号“(”字符触发,右括号“)”字符消除。 你可以通过运行 a IOleCommandTarget 来处理这些击键,以便在它收到以已知方法名称开头的左括号字符时触发签名帮助会话,并在收到右括号字符时关闭会话。

实现命令处理程序

  1. 添加一个名为 TestSignatureHelpCommand 实现的 IOleCommandTarget类。

    internal sealed class TestSignatureHelpCommandHandler : IOleCommandTarget
    
  2. IVsTextView 适配器添加专用字段(这使你可以将命令处理程序添加到命令处理程序链)、文本视图、签名帮助代理和会话、a ITextStructureNavigator和下一个 IOleCommandTarget

    IOleCommandTarget m_nextCommandHandler;
    ITextView m_textView;
    ISignatureHelpBroker m_broker;
    ISignatureHelpSession m_session;
    ITextStructureNavigator m_navigator;
    
  3. 添加构造函数来初始化这些字段,并将命令筛选器添加到命令筛选器链。

    internal TestSignatureHelpCommandHandler(IVsTextView textViewAdapter, ITextView textView, ITextStructureNavigator nav, ISignatureHelpBroker broker)
    {
        this.m_textView = textView;
        this.m_broker = broker;
        this.m_navigator = nav;
    
        //add this to the filter chain
        textViewAdapter.AddCommandFilter(this, out m_nextCommandHandler);
    }
    
  4. Exec实现当命令筛选器在已知方法名称后收到左括号“(”字符)时触发签名帮助会话的方法,并在会话仍然处于活动状态时接收右括号“)”字符时关闭会话。 在每种情况下,命令都会转发。

    public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
    {
        char typedChar = char.MinValue;
    
        if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
        {
            typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
            if (typedChar.Equals('('))
            {
                //move the point back so it's in the preceding word
                SnapshotPoint point = m_textView.Caret.Position.BufferPosition - 1;
                TextExtent extent = m_navigator.GetExtentOfWord(point);
                string word = extent.Span.GetText();
                if (word.Equals("add"))
                    m_session = m_broker.TriggerSignatureHelp(m_textView);
    
            }
            else if (typedChar.Equals(')') && m_session != null)
            {
                m_session.Dismiss();
                m_session = null;
            }
        }
        return m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
    }
    
  5. 实现该方法 QueryStatus ,使其始终转发命令。

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    

实现签名帮助命令提供程序

可以通过实现 IVsTextViewCreationListener 创建文本视图时实例化命令处理程序来提供签名帮助命令。

实现签名帮助命令提供程序

  1. 使用 和 TextViewRoleAttribute添加一个名为TestSignatureHelpController实现IVsTextViewCreationListener和导出它的NameAttribute类。 ContentTypeAttribute

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("Signature Help controller")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    [ContentType("text")]
    internal class TestSignatureHelpCommandProvider : IVsTextViewCreationListener
    
  2. IVsEditorAdaptersFactoryService 导入(用于获取ITextView给定IVsTextView对象)、ITextStructureNavigatorSelectorService(用于查找当前单词)和ISignatureHelpBroker(用于触发签名帮助会话)。

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService;
    
    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
    [Import]
    internal ISignatureHelpBroker SignatureHelpBroker;
    
  3. VsTextViewCreated通过实例化TestSignatureCommandHandler方法实现该方法。

    public void VsTextViewCreated(IVsTextView textViewAdapter)
    {
        ITextView textView = AdapterService.GetWpfTextView(textViewAdapter);
        if (textView == null)
            return;
    
        textView.Properties.GetOrCreateSingletonProperty(
             () => new TestSignatureHelpCommandHandler(textViewAdapter,
                textView,
                NavigatorService.GetTextStructureNavigator(textView.TextBuffer),
                SignatureHelpBroker));
    }
    

生成并测试代码

若要测试此代码,请生成 SignatureHelpTest 解决方案并在实验实例中运行它。

生成和测试 SignatureHelpTest 解决方案

  1. 生成解决方案。

  2. 在调试器中运行此项目时,将启动 Visual Studio 的第二个实例。

  3. 创建文本文件并键入一些包含单词“add”和左括号的文本。

  4. 键入左括号后,应会看到一个工具提示,其中显示该方法的两个签名 add() 的列表。