Beispiel #1
0
        private void ComputeApplicableToSpan(IEnumerable <ITrackingSpan> applicableToSpans)
        {
            // Requires UI thread for access to BufferGraph.
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            ITrackingSpan newApplicableToSpan = Volatile.Read(ref this.applicableToSpan);

            foreach (var result in applicableToSpans)
            {
                var applicableToSpan = result;

                if (applicableToSpan != null)
                {
                    SnapshotSpan subjectAppSnapSpan = applicableToSpan.GetSpan(applicableToSpan.TextBuffer.CurrentSnapshot);

                    var surfaceAppSpans = this.TextView.BufferGraph.MapUpToBuffer(
                        subjectAppSnapSpan,
                        applicableToSpan.TrackingMode,
                        this.TextView.TextBuffer);

                    if (surfaceAppSpans.Count >= 1)
                    {
                        applicableToSpan = surfaceAppSpans[0].Snapshot.CreateTrackingSpan(surfaceAppSpans[0], applicableToSpan.TrackingMode);

                        newApplicableToSpan = IntellisenseUtilities.GetEncapsulatingSpan(
                            this.TextView,
                            newApplicableToSpan,
                            applicableToSpan);
                    }
                }
            }

            Volatile.Write(ref this.applicableToSpan, newApplicableToSpan);
        }
Beispiel #2
0
        private ITrackingPoint PointToViewBuffer(ITextView textView, ITrackingPoint trackingPoint)
        {
            // Requires UI thread for BufferGraph.
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            if ((trackingPoint == null) || (textView.TextBuffer == trackingPoint.TextBuffer))
            {
                return(trackingPoint);
            }

            var targetSnapshot  = textView.TextSnapshot;
            var point           = trackingPoint.GetPoint(trackingPoint.TextBuffer.CurrentSnapshot);
            var viewBufferPoint = textView.BufferGraph.MapUpToSnapshot(
                point,
                trackingPoint.TrackingMode,
                PositionAffinity.Predecessor,
                targetSnapshot);

            if (viewBufferPoint == null)
            {
                return(null);
            }

            return(targetSnapshot.CreateTrackingPoint(
                       viewBufferPoint.Value.Position,
                       trackingPoint.TrackingMode));
        }
Beispiel #3
0
        private Collection <ITextBuffer> GetBuffersForTriggerPoint()
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.JoinableTaskContext);

            return(this.TextView.BufferGraph.GetTextBuffers(
                       buffer => this.GetTriggerPoint(buffer.CurrentSnapshot) != null));
        }
Beispiel #4
0
        private void TransitionTo(QuickInfoSessionState newState)
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            var  oldState = this.State;
            bool isValid  = false;

            switch (newState)
            {
            case QuickInfoSessionState.Created:
                isValid = false;
                break;

            case QuickInfoSessionState.Calculating:
                isValid = oldState == QuickInfoSessionState.Created || oldState == QuickInfoSessionState.Visible;
                break;

            case QuickInfoSessionState.Dismissed:
                isValid = oldState == QuickInfoSessionState.Visible ||
                          oldState == QuickInfoSessionState.Calculating;
                break;

            case QuickInfoSessionState.Visible:
                isValid = oldState == QuickInfoSessionState.Calculating;
                break;
            }

            if (!isValid)
            {
                throw new InvalidOperationException($"Invalid {nameof(IAsyncQuickInfoSession)} state transition from {oldState} to {newState}");
            }

            Volatile.Write(ref this.uiThreadWritableState, (int)newState);
            this.StateChanged?.Invoke(this, new QuickInfoSessionStateChangedEventArgs(oldState, newState));
        }
