File: IntelliSense\QuickInfo\IntellisenseQuickInfoBuilder.cs
Web Access
Project: ..\..\..\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Utilities;
using CodeAnalysisQuickInfoItem = Microsoft.CodeAnalysis.QuickInfo.QuickInfoItem;
using IntellisenseQuickInfoItem = Microsoft.VisualStudio.Language.Intellisense.QuickInfoItem;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo
{
    internal static class IntellisenseQuickInfoBuilder
    {
        private static async Task<ContainerElement> BuildInteractiveContentAsync(
            CodeAnalysisQuickInfoItem quickInfoItem,
            IntellisenseQuickInfoBuilderContext? context,
            CancellationToken cancellationToken)
        {
            // Build the first line of QuickInfo item, the images and the Description section should be on the first line with Wrapped style
            var glyphs = quickInfoItem.Tags.GetGlyphs();
            var symbolGlyph = glyphs.FirstOrDefault(g => g != Glyph.CompletionWarning);
            var warningGlyph = glyphs.FirstOrDefault(g => g == Glyph.CompletionWarning);
            var firstLineElements = new List<object>();
            if (symbolGlyph != Glyph.None)
            {
                firstLineElements.Add(new ImageElement(symbolGlyph.GetImageId()));
            }
 
            if (warningGlyph != Glyph.None)
            {
                firstLineElements.Add(new ImageElement(warningGlyph.GetImageId()));
            }
 
            var elements = new List<object>();
            var descSection = quickInfoItem.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.Description);
            if (descSection != null)
            {
                var isFirstElement = true;
                foreach (var element in Helpers.BuildInteractiveTextElements(descSection.TaggedParts, context))
                {
                    if (isFirstElement)
                    {
                        isFirstElement = false;
                        firstLineElements.Add(element);
                    }
                    else
                    {
                        // If the description section contains multiple paragraphs, the second and additional paragraphs
                        // are not wrapped in firstLineElements (they are normal paragraphs).
                        elements.Add(element);
                    }
                }
            }
 
            elements.Insert(0, new ContainerElement(ContainerElementStyle.Wrapped, firstLineElements));
 
            var documentationCommentSection = quickInfoItem.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.DocumentationComments);
            if (documentationCommentSection != null)
            {
                var isFirstElement = true;
                foreach (var element in Helpers.BuildInteractiveTextElements(documentationCommentSection.TaggedParts, context))
                {
                    if (isFirstElement)
                    {
                        isFirstElement = false;
 
                        // Stack the first paragraph of the documentation comments with the last line of the description
                        // to avoid vertical padding between the two.
                        var lastElement = elements[elements.Count - 1];
                        elements[elements.Count - 1] = new ContainerElement(
                            ContainerElementStyle.Stacked,
                            lastElement,
                            element);
                    }
                    else
                    {
                        elements.Add(element);
                    }
                }
            }
 
            // Add the remaining sections as Stacked style
            elements.AddRange(
                quickInfoItem.Sections.Where(s => s.Kind is not QuickInfoSectionKinds.Description and not QuickInfoSectionKinds.DocumentationComments)
                                      .SelectMany(s => Helpers.BuildInteractiveTextElements(s.TaggedParts, context)));
 
            // build text for RelatedSpan
            if (quickInfoItem.RelatedSpans.Any() && context != null)
            {
                var document = context.Document;
                var textRuns = new List<ClassifiedTextRun>();
                var spanSeparatorNeededBefore = false;
                foreach (var span in quickInfoItem.RelatedSpans)
                {
                    // We don't present additive-spans (like static/reassigned-variable) any differently, so strip them
                    // out of the classifications we get back.
                    var classifiedSpans = await ClassifierHelper.GetClassifiedSpansAsync(
                        document, span, context.ClassificationOptions, includeAdditiveSpans: false, cancellationToken).ConfigureAwait(false);
 
                    var tabSize = context.LineFormattingOptions.TabSize;
 
                    var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
                    var spans = IndentationHelper.GetSpansWithAlignedIndentation(text, classifiedSpans, tabSize);
                    var textRunsOfSpan = spans.Select(s => new ClassifiedTextRun(s.ClassificationType, text.GetSubText(s.TextSpan).ToString(), ClassifiedTextRunStyle.UseClassificationFont)).ToList();
                    if (textRunsOfSpan.Count > 0)
                    {
                        if (spanSeparatorNeededBefore)
                        {
                            textRuns.Add(new ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, "\r\n", ClassifiedTextRunStyle.UseClassificationFont));
                        }
 
                        textRuns.AddRange(textRunsOfSpan);
                        spanSeparatorNeededBefore = true;
                    }
                }
 
                if (textRuns.Any())
                {
                    elements.Add(new ClassifiedTextElement(textRuns));
                }
            }
 
            return new ContainerElement(
                                ContainerElementStyle.Stacked | ContainerElementStyle.VerticalPadding,
                                elements);
        }
 
        internal static async Task<IntellisenseQuickInfoItem> BuildItemAsync(
            ITrackingSpan trackingSpan,
            CodeAnalysisQuickInfoItem quickInfoItem,
            Document document,
            ClassificationOptions classificationOptions,
            LineFormattingOptions lineFormattingOptions,
            IThreadingContext threadingContext,
            IUIThreadOperationExecutor operationExecutor,
            IAsynchronousOperationListener asyncListener,
            Lazy<IStreamingFindUsagesPresenter> streamingPresenter,
            CancellationToken cancellationToken)
        {
            var context = new IntellisenseQuickInfoBuilderContext(document, classificationOptions, lineFormattingOptions, threadingContext, operationExecutor, asyncListener, streamingPresenter);
            var content = await BuildInteractiveContentAsync(quickInfoItem, context, cancellationToken).ConfigureAwait(false);
 
            return new IntellisenseQuickInfoItem(trackingSpan, content);
        }
 
        /// <summary>
        /// Builds the classified hover content without navigation actions and requiring
        /// an instance of <see cref="IStreamingFindUsagesPresenter"/>
        /// TODO - This can be removed once LSP supports colorization in markupcontent
        /// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138
        /// </summary>
        internal static Task<ContainerElement> BuildContentWithoutNavigationActionsAsync(
            CodeAnalysisQuickInfoItem quickInfoItem,
            IntellisenseQuickInfoBuilderContext? context,
            CancellationToken cancellationToken)
        {
            return BuildInteractiveContentAsync(quickInfoItem, context, cancellationToken);
        }
    }
}