예제 #1
0
        private static async Task AddSemanticClassificationsAsync(
            Document document,
            TextSpan textSpan,
            IClassificationService classificationService,
            ArrayBuilder <ClassifiedSpan> classifiedSpans,
            CancellationToken cancellationToken)
        {
            var workspaceStatusService = document.Project.Solution.Workspace.Services.GetRequiredService <IWorkspaceStatusService>();

            // Importantly, we do not await/wait on the fullyLoadedStateTask.  We do not want to ever be waiting on work
            // that may end up touching the UI thread (As we can deadlock if GetTagsSynchronous waits on us).  Instead,
            // we only check if the Task is completed.  Prior to that we will assume we are still loading.  Once this
            // task is completed, we know that the WaitUntilFullyLoadedAsync call will have actually finished and we're
            // fully loaded.
            var isFullyLoadedTask = workspaceStatusService.IsFullyLoadedAsync(cancellationToken);
            var isFullyLoaded     = isFullyLoadedTask.IsCompleted && isFullyLoadedTask.GetAwaiter().GetResult();

            // If we're not fully loaded try to read from the cache instead so that classifications appear up to date.
            // New code will not be semantically classified, but will eventually when the project fully loads.
            if (await TryAddSemanticClassificationsFromCacheAsync(document, textSpan, classifiedSpans, isFullyLoaded, cancellationToken).ConfigureAwait(false))
            {
                return;
            }

            var options = ClassificationOptions.From(document.Project);
            await classificationService.AddSemanticClassificationsAsync(
                document, textSpan, options, classifiedSpans, cancellationToken).ConfigureAwait(false);
        }
예제 #2
0
        protected static async Task <ImmutableArray <ClassifiedSpan> > GetSemanticClassificationsAsync(Document document, TextSpan span)
        {
            var service = document.GetRequiredLanguageService <IClassificationService>();
            var options = ClassificationOptions.From(document.Project);

            using var _ = ArrayBuilder <ClassifiedSpan> .GetInstance(out var result);

            await service.AddSemanticClassificationsAsync(document, span, options, result, CancellationToken.None);

            return(result.ToImmutable());
        }
예제 #3
0
        public static async Task ProduceTagsAsync(
            TaggerContext <IClassificationTag> context,
            DocumentSnapshotSpan spanToTag,
            IClassificationService classificationService,
            ClassificationTypeMap typeMap,
            CancellationToken cancellationToken)
        {
            var document = spanToTag.Document;

            if (document == null)
            {
                return;
            }

            var options = ClassificationOptions.From(document.Project);

            // Don't block getting classifications on building the full compilation.  This may take a significant amount
            // of time and can cause a very latency sensitive operation (copying) to block the user while we wait on this
            // work to happen.
            //
            // It's also a better experience to get classifications to the user faster versus waiting a potentially
            // large amount of time waiting for all the compilation information to be built.  For example, we can
            // classify types that we've parsed in other files, or partially loaded from metadata, even if we're still
            // parsing/loading.  For cross language projects, this also produces semantic classifications more quickly
            // as we do not have to wait on skeletons to be built.

            document  = document.WithFrozenPartialSemantics(cancellationToken);
            spanToTag = new DocumentSnapshotSpan(document, spanToTag.SnapshotSpan);

            var classified = await TryClassifyContainingMemberSpanAsync(
                context, spanToTag, classificationService, typeMap, options, cancellationToken).ConfigureAwait(false);

            if (classified)
            {
                return;
            }

            // We weren't able to use our specialized codepaths for semantic classifying.
            // Fall back to classifying the full span that was asked for.
            await ClassifySpansAsync(
                context, spanToTag, classificationService, typeMap, options, cancellationToken).ConfigureAwait(false);
        }
예제 #4
0
        public static async Task <ValueTrackedItem?> TryCreateAsync(Document document, TextSpan textSpan, ISymbol symbol, ValueTrackedItem?parent = null, CancellationToken cancellationToken = default)
        {
            var        excerptService = document.Services.GetService <IDocumentExcerptService>();
            SourceText?sourceText     = null;
            ImmutableArray <ClassifiedSpan> classifiedSpans = default;

            if (excerptService != null)
            {
                var result = await excerptService.TryExcerptAsync(document, textSpan, ExcerptMode.SingleLine, cancellationToken).ConfigureAwait(false);

                if (result.HasValue)
                {
                    var value = result.Value;
                    sourceText = value.Content;
                }
            }

            if (sourceText is null)
            {
                var options      = ClassificationOptions.From(document.Project);
                var documentSpan = await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync(document, textSpan, options, cancellationToken).ConfigureAwait(false);

                var classificationResult = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(documentSpan, options, cancellationToken).ConfigureAwait(false);

                classifiedSpans = classificationResult.ClassifiedSpans;
                var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

                sourceText = await syntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false);
            }

            return(new ValueTrackedItem(
                       SymbolKey.Create(symbol, cancellationToken),
                       sourceText,
                       classifiedSpans,
                       textSpan,
                       document.Id,
                       symbol.GetGlyph(),
                       parent: parent));
        }
예제 #5
0
        protected static async Task <ImmutableArray <ClassifiedSpan> > GetSemanticClassificationsAsync(Document document, TextSpan span)
        {
            var tree = await document.GetSyntaxTreeAsync();

            var service          = document.GetLanguageService <ISyntaxClassificationService>();
            var classifiers      = service.GetDefaultSyntaxClassifiers();
            var extensionManager = document.Project.Solution.Workspace.Services.GetService <IExtensionManager>();

            var results = ArrayBuilder <ClassifiedSpan> .GetInstance();

            var options = ClassificationOptions.From(document.Project);

            await service.AddSemanticClassificationsAsync(
                document,
                span,
                options,
                extensionManager.CreateNodeExtensionGetter(classifiers, c => c.SyntaxNodeTypes),
                extensionManager.CreateTokenExtensionGetter(classifiers, c => c.SyntaxTokenKinds),
                results,
                CancellationToken.None);

            return(results.ToImmutableAndFree());
        }