Beispiel #5
0
        private async Task <IList <Exception> > ComputeContentAndUpdateAsync(QuickInfoSessionState initialState, bool allowUpdate, CancellationToken cancellationToken)
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.JoinableTaskContext);

            // Alert subscribers on the UI thread.
            this.TransitionTo(QuickInfoSessionState.Calculating, allowUpdate);

            cancellationToken.ThrowIfCancellationRequested();

            var failures = new FrugalList <Exception>();

            // Find and create the sources. Sources cache is smart enough to
            // invalidate on content-type changed and free on view close.
            var sources = this.GetOrCreateSources(failures);

            // Compute quick info items. This method switches off the UI thread.
            // From here on out we're on an arbitrary thread.
            (IList <object> items, IList <ITrackingSpan> applicableToSpans)? results
                = await ComputeContentAsync(sources, failures, cancellationToken).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            // Update our content, or put the empty list if there is none.
            Volatile.Write(
                ref this.content,
                results != null ? ImmutableList.CreateRange(results.Value.items) : ImmutableList <object> .Empty);

            await StartUIThreadEpilogueAsync(initialState, results?.applicableToSpans, cancellationToken).ConfigureAwait(false);

            return(failures);
        }
Beispiel #6
0
        private void OnMouseHover(object sender, MouseHoverEventArgs e)
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            SnapshotPoint?surfaceHoverPointNullable = e.TextPosition.GetPoint(
                this.textView.TextViewModel.DataBuffer,
                PositionAffinity.Predecessor);

            // Does hover correspond to actual position in document or
            // is there already a session around that is valid?
            if (!surfaceHoverPointNullable.HasValue || this.IsSessionStillValid(surfaceHoverPointNullable.Value))
            {
                return;
            }

            // Cancel last queued quick info update, if there is one.
            CancelAndDisposeToken();

            this.cancellationTokenSource = new CancellationTokenSource();

            // Start quick info session async on the UI thread.
            this.joinableTaskContext.Factory.RunAsync(async delegate
            {
                await UpdateSessionStateAsync(surfaceHoverPointNullable.Value, this.cancellationTokenSource.Token);

                // Clean up the cancellation token source.
                Debug.Assert(this.joinableTaskContext.IsOnMainThread);
                this.cancellationTokenSource.Dispose();
                this.cancellationTokenSource = null;
            });
        }
Beispiel #7
0
        private IEnumerable <OrderedSource> CreateSources(ITextBuffer textBuffer)
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            int i = 0;

            foreach (var sourceProvider in this.orderedSourceProviders)
            {
                foreach (var contentType in sourceProvider.Metadata.ContentTypes)
                {
                    if (textBuffer.ContentType.IsOfType(contentType))
                    {
                        var source = this.guardedOperations.InstantiateExtension(
                            this,
                            sourceProvider,
                            provider => provider.TryCreateQuickInfoSource(textBuffer));

                        if (source != null)
                        {
                            yield return(new OrderedSource(i, source));
                        }
                    }
                }

                ++i;
            }
        }
Beispiel #8
0
        private void OnDismissed(object sender, EventArgs e)
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            this.joinableTaskContext.Factory.RunAsync(async delegate
            {
                await this.DismissAsync().ConfigureAwait(false);
            });
        }
