Django Editor in VS 2010 – Part 4 (Quick Info Controller)
Look here for complete source
Welcome to the wonders of Intellisense. You can do so many things with intellisense and with the new MEF based architecture all of the wonders are actually acheivable by mere mortals without stretching your R&D budget. OK almost all. Sometimes you still have to resort to the old ugly COM based stuff, but the promise is out it's gonna get better in the final release.
QuickInfo is only one and, probably one of the simplest, of the many features available under Intellisense umbrella. The code I will present your here has nothing of the scary COM. Still, as with any of the Intellisense it has more moving parts than a classifier and/or tagger. The reason is pretty obvious - with taggers/classifiers you have only one dimension to worry about - the text itself. If the text is the same, the set of tags/classification types will not change either.
With Intellisense this is no longer true. The behavior of Intellisense, and QuickInfo in particular depends on the content as well as on what the user is doing - where is the caret, how did it move, where is the mouse cursor, etc. Because of this in addition to the Source supplying the content for the QuickInfo you have to deal with the Controller, which controls the QuickInfo session based on what the user is doing with mouse and keyboard.
The plumbing necessary for both the Source and the Controller is very similar to what you've seen in previous posts - there is a provider to be exported and the provider generates the actual class performing the function.
Here is my controller provider:
В В В [Export(typeof(IIntellisenseControllerProvider))] В В В [Name("NDjango Completion Controller")] В В В [Order] В В В [ContentType(Constants.NDJANGO)] В В В internal class CompletionControllerProvider : IIntellisenseControllerProvider В В В { В В В В В В В [Import(typeof(IQuickInfoBrokerMapService))] В В В В В В В private IQuickInfoBrokerMapService brokerMapService { get; set; } В В В В В В В [Import] В В В В В В В internal INodeProviderBroker nodeProviderBroker { get; set; } В В В В В В В public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList<ITextBuffer> subjectBuffers, IEnvironment context) В В В В В В В { В В В В В В В В В В В bool brokerCreated = false; В В В В В В В В В В В foreach (ITextBuffer subjectBuffer in subjectBuffers) В В В В В В В В В В В { В В В В В В В В В В В В В В В if (nodeProviderBroker.IsNDjango(subjectBuffer)) В В В В В В В В В В В В В В В В В В В brokerCreated |= (brokerMapService.GetBrokerForTextView(textView, subjectBuffer) != null); В В В В В В В В В В В } В В В В В В В В В В В // There may not be a broker for any of the subject buffers for this text view.В This can happen if there are no providers available. В В В В В В В В В В В if (brokerCreated) В В В В В В В В В В В { В В В В В В В В В В В В В В В return new Controller(nodeProviderBroker, subjectBuffers, textView, brokerMapService); В В В В В В В В В В В } В В В В В В В В В В В return null; В В В В В В В } В В В }
It looks very much like a classifier provider or a tagger provider. The only noticeable difference is that tagger provider and classifier provider operate on text buffers (ITextBuffer), because they do not really care about mouse cursor movements and keyboard - only about buffer content. In here we have to worry about them, and the only way to do that is to deal with the text view (ITextView) and a text view can have multiple buffers it is showing. The rest of the code is pretty much the same. Similarly to the other providers,В my QuickInfo controller providerВ tries to create a new QuickInfo controller for every django buffer. Here is the code for the QuickInfo controller:
class Controller : IIntellisenseController { private IList subjectBuffers; private ITextView textView; private IQuickInfoBrokerMapService brokerMapService; private IQuickInfoSession activeSession; private INodeProviderBroker nodeProviderBroker; public Controller(INodeProviderBroker nodeProviderBroker, IList subjectBuffers, ITextView textView, IQuickInfoBrokerMapService brokerMapService) { this.nodeProviderBroker = nodeProviderBroker; this.subjectBuffers = subjectBuffers; this.textView = textView; this.brokerMapService = brokerMapService; textView.MouseHover += new EventHandler(textView_MouseHover); } void textView_MouseHover(object sender, MouseHoverEventArgs e) { if (activeSession != null) activeSession.Dismiss(); SnapshotPoint? point = e.TextPosition.GetPoint( textBuffer => ( subjectBuffers.Contains(textBuffer) && nodeProviderBroker.IsNDjango(textBuffer) && brokerMapService.GetBrokerForTextView(textView, textBuffer) != null ) ,PositionAffinity.Predecessor); if (point.HasValue) { NodeProvider nodeProvider = nodeProviderBroker.GetNodeProvider(point.Value.Snapshot.TextBuffer); List quickInfoNodes = nodeProvider.GetNodes(point.Value); if (quickInfoNodes != null) { // the invocation occurred in a subject buffer of interest to us IQuickInfoBroker broker = brokerMapService.GetBrokerForTextView(textView, point.Value.Snapshot.TextBuffer); ITrackingPoint triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position, PointTrackingMode.Positive); activeSession = broker.CreateQuickInfoSession(triggerPoint, true); activeSession.Properties.AddProperty(typeof(SourceProvider), quickInfoNodes); activeSession.Start(); } } } public void ConnectSubjectBuffer(ITextBuffer subjectBuffer) { } public void Detach(ITextView textView) { } public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer) { } }
It is the responsibility of the controller toВ initiate the quick info session when the moment is right. For the NDjango designer IВ want to trigger the quick info session with the mouse hover event.В So in the constructor I subscribe to the MouseHover event of the text view.
When this event is fired I am trying to acquire a point in the text buffer based on the current mouse position. I only want to consider points which satisfy the condition spelled out in the code. If a point (and a corresponding buffer) has been located, I request a node provider for the buffer and get a list of nodes from the provider based on the point. If the list is not empty, I initiate a new QuickInfo session. The list is placed on the session to be retrieved by the QuickInfo source when the content of the tooltip is requested.
So this is how the NDjango editor creates aВ QuickInfo session.В So far the session even when triggered will not show anything. In the next post I willВ cover thisВ with the code for NDjango editor Quick InfoВ Source