演练:显示 QuickInfo 工具提示

QuickInfo 是一项 IntelliSense 功能,当用户将指针移到方法名称上时显示方法签名和说明。 可以通过定义要为其提供 QuickInfo 说明的标识符,然后创建一个用于显示内容的工具提示来实现基于语言的功能,例如 QuickInfo。 可以在语言服务的上下文中定义 QuickInfo,也可以定义自己的文件扩展名和内容类型,并仅显示该类型的 QuickInfo,也可以为现有内容类型(如“text”)显示 QuickInfo。 本演练演示如何显示“text”内容类型的 QuickInfo。

本演练中的 QuickInfo 示例显示当用户将指针移到方法名称上时的工具提示。 此设计要求你实现以下四个接口:

  • 源接口

  • 源提供程序接口

  • 控制器接口

  • 控制器提供程序接口

    源和控制器提供程序是托管扩展性框架(MEF)组件部件,负责导出源和控制器类以及导入服务和中转站,例如 ITextBufferFactoryService,创建工具提示文本缓冲区,以及 IQuickInfoBroker触发 QuickInfo 会话的源和控制器类。

    在此示例中,QuickInfo 源使用方法名称和说明的硬编码列表,但在完整实现中,语言服务和语言文档负责提供该内容。

创建 MEF 项目

创建 MEF 项目

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

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

  3. 删除现有的类文件。

实现 QuickInfo 源

QuickInfo 源负责收集标识符集及其说明,并在遇到其中一个标识符时将内容添加到工具提示文本缓冲区。 在此示例中,标识符及其说明刚刚添加到源构造函数中。

实现 QuickInfo 源

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

  2. 添加对 Microsoft.VisualStudio.Language.IntelliSense 的引用。

  3. 添加以下 import 语句。

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Language.Intellisense;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Operations;
    using Microsoft.VisualStudio.Text.Tagging;
    using Microsoft.VisualStudio.Utilities;
    
  4. 声明实现 IQuickInfoSource的类并将其命名 TestQuickInfoSource

    internal class TestQuickInfoSource : IQuickInfoSource
    
  5. 为 QuickInfo 源提供程序、文本缓冲区和一组方法名称和方法签名添加字段。 在此示例中,方法名称和签名在构造函数中 TestQuickInfoSource 初始化。

    private TestQuickInfoSourceProvider m_provider;
    private ITextBuffer m_subjectBuffer;
    private Dictionary<string, string> m_dictionary;
    
  6. 添加一个构造函数,用于设置 QuickInfo 源提供程序和文本缓冲区,并填充方法名称集以及方法签名和说明。

    public TestQuickInfoSource(TestQuickInfoSourceProvider provider, ITextBuffer subjectBuffer)
    {
        m_provider = provider;
        m_subjectBuffer = subjectBuffer;
    
        //these are the method names and their descriptions
        m_dictionary = new Dictionary<string, string>();
        m_dictionary.Add("add", "int add(int firstInt, int secondInt)\nAdds one integer to another.");
        m_dictionary.Add("subtract", "int subtract(int firstInt, int secondInt)\nSubtracts one integer from another.");
        m_dictionary.Add("multiply", "int multiply(int firstInt, int secondInt)\nMultiplies one integer by another.");
        m_dictionary.Add("divide", "int divide(int firstInt, int secondInt)\nDivides one integer by another.");
    }
    
  7. 实现 AugmentQuickInfoSession 方法。 在此示例中,如果光标位于行或文本缓冲区的末尾,该方法将查找当前单词或上一个单词。 如果单词是方法名称之一,则该方法名称的说明将添加到 QuickInfo 内容中。

    public void AugmentQuickInfoSession(IQuickInfoSession session, IList<object> qiContent, out ITrackingSpan applicableToSpan)
    {
        // Map the trigger point down to our buffer.
        SnapshotPoint? subjectTriggerPoint = session.GetTriggerPoint(m_subjectBuffer.CurrentSnapshot);
        if (!subjectTriggerPoint.HasValue)
        {
            applicableToSpan = null;
            return;
        }
    
        ITextSnapshot currentSnapshot = subjectTriggerPoint.Value.Snapshot;
        SnapshotSpan querySpan = new SnapshotSpan(subjectTriggerPoint.Value, 0);
    
        //look for occurrences of our QuickInfo words in the span
        ITextStructureNavigator navigator = m_provider.NavigatorService.GetTextStructureNavigator(m_subjectBuffer);
        TextExtent extent = navigator.GetExtentOfWord(subjectTriggerPoint.Value);
        string searchText = extent.Span.GetText();
    
        foreach (string key in m_dictionary.Keys)
        {
            int foundIndex = searchText.IndexOf(key, StringComparison.CurrentCultureIgnoreCase);
            if (foundIndex > -1)
            {
                applicableToSpan = currentSnapshot.CreateTrackingSpan
                    (
                    //querySpan.Start.Add(foundIndex).Position, 9, SpanTrackingMode.EdgeInclusive
                                            extent.Span.Start + foundIndex, key.Length, SpanTrackingMode.EdgeInclusive
                    );
    
                string value;
                m_dictionary.TryGetValue(key, out value);
                if (value != null)
                    qiContent.Add(value);
                else
                    qiContent.Add("");
    
                return;
            }
        }
    
        applicableToSpan = null;
    }
    
  8. 还必须实现 Dispose() 方法,因为 IQuickInfoSource 实现 IDisposable

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