예제 #6
0
        /// <summary>
        /// Returns the semantic tokens data for a given document with an optional range.
        /// </summary>
        internal static async Task <(int[], bool isFinalized)> ComputeSemanticTokensDataAsync(
            Document document,
            Dictionary <string, int> tokenTypesToIndex,
            LSP.Range?range,
            bool includeSyntacticClassifications,
            CancellationToken cancellationToken)
        {
            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            // By default we calculate the tokens for the full document span, although the user
            // can pass in a range if they wish.
            var textSpan = range is null ? root.FullSpan : ProtocolConversions.RangeToTextSpan(range, text);

            // If the full compilation is not yet available, we'll try getting a partial one. It may contain inaccurate
            // results but will speed up how quickly we can respond to the client's request.
            var frozenDocument = document.WithFrozenPartialSemantics(cancellationToken);
            var semanticModel  = await frozenDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var isFinalized = document.Project.TryGetCompilation(out var compilation) && compilation == semanticModel.Compilation;

            document = frozenDocument;

            var options = ClassificationOptions.From(document.Project);

            var classifiedSpans = await GetClassifiedSpansForDocumentAsync(
                document, textSpan, options, includeSyntacticClassifications, cancellationToken).ConfigureAwait(false);

            // Multi-line tokens are not supported by VS (tracked by https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1265495).
            // Roslyn's classifier however can return multi-line classified spans, so we must break these up into single-line spans.
            var updatedClassifiedSpans = ConvertMultiLineToSingleLineSpans(text, classifiedSpans);

            // TO-DO: We should implement support for streaming if LSP adds support for it:
            // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1276300
            return(ComputeTokens(text.Lines, updatedClassifiedSpans, tokenTypesToIndex), isFinalized);
        }
예제 #7
0
        private static async Task <bool> TryFindLiteralReferencesAsync(
            Document document, int position, IFindUsagesContext context, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

            var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>();

            // Currently we only support FAR for numbers, strings and characters.  We don't
            // bother with true/false/null as those are likely to have way too many results
            // to be useful.
            var token = await syntaxTree.GetTouchingTokenAsync(
                position,
                t => syntaxFacts.IsNumericLiteral(t) ||
                syntaxFacts.IsCharacterLiteral(t) ||
                syntaxFacts.IsStringLiteral(t),
                cancellationToken).ConfigureAwait(false);

            if (token.RawKind == 0)
            {
                return(false);
            }

            // Searching for decimals not supported currently.  Our index can only store 64bits
            // for numeric values, and a decimal won't fit within that.
            var tokenValue = token.Value;

            if (tokenValue is null or decimal)
            {
                return(false);
            }

            if (token.Parent is null)
            {
                return(false);
            }

            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var symbol = semanticModel.GetSymbolInfo(token.Parent, cancellationToken).Symbol ?? semanticModel.GetDeclaredSymbol(token.Parent, cancellationToken);

            // Numeric labels are available in VB.  In that case we want the normal FAR engine to
            // do the searching.  For these literals we want to find symbolic results and not
            // numeric matches.
            if (symbol is ILabelSymbol)
            {
                return(false);
            }

            // Use the literal to make the title.  Trim literal if it's too long.
            var title = syntaxFacts.ConvertToSingleLine(token.Parent).ToString();

            if (title.Length >= 10)
            {
                title = title.Substring(0, 10) + "...";
            }

            var searchTitle = string.Format(EditorFeaturesResources._0_references, title);
            await context.SetSearchTitleAsync(searchTitle, cancellationToken).ConfigureAwait(false);

            var solution = document.Project.Solution;

            // There will only be one 'definition' that all matching literal reference.
            // So just create it now and report to the context what it is.
            var definition = DefinitionItem.CreateNonNavigableItem(
                ImmutableArray.Create(TextTags.StringLiteral),
                ImmutableArray.Create(new TaggedText(TextTags.Text, searchTitle)));

            await context.OnDefinitionFoundAsync(definition, cancellationToken).ConfigureAwait(false);

            var classificationOptions = ClassificationOptions.From(document.Project);
            var progressAdapter       = new FindLiteralsProgressAdapter(context, definition, classificationOptions);

            // Now call into the underlying FAR engine to find reference.  The FAR
            // engine will push results into the 'progress' instance passed into it.
            // We'll take those results, massage them, and forward them along to the
            // FindUsagesContext instance we were given.
            await SymbolFinder.FindLiteralReferencesAsync(
                tokenValue, Type.GetTypeCode(tokenValue.GetType()), solution, progressAdapter, cancellationToken).ConfigureAwait(false);

            return(true);
        }
        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?.Document is Document document)
            {
                var classificationOptions = ClassificationOptions.From(document.Project);

                var textRuns = new List <ClassifiedTextRun>();
                var spanSeparatorNeededBefore = false;
                foreach (var span in quickInfoItem.RelatedSpans)
                {
                    var classifiedSpans = await ClassifierHelper.GetClassifiedSpansAsync(document, span, classificationOptions, cancellationToken).ConfigureAwait(false);

                    var tabSize = document.Project.Solution.Options.GetOption(FormattingOptions.TabSize, document.Project.Language);
                    var text    = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

                    var spans          = IndentationHelper.GetSpansWithAlignedIndentation(text, classifiedSpans.ToImmutableArray(), 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));
        }