File: Classification\Classifier.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Collections;
using Microsoft.CodeAnalysis.Extensions;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Classification;
 
public static class Classifier
{
    internal static PooledObject<SegmentedList<ClassifiedSpan>> GetPooledList(out SegmentedList<ClassifiedSpan> classifiedSpans)
        => SegmentedListPool.GetPooledList<ClassifiedSpan>(out classifiedSpans);
 
    public static async Task<IEnumerable<ClassifiedSpan>> GetClassifiedSpansAsync(
        Document document,
        TextSpan textSpan,
        CancellationToken cancellationToken = default)
    {
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
        // public options do not affect classification:
        return GetClassifiedSpans(document.Project.Solution.Services, document.Project, semanticModel, textSpan, ClassificationOptions.Default, cancellationToken);
    }
 
    /// <summary>
    /// Returns classified spans in ascending <see cref="ClassifiedSpan"/> order.
    /// <see cref="ClassifiedSpan"/>s may have the same <see cref="ClassifiedSpan.TextSpan"/>. This occurs when there are multiple
    /// <see cref="ClassifiedSpan.ClassificationType"/>s for the same region of code. For example, a reference to a static method
    /// will have two spans, one that designates it as a method, and one that designates it as static.
    /// <see cref="ClassifiedSpan"/>s may also have overlapping <see cref="ClassifiedSpan.TextSpan"/>s. This occurs when there are
    /// strings containing regex and/or escape characters.
    /// </summary>
    [Obsolete("Use GetClassifiedSpansAsync instead")]
    public static IEnumerable<ClassifiedSpan> GetClassifiedSpans(
        SemanticModel semanticModel,
        TextSpan textSpan,
        Workspace workspace,
        CancellationToken cancellationToken = default)
    {
        // public options do not affect classification:
        return GetClassifiedSpans(workspace.Services.SolutionServices, project: null, semanticModel, textSpan, ClassificationOptions.Default, cancellationToken);
    }
 
    internal static IEnumerable<ClassifiedSpan> GetClassifiedSpans(
        SolutionServices services,
        Project? project,
        SemanticModel semanticModel,
        TextSpan textSpan,
        ClassificationOptions options,
        CancellationToken cancellationToken)
    {
        return GetClassifiedSpans(
            services, project, semanticModel, textSpan, options, includedEmbeddedClassifications: true, cancellationToken);
    }
 
    internal static IEnumerable<ClassifiedSpan> GetClassifiedSpans(
        SolutionServices services,
        Project? project,
        SemanticModel semanticModel,
        TextSpan textSpan,
        ClassificationOptions options,
        bool includedEmbeddedClassifications,
        CancellationToken cancellationToken)
    {
        var projectServices = services.GetLanguageServices(semanticModel.Language);
        var classificationService = projectServices.GetRequiredService<ISyntaxClassificationService>();
 
        var syntaxClassifiers = classificationService.GetDefaultSyntaxClassifiers();
 
        var extensionManager = services.GetRequiredService<IExtensionManager>();
        var getNodeClassifiers = extensionManager.CreateNodeExtensionGetter(syntaxClassifiers, c => c.SyntaxNodeTypes);
        var getTokenClassifiers = extensionManager.CreateTokenExtensionGetter(syntaxClassifiers, c => c.SyntaxTokenKinds);
 
        using var _1 = GetPooledList(out var syntacticClassifications);
        using var _2 = GetPooledList(out var semanticClassifications);
 
        var root = semanticModel.SyntaxTree.GetRoot(cancellationToken);
 
        classificationService.AddSyntacticClassifications(root, textSpan, syntacticClassifications, cancellationToken);
        classificationService.AddSemanticClassifications(semanticModel, textSpan, getNodeClassifiers, getTokenClassifiers, semanticClassifications, options, cancellationToken);
 
        // intentionally adding to the semanticClassifications array here.
        if (includedEmbeddedClassifications
            && project != null
            && projectServices.GetService<IEmbeddedLanguageClassificationService>() is { } embeddedLanguageService)
        {
            embeddedLanguageService.AddEmbeddedLanguageClassifications(services, project, semanticModel, textSpan, options, semanticClassifications, cancellationToken);
        }
 
        var allClassifications = new List<ClassifiedSpan>(semanticClassifications.Where(s => s.TextSpan.OverlapsWith(textSpan)));
        var semanticSet = semanticClassifications.Select(s => s.TextSpan).ToSet();
 
        allClassifications.AddRange(syntacticClassifications.Where(
            s => s.TextSpan.OverlapsWith(textSpan) && !semanticSet.Contains(s.TextSpan)));
        allClassifications.Sort((s1, s2) => s1.TextSpan.Start - s2.TextSpan.Start);
 
        return allClassifications;
    }
 
