Skip to content

Commit c118328

Browse files
authored
Add Razor C# semantic tokens support in VS Code (#6489)
1 parent d443538 commit c118328

File tree

5 files changed

+237
-18
lines changed

5 files changed

+237
-18
lines changed

package.json

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5175,6 +5175,168 @@
51755175
"markupCommentPunctuation": [
51765176
"punctuation.definition.comment.html",
51775177
"comment.block.html"
5178+
],
5179+
"keyword": [
5180+
"keyword.cs"
5181+
],
5182+
"excludedCode": [
5183+
"support.other.excluded.cs"
5184+
],
5185+
"controlKeyword": [
5186+
"keyword.control.cs"
5187+
],
5188+
"operatorOverloaded": [
5189+
"entity.name.function.member.overload.cs"
5190+
],
5191+
"preprocessorText": [
5192+
"meta.preprocessor.string.cs"
5193+
],
5194+
"punctuation": [
5195+
"punctuation.cs"
5196+
],
5197+
"stringVerbatim": [
5198+
"string.verbatim.cs"
5199+
],
5200+
"stringEscapeCharacter": [
5201+
"constant.character.escape.cs"
5202+
],
5203+
"delegate": [
5204+
"entity.name.type.delegate.cs"
5205+
],
5206+
"module": [
5207+
"entity.name.type.module.cs"
5208+
],
5209+
"field": [
5210+
"entity.name.variable.field.cs"
5211+
],
5212+
"constant": [
5213+
"variable.other.constant"
5214+
],
5215+
"extensionMethod": [
5216+
"entity.name.function.extension.cs"
5217+
],
5218+
"xmlDocCommentAttributeName": [
5219+
"comment.documentation.attribute.name.cs"
5220+
],
5221+
"xmlDocCommentAttributeQuotes": [
5222+
"comment.documentation.attribute.quotes.cs"
5223+
],
5224+
"xmlDocCommentAttributeValue": [
5225+
"comment.documentation.attribute.value.cs"
5226+
],
5227+
"xmlDocCommentCDataSection": [
5228+
"comment.documentation.cdata.cs"
5229+
],
5230+
"xmlDocCommentComment": [
5231+
"comment.documentation.comment.cs"
5232+
],
5233+
"xmlDocCommentDelimiter": [
5234+
"comment.documentation.delimiter.cs"
5235+
],
5236+
"xmlDocCommentEntityReference": [
5237+
"comment.documentation.entityReference.cs"
5238+
],
5239+
"xmlDocCommentName": [
5240+
"comment.documentation.name.cs"
5241+
],
5242+
"xmlDocCommentProcessingInstruction": [
5243+
"comment.documentation.processingInstruction.cs"
5244+
],
5245+
"xmlDocCommentText": [
5246+
"comment.documentation.cs"
5247+
],
5248+
"xmlLiteralAttributeName": [
5249+
"entity.other.attribute-name.localname.xml"
5250+
],
5251+
"xmlLiteralAttributeQuotes": [
5252+
"string.quoted.double.xml"
5253+
],
5254+
"xmlLiteralAttributeValue": [
5255+
"meta.tag.xml"
5256+
],
5257+
"xmlLiteralCDataSection": [
5258+
"string.quoted.double.xml"
5259+
],
5260+
"xmlLiteralComment": [
5261+
"comment.block.xml"
5262+
],
5263+
"xmlLiteralDelimiter": [
5264+
"text.xml"
5265+
],
5266+
"xmlLiteralEmbeddedExpression": [
5267+
"meta.tag.xml"
5268+
],
5269+
"xmlLiteralEntityReference": [
5270+
"meta.tag.xml"
5271+
],
5272+
"xmlLiteralName": [
5273+
"entity.name.tag.localname.xml"
5274+
],
5275+
"xmlLiteralProcessingInstruction": [
5276+
"meta.tag.xml"
5277+
],
5278+
"xmlLiteralText": [
5279+
"text.xml"
5280+
],
5281+
"regexComment": [
5282+
"string.regexp.comment.cs"
5283+
],
5284+
"regexCharacterClass": [
5285+
"constant.character.character-class.regexp.cs"
5286+
],
5287+
"regexAnchor": [
5288+
"keyword.control.anchor.regexp.cs"
5289+
],
5290+
"regexQuantifier": [
5291+
"keyword.operator.quantifier.regexp.cs"
5292+
],
5293+
"regexGrouping": [
5294+
"punctuation.definition.group.regexp.cs"
5295+
],
5296+
"regexAlternation": [
5297+
"keyword.operator.or.regexp.cs"
5298+
],
5299+
"regexText": [
5300+
"string.regexp"
5301+
],
5302+
"regexSelfEscapedCharacter": [
5303+
"string.regexp.self-escaped-character.cs"
5304+
],
5305+
"regexOtherEscape": [
5306+
"string.regexp.other-escape.cs"
5307+
],
5308+
"jsonComment": [
5309+
"comment.line.double-slash.js"
5310+
],
5311+
"jsonNumber": [
5312+
"constant.numeric.json"
5313+
],
5314+
"jsonString": [
5315+
"string.quoted.double.json"
5316+
],
5317+
"jsonKeyword": [
5318+
"constant.language.json"
5319+
],
5320+
"jsonText": [
5321+
"string.quoted.double.json"
5322+
],
5323+
"jsonOperator": [
5324+
"string.quoted.double.json"
5325+
],
5326+
"jsonPunctuation": [
5327+
"punctuation.separator.dictionary.key-value.json"
5328+
],
5329+
"jsonArray": [
5330+
"punctuation.definition.array.begin.json"
5331+
],
5332+
"jsonObject": [
5333+
"punctuation.definition.dictionary.begin.json"
5334+
],
5335+
"jsonPropertyName": [
5336+
"support.type.property-name.json"
5337+
],
5338+
"jsonConstructorName": [
5339+
"support.type.property-name.json"
51785340
]
51795341
}
51805342
},
@@ -5369,7 +5531,12 @@
53695531
{
53705532
"language": "aspnetcorerazor",
53715533
"scopeName": "text.aspnetcorerazor",
5372-
"path": "./src/razor/syntaxes/aspnetcorerazor.tmLanguage.json"
5534+
"path": "./src/razor/syntaxes/aspnetcorerazor.tmLanguage.json",
5535+
"unbalancedBracketScopes": [
5536+
"keyword.operator.arrow.cs",
5537+
"keyword.operator.bitwise.shift.cs",
5538+
"keyword.operator.relational.cs"
5539+
]
53735540
}
53745541
],
53755542
"menus": {

src/lsptoolshost/razorCommands.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const provideCodeActionsCommand = 'roslyn.provideCodeActions';
3636
export const resolveCodeActionCommand = 'roslyn.resolveCodeAction';
3737
export const provideCompletionsCommand = 'roslyn.provideCompletions';
3838
export const resolveCompletionsCommand = 'roslyn.resolveCompletion';
39+
export const provideSemanticTokensRangeCommand = 'roslyn.provideSemanticTokensRange';
3940
export const roslynSimplifyMethodCommand = 'roslyn.simplifyMethod';
4041
export const roslynFormatNewFileCommand = 'roslyn.formatNewFile';
4142
export const razorInitializeCommand = 'razor.initialize';
@@ -72,15 +73,14 @@ export function registerRazorCommands(context: vscode.ExtensionContext, language
7273
}
7374
)
7475
);
76+
77+
const formatNewFileRequestType = new RequestType<SerializableFormatNewFileParams, string | undefined, any>(
78+
'roslyn/formatNewFile'
79+
);
7580
context.subscriptions.push(
7681
vscode.commands.registerCommand(
7782
roslynFormatNewFileCommand,
7883
async (request: SerializableFormatNewFileParams) => {
79-
const formatNewFileRequestType = new RequestType<
80-
SerializableFormatNewFileParams,
81-
string | undefined,
82-
any
83-
>('roslyn/formatNewFile');
8484
return await languageServer.sendRequest(formatNewFileRequestType, request, CancellationToken.None);
8585
}
8686
)

src/razor/src/extension.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,12 @@ export async function activate(
145145
languageServiceClient,
146146
logger
147147
);
148-
const semanticTokenHandler = new SemanticTokensRangeHandler(languageServerClient);
148+
const semanticTokenHandler = new SemanticTokensRangeHandler(
149+
documentManager,
150+
documentSynchronizer,
151+
languageServerClient,
152+
logger
153+
);
149154
const colorPresentationHandler = new ColorPresentationHandler(
150155
documentManager,
151156
languageServerClient,

src/razor/src/semantic/provideSemanticTokensResponse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55

66
export class ProvideSemanticTokensResponse {
77
// tslint:disable-next-line: variable-name
8-
constructor(public Tokens: Array<Array<number>>, public HostDocumentSyncVersion: number) {}
8+
constructor(public Tokens: number[], public HostDocumentSyncVersion: number) {}
99
}

src/razor/src/semantic/semanticTokensRangeHandler.ts

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { RequestType } from 'vscode-languageclient';
88
import { RazorLanguageServerClient } from '../razorLanguageServerClient';
99
import { ProvideSemanticTokensResponse } from './provideSemanticTokensResponse';
1010
import { SerializableSemanticTokensParams } from './serializableSemanticTokensParams';
11+
import { RazorDocumentManager } from '../document/razorDocumentManager';
12+
import { RazorDocumentSynchronizer } from '../document/razorDocumentSynchronizer';
13+
import { RazorLogger } from '../razorLogger';
1114

1215
export class SemanticTokensRangeHandler {
1316
private static readonly getSemanticTokensRangeEndpoint = 'razor/provideSemanticTokensRange';
@@ -16,9 +19,14 @@ export class SemanticTokensRangeHandler {
1619
ProvideSemanticTokensResponse,
1720
any
1821
> = new RequestType(SemanticTokensRangeHandler.getSemanticTokensRangeEndpoint);
19-
private emptyTokensInResponse: Array<Array<number>> = new Array<Array<number>>();
22+
private emptyTokensResponse: number[] = new Array<number>();
2023

21-
constructor(private readonly serverClient: RazorLanguageServerClient) {}
24+
constructor(
25+
private readonly documentManager: RazorDocumentManager,
26+
private readonly documentSynchronizer: RazorDocumentSynchronizer,
27+
private readonly serverClient: RazorLanguageServerClient,
28+
private readonly logger: RazorLogger
29+
) {}
2230

2331
public async register() {
2432
await this.serverClient.onRequestWithParams<
@@ -33,16 +41,55 @@ export class SemanticTokensRangeHandler {
3341
}
3442

3543
private async getSemanticTokens(
36-
_semanticTokensParams: SerializableSemanticTokensParams,
37-
_cancellationToken: vscode.CancellationToken
44+
semanticTokensParams: SerializableSemanticTokensParams,
45+
cancellationToken: vscode.CancellationToken
3846
): Promise<ProvideSemanticTokensResponse> {
39-
// This is currently a no-op since (1) the default C# semantic tokens experience is already powerful and
40-
// (2) there seems to be an issue with the semantic tokens execute command - possibly either O# not
41-
// returning tokens, or an issue with the command itself:
42-
// https://github.com/dotnet/razor/issues/6922
47+
try {
48+
const razorDocumentUri = vscode.Uri.parse(semanticTokensParams.textDocument.uri, true);
49+
const razorDocument = await this.documentManager.getDocument(razorDocumentUri);
50+
if (razorDocument === undefined) {
51+
this.logger.logWarning(
52+
`Could not find Razor document ${razorDocumentUri}; returning semantic tokens information.`
53+
);
54+
55+
return new ProvideSemanticTokensResponse(
56+
this.emptyTokensResponse,
57+
semanticTokensParams.requiredHostDocumentVersion
58+
);
59+
}
60+
61+
const textDocument = await vscode.workspace.openTextDocument(razorDocumentUri);
62+
const synchronized = await this.documentSynchronizer.trySynchronizeProjectedDocument(
63+
textDocument,
64+
razorDocument.csharpDocument,
65+
semanticTokensParams.requiredHostDocumentVersion,
66+
cancellationToken
67+
);
68+
69+
if (!synchronized) {
70+
return new ProvideSemanticTokensResponse(
71+
this.emptyTokensResponse,
72+
semanticTokensParams.requiredHostDocumentVersion
73+
);
74+
}
75+
76+
const tokens = await vscode.commands.executeCommand<vscode.SemanticTokens>(
77+
'vscode.provideDocumentRangeSemanticTokens',
78+
razorDocument.csharpDocument.uri,
79+
semanticTokensParams.ranges[0]
80+
);
81+
82+
return new ProvideSemanticTokensResponse(
83+
Array.from(tokens.data),
84+
semanticTokensParams.requiredHostDocumentVersion
85+
);
86+
} catch (error) {
87+
this.logger.logWarning(`${SemanticTokensRangeHandler.getSemanticTokensRangeEndpoint} failed with ${error}`);
88+
}
89+
4390
return new ProvideSemanticTokensResponse(
44-
this.emptyTokensInResponse,
45-
_semanticTokensParams.requiredHostDocumentVersion
91+
this.emptyTokensResponse,
92+
semanticTokensParams.requiredHostDocumentVersion
4693
);
4794
}
4895
}

0 commit comments

Comments
 (0)