Sam's Blog

25 Nov

Tool tips for squiggles and URLs in Visual Studio 2010

I’ve been experimenting with Visual Studio’s new extensibility model, and ran into a problem with squiggles. It seems that despite having an internal IQuickInfoSource for the SquiggleTag and IUrlTag, it’s not hooked up to a IIntellisenseController so it never appears. Here is a general implementation for an SquiggleQuickInfoIntellisenseController and UrlQuickInfoIntellisenseController that will provide QuickInfo for squiggle tags and URLs provided by custom implementations of ITagger<SquiggleTag>. The controller itself is a generic that supports any ITag that comes with a IQuickInfoSource.

This code is very heavily based on an existing internal IntelliSense controller in Visual Studio 2010 that’s used for a different tag type.

First we have the IIntellisenseControllerProvider for squiggles and URLs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
namespace CustomLanguageServices.Text
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Language.Intellisense;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Tagging;
    using Microsoft.VisualStudio.Utilities;
 
    [Export(typeof(IIntellisenseControllerProvider))]
    [ContentType("text")]
    public sealed class SquiggleQuickInfoControllerProvider : IIntellisenseControllerProvider
    {
        [Import]
        public IQuickInfoBroker QuickInfoBroker;
 
        [Import]
        public IViewTagAggregatorFactoryService TagAggregatorFactoryService;
 
        public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList<ITextBuffer> subjectBuffers)
        {
            Func<TagQuickInfoController<SquiggleTag>> creator =
                () =>
                {
                    var tagAggregator = TagAggregatorFactoryService.CreateTagAggregator<SquiggleTag>(textView);
                    var controller = new TagQuickInfoController<SquiggleTag>(QuickInfoBroker, textView, tagAggregator);
                    return controller;
                };
 
            return textView.Properties.GetOrCreateSingletonProperty(creator);
        }
    }
 
    [Export(typeof(IIntellisenseControllerProvider))]
    [ContentType("text")]
    public sealed class UrlQuickInfoControllerProvider : IIntellisenseControllerProvider
    {
        [Import]
        public IQuickInfoBroker QuickInfoBroker;
 
        [Import]
        public IViewTagAggregatorFactoryService TagAggregatorFactoryService;
 
        public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList<ITextBuffer> subjectBuffers)
        {
            Func<TagQuickInfoController<IUrlTag>> creator =
                () =>
                {
                    var tagAggregator = TagAggregatorFactoryService.CreateTagAggregator<IUrlTag>(textView);
                    var controller = new TagQuickInfoController<IUrlTag>(QuickInfoBroker, textView, tagAggregator);
                    return controller;
                };
 
            return textView.Properties.GetOrCreateSingletonProperty(creator);
        }
    }
}

And then we have the actual IIntellisenseController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
namespace CustomLanguageService.Text
{
    using Microsoft.VisualStudio.Language.Intellisense;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Tagging;
 
    public class TagQuickInfoController<T> : IIntellisenseController
        where T : ITag
    {
        private IQuickInfoBroker _quickInfoBroker;
        private ITextView _textView;
        private ITagAggregator<T> _tagAggregator;
        private ITextBuffer _surfaceBuffer;
        private IQuickInfoSession _session;
 
        public TagQuickInfoController(IQuickInfoBroker quickInfoBroker, ITextView textView, ITagAggregator<T> tagAggregator)
        {
            this._quickInfoBroker = quickInfoBroker;
            this._textView = textView;
            this._tagAggregator = tagAggregator;
 
            this._surfaceBuffer = textView.TextViewModel.DataBuffer;
            this._surfaceBuffer.Changed += OnSurfaceBuffer_Changed;
            this._textView.MouseHover += OnTextView_MouseHover;
            this._textView.Caret.PositionChanged += OnCaret_PositionChanged;
        }
 
        public void ConnectSubjectBuffer(ITextBuffer subjectBuffer)
        {
        }
 
        public void Detach(ITextView textView)
        {
            this._surfaceBuffer.Changed -= OnSurfaceBuffer_Changed;
            this._textView.MouseHover -= OnTextView_MouseHover;
            this._textView.Caret.PositionChanged -= OnCaret_PositionChanged;
            if (this._tagAggregator != null)
            {
                this._tagAggregator.Dispose();
                this._tagAggregator = null;
            }
        }
 
        public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer)
        {
        }
 
        internal void DismissSession()
        {
            if ((this._session != null) && !this._session.IsDismissed)
            {
                this._session.Dismiss();
                this._session = null;
            }
        }
 
        private bool EnsureSessionStillValid(SnapshotPoint point)
        {
            if (this._session != null)
            {
                if (this._session.IsDismissed)
                {
                    this._session = null;
                    return false;
                }
                if ((this._session.ApplicableToSpan.TextBuffer == point.Snapshot.TextBuffer) && this._session.ApplicableToSpan.GetSpan(point.Snapshot).IntersectsWith(new Span(point.Position, 0)))
                {
                    return true;
                }
                this._session.Dismiss();
                this._session = null;
            }
            return false;
        }
 
        private bool TryExtractQuickInfoFromMarkers(int position)
        {
            IMappingTagSpan<T> mappingTagSpan = null;
 
            foreach (IMappingTagSpan<T> span in this._tagAggregator.GetTags(new SnapshotSpan(this._textView.TextSnapshot, position, 1)))
            {
                if (span.Tag != null)
                    mappingTagSpan = span;
            }
 
            if (mappingTagSpan != null)
            {
                NormalizedSnapshotSpanCollection spans = mappingTagSpan.Span.GetSpans(this._textView.TextBuffer);
                if (spans.Count > 0)
                {
                    this.DismissSession();
                    SnapshotSpan span = spans[0];
                    ITrackingPoint triggerPoint = span.Snapshot.CreateTrackingPoint(span.Start.Position, PointTrackingMode.Positive);
                    this._session = this._quickInfoBroker.CreateQuickInfoSession(this._textView, triggerPoint, true);
                    this._session.Start();
                    return true;
                }
            }
 
            return false;
        }
 
        private void OnSurfaceBuffer_Changed(object sender, TextContentChangedEventArgs e)
        {
            this.DismissSession();
        }
 
        private void OnTextView_MouseHover(object sender, MouseHoverEventArgs e)
        {
            SnapshotPoint? nullable = e.TextPosition.GetPoint(this._surfaceBuffer, PositionAffinity.Successor);
            if (!nullable.HasValue)
                return;
 
            SnapshotPoint point = nullable.Value;
            if (this.EnsureSessionStillValid(point))
                return;
 
            if (this._tagAggregator != null)
            {
                this.TryExtractQuickInfoFromMarkers(e.Position);
            }
        }
 
        private void OnCaret_PositionChanged(object sender, CaretPositionChangedEventArgs e)
        {
            this.DismissSession();
        }
    }
}

Leave a Reply

© 2017 Sam's Blog | Entries (RSS) and Comments (RSS)

Your Index Web Directorywordpress logo