    internal static async Task<ImmutableArray<SymbolDisplayPart>> GetClassifiedSymbolDisplayPartsAsync(
        LanguageServices languageServices, SemanticModel semanticModel, TextSpan textSpan, ClassificationOptions options,
        CancellationToken cancellationToken = default)
    {
        var classifiedSpans = GetClassifiedSpans(languageServices.SolutionServices, project: null, semanticModel, textSpan, options, cancellationToken);
        var sourceText = await semanticModel.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false);
 
        return ConvertClassificationsToParts(sourceText, textSpan.Start, classifiedSpans);
    }
 
    internal static ImmutableArray<SymbolDisplayPart> ConvertClassificationsToParts(
        SourceText sourceText, int startPosition, IEnumerable<ClassifiedSpan> classifiedSpans)
    {
        var parts = ArrayBuilder<SymbolDisplayPart>.GetInstance();
 
        foreach (var span in classifiedSpans)
        {
            // If there is space between this span and the last one, then add a space.
            if (startPosition < span.TextSpan.Start)
            {
                parts.AddRange(Space());
            }
 
            var kind = GetClassificationKind(span.ClassificationType);
            if (kind != null)
            {
                parts.Add(new SymbolDisplayPart(kind.Value, null, sourceText.ToString(span.TextSpan)));
 
                startPosition = span.TextSpan.End;
            }
        }
 
        return parts.ToImmutableAndFree();
    }
 
    private static IEnumerable<SymbolDisplayPart> Space(int count = 1)
    {
        yield return new SymbolDisplayPart(SymbolDisplayPartKind.Space, null, new string(' ', count));
    }
 
    private static SymbolDisplayPartKind? GetClassificationKind(string type)
        => type switch
        {
            ClassificationTypeNames.Identifier => SymbolDisplayPartKind.Text,
            ClassificationTypeNames.Keyword => SymbolDisplayPartKind.Keyword,
            ClassificationTypeNames.NumericLiteral => SymbolDisplayPartKind.NumericLiteral,
            ClassificationTypeNames.StringLiteral => SymbolDisplayPartKind.StringLiteral,
            ClassificationTypeNames.WhiteSpace => SymbolDisplayPartKind.Space,
            ClassificationTypeNames.Operator => SymbolDisplayPartKind.Operator,
            ClassificationTypeNames.Punctuation => SymbolDisplayPartKind.Punctuation,
            ClassificationTypeNames.ClassName => SymbolDisplayPartKind.ClassName,
            ClassificationTypeNames.RecordClassName => SymbolDisplayPartKind.RecordClassName,
            ClassificationTypeNames.StructName => SymbolDisplayPartKind.StructName,
            ClassificationTypeNames.InterfaceName => SymbolDisplayPartKind.InterfaceName,
            ClassificationTypeNames.DelegateName => SymbolDisplayPartKind.DelegateName,
            ClassificationTypeNames.EnumName => SymbolDisplayPartKind.EnumName,
            ClassificationTypeNames.TypeParameterName => SymbolDisplayPartKind.TypeParameterName,
            ClassificationTypeNames.ModuleName => SymbolDisplayPartKind.ModuleName,
            ClassificationTypeNames.VerbatimStringLiteral => SymbolDisplayPartKind.StringLiteral,
            ClassificationTypeNames.FieldName => SymbolDisplayPartKind.FieldName,
            ClassificationTypeNames.EnumMemberName => SymbolDisplayPartKind.EnumMemberName,
            ClassificationTypeNames.ConstantName => SymbolDisplayPartKind.ConstantName,
            ClassificationTypeNames.LocalName => SymbolDisplayPartKind.LocalName,
            ClassificationTypeNames.ParameterName => SymbolDisplayPartKind.ParameterName,
            ClassificationTypeNames.ExtensionMethodName => SymbolDisplayPartKind.ExtensionMethodName,
            ClassificationTypeNames.MethodName => SymbolDisplayPartKind.MethodName,
            ClassificationTypeNames.PropertyName => SymbolDisplayPartKind.PropertyName,
            ClassificationTypeNames.LabelName => SymbolDisplayPartKind.LabelName,
            ClassificationTypeNames.NamespaceName => SymbolDisplayPartKind.NamespaceName,
            ClassificationTypeNames.EventName => SymbolDisplayPartKind.EventName,
            _ => null,
        };
}