From c1183287c109d8c1b109f14b67239e7fc1840fd5 Mon Sep 17 00:00:00 2001 From: Allison Chou Date: Wed, 4 Oct 2023 21:12:18 -0700 Subject: [PATCH] Add Razor C# semantic tokens support in VS Code (#6489) --- package.json | 169 +++++++++++++++++- src/lsptoolshost/razorCommands.ts | 10 +- src/razor/src/extension.ts | 7 +- .../semantic/provideSemanticTokensResponse.ts | 2 +- .../semantic/semanticTokensRangeHandler.ts | 67 +++++-- 5 files changed, 237 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 171d5e128..d58e7c41c 100644 --- a/package.json +++ b/package.json @@ -5175,6 +5175,168 @@ "markupCommentPunctuation": [ "punctuation.definition.comment.html", "comment.block.html" + ], + "keyword": [ + "keyword.cs" + ], + "excludedCode": [ + "support.other.excluded.cs" + ], + "controlKeyword": [ + "keyword.control.cs" + ], + "operatorOverloaded": [ + "entity.name.function.member.overload.cs" + ], + "preprocessorText": [ + "meta.preprocessor.string.cs" + ], + "punctuation": [ + "punctuation.cs" + ], + "stringVerbatim": [ + "string.verbatim.cs" + ], + "stringEscapeCharacter": [ + "constant.character.escape.cs" + ], + "delegate": [ + "entity.name.type.delegate.cs" + ], + "module": [ + "entity.name.type.module.cs" + ], + "field": [ + "entity.name.variable.field.cs" + ], + "constant": [ + "variable.other.constant" + ], + "extensionMethod": [ + "entity.name.function.extension.cs" + ], + "xmlDocCommentAttributeName": [ + "comment.documentation.attribute.name.cs" + ], + "xmlDocCommentAttributeQuotes": [ + "comment.documentation.attribute.quotes.cs" + ], + "xmlDocCommentAttributeValue": [ + "comment.documentation.attribute.value.cs" + ], + "xmlDocCommentCDataSection": [ + "comment.documentation.cdata.cs" + ], + "xmlDocCommentComment": [ + "comment.documentation.comment.cs" + ], + "xmlDocCommentDelimiter": [ + "comment.documentation.delimiter.cs" + ], + "xmlDocCommentEntityReference": [ + "comment.documentation.entityReference.cs" + ], + "xmlDocCommentName": [ + "comment.documentation.name.cs" + ], + "xmlDocCommentProcessingInstruction": [ + "comment.documentation.processingInstruction.cs" + ], + "xmlDocCommentText": [ + "comment.documentation.cs" + ], + "xmlLiteralAttributeName": [ + "entity.other.attribute-name.localname.xml" + ], + "xmlLiteralAttributeQuotes": [ + "string.quoted.double.xml" + ], + "xmlLiteralAttributeValue": [ + "meta.tag.xml" + ], + "xmlLiteralCDataSection": [ + "string.quoted.double.xml" + ], + "xmlLiteralComment": [ + "comment.block.xml" + ], + "xmlLiteralDelimiter": [ + "text.xml" + ], + "xmlLiteralEmbeddedExpression": [ + "meta.tag.xml" + ], + "xmlLiteralEntityReference": [ + "meta.tag.xml" + ], + "xmlLiteralName": [ + "entity.name.tag.localname.xml" + ], + "xmlLiteralProcessingInstruction": [ + "meta.tag.xml" + ], + "xmlLiteralText": [ + "text.xml" + ], + "regexComment": [ + "string.regexp.comment.cs" + ], + "regexCharacterClass": [ + "constant.character.character-class.regexp.cs" + ], + "regexAnchor": [ + "keyword.control.anchor.regexp.cs" + ], + "regexQuantifier": [ + "keyword.operator.quantifier.regexp.cs" + ], + "regexGrouping": [ + "punctuation.definition.group.regexp.cs" + ], + "regexAlternation": [ + "keyword.operator.or.regexp.cs" + ], + "regexText": [ + "string.regexp" + ], + "regexSelfEscapedCharacter": [ + "string.regexp.self-escaped-character.cs" + ], + "regexOtherEscape": [ + "string.regexp.other-escape.cs" + ], + "jsonComment": [ + "comment.line.double-slash.js" + ], + "jsonNumber": [ + "constant.numeric.json" + ], + "jsonString": [ + "string.quoted.double.json" + ], + "jsonKeyword": [ + "constant.language.json" + ], + "jsonText": [ + "string.quoted.double.json" + ], + "jsonOperator": [ + "string.quoted.double.json" + ], + "jsonPunctuation": [ + "punctuation.separator.dictionary.key-value.json" + ], + "jsonArray": [ + "punctuation.definition.array.begin.json" + ], + "jsonObject": [ + "punctuation.definition.dictionary.begin.json" + ], + "jsonPropertyName": [ + "support.type.property-name.json" + ], + "jsonConstructorName": [ + "support.type.property-name.json" ] } }, @@ -5369,7 +5531,12 @@ { "language": "aspnetcorerazor", "scopeName": "text.aspnetcorerazor", - "path": "./src/razor/syntaxes/aspnetcorerazor.tmLanguage.json" + "path": "./src/razor/syntaxes/aspnetcorerazor.tmLanguage.json", + "unbalancedBracketScopes": [ + "keyword.operator.arrow.cs", + "keyword.operator.bitwise.shift.cs", + "keyword.operator.relational.cs" + ] } ], "menus": { diff --git a/src/lsptoolshost/razorCommands.ts b/src/lsptoolshost/razorCommands.ts index 8a3c5abf4..52495bb8a 100644 --- a/src/lsptoolshost/razorCommands.ts +++ b/src/lsptoolshost/razorCommands.ts @@ -36,6 +36,7 @@ export const provideCodeActionsCommand = 'roslyn.provideCodeActions'; export const resolveCodeActionCommand = 'roslyn.resolveCodeAction'; export const provideCompletionsCommand = 'roslyn.provideCompletions'; export const resolveCompletionsCommand = 'roslyn.resolveCompletion'; +export const provideSemanticTokensRangeCommand = 'roslyn.provideSemanticTokensRange'; export const roslynSimplifyMethodCommand = 'roslyn.simplifyMethod'; export const roslynFormatNewFileCommand = 'roslyn.formatNewFile'; export const razorInitializeCommand = 'razor.initialize'; @@ -72,15 +73,14 @@ export function registerRazorCommands(context: vscode.ExtensionContext, language } ) ); + + const formatNewFileRequestType = new RequestType( + 'roslyn/formatNewFile' + ); context.subscriptions.push( vscode.commands.registerCommand( roslynFormatNewFileCommand, async (request: SerializableFormatNewFileParams) => { - const formatNewFileRequestType = new RequestType< - SerializableFormatNewFileParams, - string | undefined, - any - >('roslyn/formatNewFile'); return await languageServer.sendRequest(formatNewFileRequestType, request, CancellationToken.None); } ) diff --git a/src/razor/src/extension.ts b/src/razor/src/extension.ts index 9d507f885..50efbabb5 100644 --- a/src/razor/src/extension.ts +++ b/src/razor/src/extension.ts @@ -145,7 +145,12 @@ export async function activate( languageServiceClient, logger ); - const semanticTokenHandler = new SemanticTokensRangeHandler(languageServerClient); + const semanticTokenHandler = new SemanticTokensRangeHandler( + documentManager, + documentSynchronizer, + languageServerClient, + logger + ); const colorPresentationHandler = new ColorPresentationHandler( documentManager, languageServerClient, diff --git a/src/razor/src/semantic/provideSemanticTokensResponse.ts b/src/razor/src/semantic/provideSemanticTokensResponse.ts index 18a72a89e..ce787867d 100644 --- a/src/razor/src/semantic/provideSemanticTokensResponse.ts +++ b/src/razor/src/semantic/provideSemanticTokensResponse.ts @@ -5,5 +5,5 @@ export class ProvideSemanticTokensResponse { // tslint:disable-next-line: variable-name - constructor(public Tokens: Array>, public HostDocumentSyncVersion: number) {} + constructor(public Tokens: number[], public HostDocumentSyncVersion: number) {} } diff --git a/src/razor/src/semantic/semanticTokensRangeHandler.ts b/src/razor/src/semantic/semanticTokensRangeHandler.ts index ec21c55e5..cbf8c76c6 100644 --- a/src/razor/src/semantic/semanticTokensRangeHandler.ts +++ b/src/razor/src/semantic/semanticTokensRangeHandler.ts @@ -8,6 +8,9 @@ import { RequestType } from 'vscode-languageclient'; import { RazorLanguageServerClient } from '../razorLanguageServerClient'; import { ProvideSemanticTokensResponse } from './provideSemanticTokensResponse'; import { SerializableSemanticTokensParams } from './serializableSemanticTokensParams'; +import { RazorDocumentManager } from '../document/razorDocumentManager'; +import { RazorDocumentSynchronizer } from '../document/razorDocumentSynchronizer'; +import { RazorLogger } from '../razorLogger'; export class SemanticTokensRangeHandler { private static readonly getSemanticTokensRangeEndpoint = 'razor/provideSemanticTokensRange'; @@ -16,9 +19,14 @@ export class SemanticTokensRangeHandler { ProvideSemanticTokensResponse, any > = new RequestType(SemanticTokensRangeHandler.getSemanticTokensRangeEndpoint); - private emptyTokensInResponse: Array> = new Array>(); + private emptyTokensResponse: number[] = new Array(); - constructor(private readonly serverClient: RazorLanguageServerClient) {} + constructor( + private readonly documentManager: RazorDocumentManager, + private readonly documentSynchronizer: RazorDocumentSynchronizer, + private readonly serverClient: RazorLanguageServerClient, + private readonly logger: RazorLogger + ) {} public async register() { await this.serverClient.onRequestWithParams< @@ -33,16 +41,55 @@ export class SemanticTokensRangeHandler { } private async getSemanticTokens( - _semanticTokensParams: SerializableSemanticTokensParams, - _cancellationToken: vscode.CancellationToken + semanticTokensParams: SerializableSemanticTokensParams, + cancellationToken: vscode.CancellationToken ): Promise { - // This is currently a no-op since (1) the default C# semantic tokens experience is already powerful and - // (2) there seems to be an issue with the semantic tokens execute command - possibly either O# not - // returning tokens, or an issue with the command itself: - // https://github.com/dotnet/razor/issues/6922 + try { + const razorDocumentUri = vscode.Uri.parse(semanticTokensParams.textDocument.uri, true); + const razorDocument = await this.documentManager.getDocument(razorDocumentUri); + if (razorDocument === undefined) { + this.logger.logWarning( + `Could not find Razor document ${razorDocumentUri}; returning semantic tokens information.` + ); + + return new ProvideSemanticTokensResponse( + this.emptyTokensResponse, + semanticTokensParams.requiredHostDocumentVersion + ); + } + + const textDocument = await vscode.workspace.openTextDocument(razorDocumentUri); + const synchronized = await this.documentSynchronizer.trySynchronizeProjectedDocument( + textDocument, + razorDocument.csharpDocument, + semanticTokensParams.requiredHostDocumentVersion, + cancellationToken + ); + + if (!synchronized) { + return new ProvideSemanticTokensResponse( + this.emptyTokensResponse, + semanticTokensParams.requiredHostDocumentVersion + ); + } + + const tokens = await vscode.commands.executeCommand( + 'vscode.provideDocumentRangeSemanticTokens', + razorDocument.csharpDocument.uri, + semanticTokensParams.ranges[0] + ); + + return new ProvideSemanticTokensResponse( + Array.from(tokens.data), + semanticTokensParams.requiredHostDocumentVersion + ); + } catch (error) { + this.logger.logWarning(`${SemanticTokensRangeHandler.getSemanticTokensRangeEndpoint} failed with ${error}`); + } + return new ProvideSemanticTokensResponse( - this.emptyTokensInResponse, - _semanticTokensParams.requiredHostDocumentVersion + this.emptyTokensResponse, + semanticTokensParams.requiredHostDocumentVersion ); } }