Beispiel #9
0
        private void ComputeApplicableToSpan(IEnumerable <ITrackingSpan> applicableToSpans)
        {
            // Requires UI thread for access to BufferGraph.
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.JoinableTaskContext);

            ITrackingSpan newApplicableToSpan = Volatile.Read(ref this.applicableToSpan);

            foreach (var result in applicableToSpans)
            {
                var applicableToSpan = result;

                if (applicableToSpan != null)
                {
                    SnapshotSpan subjectAppSnapSpan = applicableToSpan.GetSpan(applicableToSpan.TextBuffer.CurrentSnapshot);

                    var surfaceAppSpans = this.TextView.BufferGraph.MapUpToBuffer(
                        subjectAppSnapSpan,
                        applicableToSpan.TrackingMode,
                        this.TextView.TextBuffer);

                    if (surfaceAppSpans.Count >= 1)
                    {
                        applicableToSpan = surfaceAppSpans[0].Snapshot.CreateTrackingSpan(surfaceAppSpans[0], applicableToSpan.TrackingMode);

                        newApplicableToSpan = IntellisenseUtilities.GetEncapsulatingSpan(
                            this.TextView,
                            newApplicableToSpan,
                            applicableToSpan);
                    }
                }
            }

            // Scope the applicableToSpan down to just the current line to ensure that interactions
            // with interactive controls such as lightbulb are not impeded by the tip appearing
            // far away from the mouse.
            if (newApplicableToSpan != null)
            {
                var currentSnapshot        = newApplicableToSpan.TextBuffer.CurrentSnapshot;
                var spanStart              = newApplicableToSpan.GetStartPoint(currentSnapshot);
                var spanEnd                = newApplicableToSpan.GetEndPoint(currentSnapshot);
                var triggerPointLine       = this.triggerPoint.GetPoint(currentSnapshot).GetContainingLine();
                var triggerPointLineExtent = triggerPointLine.Extent;
                var newStart               = Math.Max(triggerPointLineExtent.Start, spanStart);
                var newEnd = Math.Min(triggerPointLineExtent.End, spanEnd);
                if (newStart <= newEnd)
                {
                    newApplicableToSpan = currentSnapshot.CreateTrackingSpan(Span.FromBounds(newStart, newEnd), SpanTrackingMode.EdgeInclusive);
                }
            }

            Volatile.Write(ref this.applicableToSpan, newApplicableToSpan);
        }
Beispiel #10
0
        internal QuickInfoController(
            IAsyncQuickInfoBroker quickInfoBroker,
            JoinableTaskContext joinableTaskContext,
            ITextView textView)
        {
            this.quickInfoBroker     = quickInfoBroker ?? throw new ArgumentNullException(nameof(quickInfoBroker));
            this.joinableTaskContext = joinableTaskContext ?? throw new ArgumentNullException(nameof(joinableTaskContext));
            this.textView            = textView ?? throw new ArgumentNullException(nameof(textView));

            IntellisenseUtilities.ThrowIfNotOnMainThread(joinableTaskContext);

            this.textView.MouseHover += this.OnMouseHover;
            this.textView.Closed     += this.OnTextViewClosed;
        }
#pragma warning restore 618

        // Listens for the session being dismissed so that we can remove it from the view's property bag.
        private void OnStateChanged(object sender, QuickInfoSessionStateChangedEventArgs e)
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            if (e.NewState == QuickInfoSessionState.Dismissed)
            {
                if (sender is AsyncQuickInfoSession session)
                {
                    session.TextView.Properties.RemoveProperty(typeof(AsyncQuickInfoSession));
                    session.StateChanged -= this.OnStateChanged;
                    return;
                }

                Debug.Fail("Unexpected sender type");
            }
        }
Beispiel #12
0
        private bool ContentRequestsKeepOpen()
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            if (this.HasInteractiveContent)
            {
                foreach (var content in this.Content)
                {
                    if ((content is IInteractiveQuickInfoContent interactiveContent) &&
                        ((interactiveContent.KeepQuickInfoOpen || interactiveContent.IsMouseOverAggregated)))
                    {
                        return(true);
                    }
                }
            }

            return(false);
        }
Beispiel #13
0
        // Internal for unit test.
        internal void OnTextViewClosed(object sender, EventArgs e)
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            this.textView.Closed -= this.OnTextViewClosed;

            // Cancel any calculating sessions and dispose the token.
            this.CancelAndDisposeToken();

            // Terminate any open quick info sessions.
            this.joinableTaskContext.Factory.RunAsync(async delegate
            {
                var session = this.quickInfoBroker.GetSession(this.textView);
                if (session != null)
                {
                    await session.DismissAsync();
                }
            });

            this.textView.MouseHover -= this.OnMouseHover;
        }
