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