|
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.Classification;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens
{
internal readonly struct SemanticTokensSchema
{
// TO-DO: Expand this mapping once support for custom token types is added:
// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1085998
/// <summary>
/// Core VS classifications, only map a few things to LSP. The rest we keep as our own standard classification
/// type names so those continue to work in VS.
/// </summary>
private static readonly ImmutableDictionary<string, string> s_vsDirectTypeMap = new Dictionary<string, string>()
{
[ClassificationTypeNames.Comment] = SemanticTokenTypes.Comment,
[ClassificationTypeNames.Identifier] = SemanticTokenTypes.Variable,
[ClassificationTypeNames.Keyword] = SemanticTokenTypes.Keyword,
[ClassificationTypeNames.NumericLiteral] = SemanticTokenTypes.Number,
[ClassificationTypeNames.Operator] = SemanticTokenTypes.Operator,
[ClassificationTypeNames.StringLiteral] = SemanticTokenTypes.String,
}.ToImmutableDictionary();
/// <summary>
/// The 'pure' set of classification types maps exact Roslyn matches to the well defined values actually in LSP.
/// For example "class name" to "class". Importantly though, if there is no exact match, we do not map things
/// along. This allows the user to theme things however they want.
/// </summary>
private static readonly ImmutableDictionary<string, string> s_pureLspDirectTypeMap = s_vsDirectTypeMap.Concat(new Dictionary<string, string>
{
[ClassificationTypeNames.ClassName] = SemanticTokenTypes.Class,
[ClassificationTypeNames.StructName] = SemanticTokenTypes.Struct,
[ClassificationTypeNames.NamespaceName] = SemanticTokenTypes.Namespace,
[ClassificationTypeNames.EnumName] = SemanticTokenTypes.Enum,
[ClassificationTypeNames.InterfaceName] = SemanticTokenTypes.Interface,
[ClassificationTypeNames.TypeParameterName] = SemanticTokenTypes.TypeParameter,
[ClassificationTypeNames.ParameterName] = SemanticTokenTypes.Parameter,
[ClassificationTypeNames.LocalName] = SemanticTokenTypes.Variable,
[ClassificationTypeNames.PropertyName] = SemanticTokenTypes.Property,
[ClassificationTypeNames.MethodName] = SemanticTokenTypes.Method,
[ClassificationTypeNames.EnumMemberName] = SemanticTokenTypes.EnumMember,
[ClassificationTypeNames.EventName] = SemanticTokenTypes.Event,
[ClassificationTypeNames.PreprocessorKeyword] = SemanticTokenTypes.Macro,
// in https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide#standard-token-types-and-modifiers
[ClassificationTypeNames.LabelName] = "label",
}).ToImmutableDictionary();
/// <summary>
/// A schema for mapping classification type names to VS LSP token names. This maps a few classification type names
/// directly to LSP semantic token types, but otherwise generally returns the classification type name as a custom token type.
/// </summary>
private static readonly SemanticTokensSchema s_vsTokenSchema = new(ClassificationTypeNames.AllTypeNames
.Where(classificationTypeName => !ClassificationTypeNames.AdditiveTypeNames.Contains(classificationTypeName))
.ToImmutableDictionary(
classificationTypeName => classificationTypeName,
classificationTypeName => IDictionaryExtensions.GetValueOrDefault(s_vsDirectTypeMap, classificationTypeName) ?? classificationTypeName));
/// <summary>
/// A schema for mapping classification type names to 'pure' LSP token names. This includes classification type names
/// that are directly mapped to LSP semantic token types as well as mappings from roslyn classification type names to
/// LSP compatible custom token type names.
/// </summary>
private static readonly SemanticTokensSchema s_pureLspTokenSchema = new(ClassificationTypeNames.AllTypeNames
.Where(classificationTypeName => !ClassificationTypeNames.AdditiveTypeNames.Contains(classificationTypeName))
.ToImmutableDictionary(
classificationTypeName => classificationTypeName,
classificationTypeName => IDictionaryExtensions.GetValueOrDefault(s_pureLspDirectTypeMap, classificationTypeName) ?? CustomLspSemanticTokenNames.ClassificationTypeNameToCustomTokenName[classificationTypeName]));
/// <summary>
/// Mapping from roslyn <see cref="ClassificationTypeNames"/> to the LSP token name. This is either a standard
/// <see cref="SemanticTokenTypes"/> or a custom token name.
/// </summary>
public readonly IReadOnlyDictionary<string, string> TokenTypeMap;
/// <summary>
/// Mapping from the semantic token type name to the index in <see cref="AllTokenTypes"/>. Required since we report
/// tokens back to LSP as a series of ints, and LSP needs a way to decipher them.
/// </summary>
public readonly IReadOnlyDictionary<string, int> TokenTypeToIndex;
/// <summary>
/// Equivalent to see <see cref="SemanticTokenTypes.AllTypes"/> combined with the remaining custom token names from <see cref="TokenTypeMap"/>
/// </summary>
public readonly ImmutableArray<string> AllTokenTypes;
public SemanticTokensSchema(IReadOnlyDictionary<string, string> tokenTypeMap)
{
TokenTypeMap = tokenTypeMap;
// Get all custom token type names that don't directly map to an built-in LSP semantic token type.
var customTokenTypes = TokenTypeMap.Values
.Where(tokenType => !SemanticTokenTypes.AllTypes.Contains(tokenType))
.Order()
.ToImmutableArray();
AllTokenTypes = [.. SemanticTokenTypes.AllTypes, .. customTokenTypes];
var tokenTypeToIndex = new Dictionary<string, int>();
foreach (var lspTokenType in SemanticTokenTypes.AllTypes)
tokenTypeToIndex.Add(lspTokenType, tokenTypeToIndex.Count);
foreach (var roslynTokenType in customTokenTypes)
tokenTypeToIndex.Add(roslynTokenType, tokenTypeToIndex.Count);
TokenTypeToIndex = tokenTypeToIndex;
}
public static SemanticTokensSchema GetSchema(bool clientSupportsVisualStudioExtensions)
=> clientSupportsVisualStudioExtensions
? s_vsTokenSchema
: s_pureLspTokenSchema;
public static SemanticTokensSchema LegacyTokenSchemaForRazor
=> s_vsTokenSchema;
public static SemanticTokensSchema LegacyTokensSchemaForLSIF
=> s_vsTokenSchema;
public static string[] TokenModifiers =
[
// This must be in the same order as SemanticTokens.TokenModifiers, but skip the "None" item
SemanticTokenModifiers.Static,
nameof(SemanticTokens.TokenModifiers.ReassignedVariable),
SemanticTokenModifiers.Deprecated,
];
}
}
|