Beispiel #14
0
        private void CreateAndStartPresenter()
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);
            Debug.Assert(this.uiThreadOnlyPresenter == null);

            // Configure presenter behavior.
            var parameters = new ToolTipParameters(
                this.Options.HasFlag(QuickInfoSessionOptions.TrackMouse),
                keepOpenFunc: this.ContentRequestsKeepOpen);

            // Create and show presenter.
            this.uiThreadOnlyPresenter            = this.toolTipService.CreatePresenter(this.TextView, parameters);
            this.uiThreadOnlyPresenter.Dismissed += this.OnDismissed;
            this.uiThreadOnlyPresenter.StartOrUpdate(this.ApplicableToSpan, this.Content);

            // Ensure that the presenter didn't dismiss the session.
            if (this.State != QuickInfoSessionState.Dismissed)
            {
                // Update state and alert subscribers on the UI thread.
                this.TransitionTo(QuickInfoSessionState.Visible);
            }
        }
Beispiel #15
0
#pragma warning disable 618
        // Bug #512117: Remove compatibility shims for 2nd gen. Quick Info APIs.
        // ILegacyQuickInfoMetadata should be removed and switched out for IOrderableContentTypeMetadata.
        private static IReadOnlyCollection <OrderedSource> CreateSources(
            JoinableTaskContext joinableTaskContext,
            IEnumerable <Lazy <IAsyncQuickInfoSourceProvider, IOrderableContentTypeMetadata> > orderedSourceProviders,
            ITextBuffer textBuffer,
            IList <Exception> failures)
        {
#pragma warning restore 618
            IntellisenseUtilities.ThrowIfNotOnMainThread(joinableTaskContext);

            int i           = 0;
            var sourcesList = new List <OrderedSource>();

            foreach (var sourceProvider in orderedSourceProviders)
            {
                foreach (var contentType in sourceProvider.Metadata.ContentTypes)
                {
                    if (textBuffer.ContentType.IsOfType(contentType))
                    {
                        try
                        {
                            var source = sourceProvider.Value.TryCreateQuickInfoSource(textBuffer);
                            if (source != null)
                            {
                                sourcesList.Add(new OrderedSource(i, source));
                            }
                        }
                        catch (Exception ex)
                        {
                            failures.Add(ex);
                        }
                    }
                }

                ++i;
            }

            return(sourcesList);
        }
        private void ComputePresentationSpan()
        {
            if ((_session == null) || (_session.IsDismissed))
            {
                return;
            }

            // Save off the old presentation span so we can tell if it changes.
            ITrackingSpan oldPresentationSpan = _presentationSpan;

            if (_session.Signatures.Count > 0)
            {
                _presentationSpan = null;
                foreach (ISignature signature in _session.Signatures)
                {
                    _presentationSpan = IntellisenseUtilities.GetEncapsulatingSpan(_session.TextView, _presentationSpan, signature.ApplicableToSpan);
                }
            }

            // For now, we're only interested in letting consumers know about presentation span changes if the START of our
            // presentation span changed.  That's where our display position gets calculated.
            // TODO : Develop a better story around presentation span changes in popup presenters.  If the presentation location
            //        (coordinates) doesn't really change, we shouldn't have a flicker...
            ITextSnapshot viewSnapshot = _session.TextView.TextSnapshot;
            SnapshotSpan  newSpan      = _presentationSpan.GetSpan(viewSnapshot);
            SnapshotSpan? oldSpan      = null;

            if (oldPresentationSpan != null)
            {
                oldSpan = oldPresentationSpan.GetSpan(viewSnapshot);
            }
            if (!oldSpan.HasValue || (newSpan.Start != oldSpan.Value.Start))
            {
                this.FirePresentationSpanChanged();
            }
        }
