연습: QuickInfo 도구 설명 표시

QuickInfo는 사용자가 메서드 이름 위로 포인터를 이동할 때 메서드 서명 및 설명을 표시하는 IntelliSense 기능입니다. QuickInfo 설명을 제공할 식별자를 정의한 다음 콘텐츠를 표시할 도구 설명을 만들어 QuickInfo와 같은 언어 기반 기능을 구현할 수 있습니다. 언어 서비스의 컨텍스트에서 QuickInfo를 정의하거나 사용자 고유의 파일 이름 확장명 및 콘텐츠 형식을 정의하고 해당 형식에 대한 QuickInfo를 표시하거나 기존 콘텐츠 형식(예: “텍스트”)에 대해 QuickInfo를 표시할 수 있습니다. 이 연습에서는 “텍스트” 콘텐츠 형식에 대해 QuickInfo를 표시하는 방법을 보여 줍니다.

이 연습의 QuickInfo 예제는 사용자가 메서드 이름 위로 포인터를 이동할 때 도구 설명을 표시합니다. 이 디자인을 사용하려면 다음 네 가지 인터페이스를 구현해야 합니다.

  • 원본 인터페이스

  • 원본 공급자 인터페이스

  • 컨트롤러 인터페이스

  • 컨트롤러 공급자 인터페이스

    소스 및 컨트롤러 공급자는 MEF(Managed Extensibility Framework) 구성 요소 부분이며 소스 및 컨트롤러 클래스를 내보내고 도구 설명 텍스트 버퍼를 생성하는 ITextBufferFactoryService 및 QuickInfo 세션을 트리거하는 IQuickInfoBroker와 같은 서비스 및 브로커 가져오기를 담당합니다.

    이 예제에서 QuickInfo 원본은 하드 코드된 메서드 이름 및 설명 목록을 사용하지만 전체 구현에서는 언어 서비스 및 언어 설명서에서 해당 콘텐츠를 제공해야 합니다.

MEF 프로젝트 만들기

MEF 프로젝트를 만들려면

  1. C# VSIX 프로젝트를 만듭니다. (새 프로젝트 대화 상자에서 Visual C#/확장성, VSIX 프로젝트를 차례로 선택합니다.) 솔루션 이름을 QuickInfoTest로 지정합니다.

  2. 프로젝트에 편집기 분류자 항목 템플릿을 추가합니다. 자세한 내용은 편집기 항목 템플릿을 사용하여 확장 만들기를 참조하세요.

  3. 기존 클래스 파일을 삭제합니다.

QuickInfo 원본 구현

QuickInfo 원본은 식별자 집합 및 해당 설명을 수집하고 식별자 중 하나가 발견되면 도구 설명 텍스트 버퍼에 콘텐츠를 추가하는 역할을 합니다. 이 예제에서는 식별자와 해당 설명이 원본 생성자에 추가됩니다.

QuickInfo 원본을 구현하려면

  1. 클래스 파일을 추가하고 이름을 TestQuickInfoSource로 지정합니다.

  2. Microsoft.VisualStudio.Language.IntelliSense에 대한 참조를 추가합니다.

  3. 다음 가져오기를 추가합니다.

    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. IQuickInfoSourceIDisposable을 구현하므로 Dispose() 메서드도 구현해야 합니다.

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

QuickInfo 원본 공급자 구현

QuickInfo 원본 공급자는 주로 MEF 구성 요소 부분으로 자신을 내보내고 QuickInfo 소스를 인스턴스화하는 역할을 합니다. MEF 구성 요소 부분이기 때문에 다른 MEF 구성 요소 부분을 가져올 수 있습니다.

QuickInfo 원본 공급자를 구현하려면

  1. IQuickInfoSourceProvider를 구현하는 이름이 TestQuickInfoSourceProvider인 QuickInfo 원본 공급자를 선언하고 “ToolTip QuickInfo Source”의 NameAttribute, Before="default"의 OrderAttribute, “text”의 ContentTypeAttribute를 사용하여 내보냅니다.

    [Export(typeof(IQuickInfoSourceProvider))]
    [Name("ToolTip QuickInfo Source")]
    [Order(Before = "Default Quick Info Presenter")]
    [ContentType("text")]
    internal class TestQuickInfoSourceProvider : IQuickInfoSourceProvider
    
  2. TestQuickInfoSourceProvider의 속성으로 두 개의 편집기 서비스(ITextStructureNavigatorSelectorServiceITextBufferFactoryService)를 가져옵니다.

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
    [Import]
    internal ITextBufferFactoryService TextBufferFactoryService { get; set; }
    
  3. TestQuickInfoSource를 반환하려면 TryCreateQuickInfoSource를 구현하세요.

    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. IIntellisenseControllerProvider를 구현하는 TestQuickInfoControllerProvider라는 클래스를 선언하고 “ToolTip QuickInfo Controller”의 NameAttribute 및 “텍스트”의 ContentTypeAttribute를 사용하여 내보냅니다.

    [Export(typeof(IIntellisenseControllerProvider))]
    [Name("ToolTip QuickInfo Controller")]
    [ContentType("text")]
    internal class TestQuickInfoControllerProvider : IIntellisenseControllerProvider
    
  2. IQuickInfoBroker를 속성으로 가져옵니다.

    [Import]
    internal IQuickInfoBroker QuickInfoBroker { get; set; }
    
  3. QuickInfo 컨트롤러를 인스턴스화하여 TryCreateIntellisenseController 메서드를 구현합니다.

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

코드 빌드 및 테스트

이 코드를 테스트하려면 QuickInfoTest 솔루션을 빌드하고 실험적 인스턴스에서 실행합니다.

QuickInfoTest 솔루션을 빌드하고 테스트하려면

  1. 솔루션을 빌드합니다.

  2. 디버거에서 이 프로젝트를 실행하면 Visual Studio의 두 번째 인스턴스가 시작됩니다.

  3. 텍스트 파일을 만들고 “추가” 및 “빼기”라는 단어가 포함된 텍스트를 입력합니다.

  4. 포인터를 “추가” 발생 중 하나로 이동합니다. add 메서드의 서명과 설명이 표시되어야 합니다.