实现 QuickInfo 源提供程序

QuickInfo 源的提供程序主要用于将自身导出为 MEF 组件部件并实例化 QuickInfo 源。 因为它是 MEF 组件部件,因此可以导入其他 MEF 组件部件。

实现 QuickInfo 源提供程序

  1. 声明名为TestQuickInfoSourceProvider实现的 IQuickInfoSourceProviderQuickInfo 源提供程序,并使用“ToolTip QuickInfo Source”、“Before=”default“OrderAttributeContentTypeAttribute”text“导出它NameAttribute

    [Export(typeof(IQuickInfoSourceProvider))]
    [Name("ToolTip QuickInfo Source")]
    [Order(Before = "Default Quick Info Presenter")]
    [ContentType("text")]
    internal class TestQuickInfoSourceProvider : IQuickInfoSourceProvider
    
  2. 导入两个编辑器服务, ITextStructureNavigatorSelectorServiceITextBufferFactoryService作为属性导入 TestQuickInfoSourceProvider

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
    [Import]
    internal ITextBufferFactoryService TextBufferFactoryService { get; set; }
    
  3. 实现 TryCreateQuickInfoSource 以返回新的 TestQuickInfoSource

    public IQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer)
    {
        return new TestQuickInfoSource(this, textBuffer);
    }
    

实现 QuickInfo 控制器

QuickInfo 控制器确定何时显示 QuickInfo。 在此示例中,当指针位于与其中一个方法名称相对应的单词上时,将显示 QuickInfo。 QuickInfo 控制器实现触发 QuickInfo 会话的鼠标悬停事件处理程序。

实现 QuickInfo 控制器

  1. 声明实现 IIntellisenseController的类并将其命名 TestQuickInfoController

    internal class TestQuickInfoController : IIntellisenseController
    
  2. 为文本视图、文本视图中表示的文本缓冲区、QuickInfo 会话和 QuickInfo 控制器提供程序添加专用字段。

    private ITextView m_textView;
    private IList<ITextBuffer> m_subjectBuffers;
    private TestQuickInfoControllerProvider m_provider;
    private IQuickInfoSession m_session;
    
  3. 添加一个构造函数,用于设置字段并添加鼠标悬停事件处理程序。

    internal TestQuickInfoController(ITextView textView, IList<ITextBuffer> subjectBuffers, TestQuickInfoControllerProvider provider)
    {
        m_textView = textView;
        m_subjectBuffers = subjectBuffers;
        m_provider = provider;
    
        m_textView.MouseHover += this.OnTextViewMouseHover;
    }
    
  4. 添加触发 QuickInfo 会话的鼠标悬停事件处理程序。

    private void OnTextViewMouseHover(object sender, MouseHoverEventArgs e)
    {
        //find the mouse position by mapping down to the subject buffer
        SnapshotPoint? point = m_textView.BufferGraph.MapDownToFirstMatch
             (new SnapshotPoint(m_textView.TextSnapshot, e.Position),
            PointTrackingMode.Positive,
            snapshot => m_subjectBuffers.Contains(snapshot.TextBuffer),
            PositionAffinity.Predecessor);
    
        if (point != null)
        {
            ITrackingPoint triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position,
            PointTrackingMode.Positive);
    
            if (!m_provider.QuickInfoBroker.IsQuickInfoActive(m_textView))
            {
                m_session = m_provider.QuickInfoBroker.TriggerQuickInfo(m_textView, triggerPoint, true);
            }
        }
    }
    
  5. 实现该方法 Detach ,以便在控制器与文本视图分离时删除鼠标悬停事件处理程序。

    public void Detach(ITextView textView)
    {
        if (m_textView == textView)
        {
            m_textView.MouseHover -= this.OnTextViewMouseHover;
            m_textView = null;
        }
    }
    
  6. ConnectSubjectBuffer 该方法和 DisconnectSubjectBuffer 方法实现为此示例的空方法。

    public void ConnectSubjectBuffer(ITextBuffer subjectBuffer)
    {
    }
    
    public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer)
    {
    }
    

实现 QuickInfo 控制器提供程序

QuickInfo 控制器的提供程序主要用于将自身导出为 MEF 组件部件并实例化 QuickInfo 控制器。 因为它是 MEF 组件部件,因此可以导入其他 MEF 组件部件。

实现 QuickInfo 控制器提供程序

  1. 声明一个名为TestQuickInfoControllerProvider实现IIntellisenseControllerProvider的类,并使用“ToolTip QuickInfo Controller”和ContentTypeAttribute“text”导出它NameAttribute

    [Export(typeof(IIntellisenseControllerProvider))]
    [Name("ToolTip QuickInfo Controller")]
    [ContentType("text")]
    internal class TestQuickInfoControllerProvider : IIntellisenseControllerProvider
    
  2. IQuickInfoBroker 作为属性导入。

    [Import]
    internal IQuickInfoBroker QuickInfoBroker { get; set; }
    
  3. TryCreateIntellisenseController通过实例化 QuickInfo 控制器来实现该方法。

    public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList<ITextBuffer> subjectBuffers)
    {
        return new TestQuickInfoController(textView, subjectBuffers, this);
    }
    

生成并测试代码

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

生成和测试 QuickInfoTest 解决方案

  1. 生成解决方案。

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

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

  4. 将指针移到“add”的其中一个匹配项上。 应显示方法的 add 签名和说明。