Beispiel #17
0
        private async Task <IList <Exception> > ComputeContentAndStartPresenterAsync(CancellationToken cancellationToken)
        {
            IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext);

            // Read current state.
            var originalState = this.State;

            (IList <object> items, IList <ITrackingSpan> applicableToSpans, IList <Exception> failures)? results = null;

            // Alert subscribers on the UI thread.
            this.TransitionTo(QuickInfoSessionState.Calculating);

            cancellationToken.ThrowIfCancellationRequested();

            // Find and create the sources.  Sources cache is smart enough to
            // invalidate on content-type changed and free on view close.
            var sources = IntellisenseSourceCache.GetSources(
                this.TextView,
                GetBuffersForTriggerPoint().ToList(),
                this.CreateSources);

            // Compute quick info items. This method switches off the UI thread.
            // From here on out we're on an arbitrary thread.
            results = await ComputeContentAsync(sources, cancellationToken).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            // Update our content, or put the empty list if there is none.
            Volatile.Write(
                ref this.content,
                results != null ? ImmutableList.CreateRange(results.Value.items) : ImmutableList <object> .Empty);

            await StartUIThreadEpilogueAsync(originalState, results?.applicableToSpans, cancellationToken).ConfigureAwait(false);

            return(results?.failures);
        }
Beispiel #18
0
        public async Task <QuickInfoItem> GetQuickInfoItemAsync(
            IAsyncQuickInfoSession session,
            CancellationToken cancellationToken)
        {
            if (this.disposed)
            {
                throw new ObjectDisposedException("SquiggleQuickInfoSource");
            }

            if (session.TextView.TextBuffer != this.textBuffer)
            {
                return(null);
            }

            // TagAggregators must be used exclusively on the UI thread.
            await this.componentContext.JoinableTaskContext.Factory.SwitchToMainThreadAsync();

            ITrackingSpan applicableToSpan           = null;
            var           quickInfoContent           = new FrugalList <object>();
            ITagAggregator <IErrorTag> tagAggregator = GetTagAggregator(session.TextView);

            Debug.Assert(tagAggregator != null, "Couldn't create a tag aggregator for error tags");
            if (tagAggregator != null)
            {
                // Put together the span over which tags are to be discovered.  This will be the zero-length span at the trigger
                // point of the session.
                SnapshotPoint?subjectTriggerPoint = session.GetTriggerPoint(this.textBuffer.CurrentSnapshot);
                if (!subjectTriggerPoint.HasValue)
                {
                    Debug.Fail("The squiggle QuickInfo source is being called when it shouldn't be.");
                    return(null);
                }

                ITextSnapshot currentSnapshot = subjectTriggerPoint.Value.Snapshot;
                var           querySpan       = new SnapshotSpan(subjectTriggerPoint.Value, 0);

                // Ask for all of the error tags that intersect our query span.  We'll get back a list of mapping tag spans.
                // The first of these is what we'll use for our quick info.
                IEnumerable <IMappingTagSpan <IErrorTag> > tags = tagAggregator.GetTags(querySpan);
                ITrackingSpan appToSpan = null;
                foreach (MappingTagSpan <IErrorTag> tag in tags)
                {
                    NormalizedSnapshotSpanCollection applicableToSpans = tag.Span.GetSpans(currentSnapshot);
                    if ((applicableToSpans.Count > 0) && (tag.Tag.ToolTipContent != null))
                    {
                        // We've found a error tag at the right location with a tag span that maps to our subject buffer.
                        // Return the applicability span as well as the tooltip content.
                        appToSpan = IntellisenseUtilities.GetEncapsulatingSpan(
                            session.TextView,
                            appToSpan,
                            currentSnapshot.CreateTrackingSpan(
                                applicableToSpans[0].Span,
                                SpanTrackingMode.EdgeInclusive));

                        quickInfoContent.Add(tag.Tag.ToolTipContent);
                    }
                }

                if (quickInfoContent.Count > 0)
                {
                    applicableToSpan = appToSpan;
                    return(new QuickInfoItem(
                               applicableToSpan,
                               new ContainerElement(
                                   ContainerElementStyle.Stacked | ContainerElementStyle.VerticalPadding,
                                   quickInfoContent)));
                }
            }

            return(null);
        }