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(); } } } |