Skip to content

Commit 1a2f94b

Browse files
committed
feat: add semantic tokens
1 parent a39e01d commit 1a2f94b

File tree

3 files changed

+114
-0
lines changed

3 files changed

+114
-0
lines changed

client/src/client.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ export class AngularLanguageClient implements vscode.Disposable {
171171
}
172172
return next(document, context, token);
173173
},
174+
provideDocumentSemanticTokens: async (document, token, next) => {
175+
if (await this.isInAngularProject(document)) {
176+
return next(document, token);
177+
}
178+
},
179+
provideDocumentRangeSemanticTokens: async (document, range, token, next) => {
180+
if (await this.isInAngularProject(document)) {
181+
return next(document, range, token);
182+
}
183+
},
174184
}
175185
};
176186
}

server/src/semantic_tokens.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import * as ts from 'typescript/lib/tsserverlibrary';
10+
import * as lsp from 'vscode-languageserver/node';
11+
12+
export enum TokenEncodingConsts {
13+
typeOffset = 8,
14+
modifierMask = (1 << typeOffset) - 1,
15+
}
16+
17+
export function getSemanticTokens(
18+
classifications: ts.Classifications, script: ts.server.ScriptInfo): lsp.SemanticTokens {
19+
const spans = classifications.spans;
20+
const builder = new lsp.SemanticTokensBuilder();
21+
22+
for (let i = 0; i < spans.length;) {
23+
const offset = spans[i++];
24+
const length = spans[i++];
25+
const tsClassification = spans[i++];
26+
27+
const tokenType = getTokenTypeFromClassification(tsClassification);
28+
if (tokenType === undefined) {
29+
continue;
30+
}
31+
32+
const tokenModifiers = getTokenModifierFromClassification(tsClassification);
33+
34+
const startPos = script.positionToLineOffset(offset);
35+
startPos.line -= 1;
36+
startPos.offset -= 1;
37+
38+
const endPos = script.positionToLineOffset(offset + length);
39+
endPos.line -= 1;
40+
endPos.offset -= 1;
41+
42+
for (let line = startPos.line; line <= endPos.line; line++) {
43+
const startCharacter = line === startPos.line ? startPos.offset : 0;
44+
const endCharacter =
45+
line === endPos.line ? endPos.offset : script.lineToTextSpan(line - 1).length;
46+
builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers);
47+
}
48+
}
49+
50+
return builder.build();
51+
}
52+
53+
function getTokenTypeFromClassification(tsClassification: number): number|undefined {
54+
if (tsClassification > TokenEncodingConsts.modifierMask) {
55+
return (tsClassification >> TokenEncodingConsts.typeOffset) - 1;
56+
}
57+
return undefined;
58+
}
59+
60+
function getTokenModifierFromClassification(tsClassification: number) {
61+
return tsClassification & TokenEncodingConsts.modifierMask;
62+
}

server/src/session.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {GetComponentsWithTemplateFile, GetTcbParams, GetTcbRequest, GetTcbRespon
2020
import {readNgCompletionData, tsCompletionEntryToLspCompletionItem} from './completion';
2121
import {tsDiagnosticToLspDiagnostic} from './diagnostic';
2222
import {getHTMLVirtualContent} from './embedded_support';
23+
import {getSemanticTokens} from './semantic_tokens';
2324
import {ServerHost} from './server_host';
2425
import {documentationToMarkdown} from './text_render';
2526
import {filePathToUri, getMappedDefinitionInfo, isConfiguredProject, isDebugMode, lspPositionToTsPosition, lspRangeToTsPositions, MruTracker, tsDisplayPartsToText, tsFileTextChangesToLspWorkspaceEdit, tsTextSpanToLspRange, uriToFilePath} from './utils';
@@ -217,8 +218,38 @@ export class Session {
217218
conn.onSignatureHelp(p => this.onSignatureHelp(p));
218219
conn.onCodeAction(p => this.onCodeAction(p));
219220
conn.onCodeActionResolve(async p => await this.onCodeActionResolve(p));
221+
conn.onRequest(lsp.SemanticTokensRequest.type, p => this.onSemanticTokensRequest(p));
222+
conn.onRequest(lsp.SemanticTokensRangeRequest.type, p => this.onSemanticTokensRangeRequest(p));
220223
}
221224

225+
private onSemanticTokensRequest(params: lsp.SemanticTokensParams): lsp.SemanticTokens|null {
226+
const lsInfo = this.getLSAndScriptInfo(params.textDocument);
227+
if (lsInfo === null) {
228+
return null;
229+
}
230+
const {languageService, scriptInfo} = lsInfo;
231+
const span = {start: 0, length: scriptInfo.getSnapshot().getLength()};
232+
const classifications = languageService.getEncodedSemanticClassifications(
233+
scriptInfo.fileName, span, ts.SemanticClassificationFormat.TwentyTwenty);
234+
return getSemanticTokens(classifications, scriptInfo);
235+
}
236+
237+
private onSemanticTokensRangeRequest(params: lsp.SemanticTokensRangeParams): lsp.SemanticTokens
238+
|null {
239+
const lsInfo = this.getLSAndScriptInfo(params.textDocument);
240+
if (lsInfo === null) {
241+
return null;
242+
}
243+
const {languageService, scriptInfo} = lsInfo;
244+
const start = lspPositionToTsPosition(lsInfo.scriptInfo, params.range.start);
245+
const end = lspPositionToTsPosition(lsInfo.scriptInfo, params.range.end);
246+
const span = {start, length: end - start};
247+
const classifications = languageService.getEncodedSemanticClassifications(
248+
scriptInfo.fileName, span, ts.SemanticClassificationFormat.TwentyTwenty);
249+
return getSemanticTokens(classifications, scriptInfo);
250+
}
251+
252+
222253
private onCodeAction(params: lsp.CodeActionParams): lsp.CodeAction[]|null {
223254
const filePath = uriToFilePath(params.textDocument.uri);
224255
const lsInfo = this.getLSAndScriptInfo(params.textDocument);
@@ -809,6 +840,17 @@ export class Session {
809840
// [here](https://github.com/angular/vscode-ng-language-service/issues/1828)
810841
codeActionKinds: [lsp.CodeActionKind.QuickFix],
811842
},
843+
semanticTokensProvider: {
844+
documentSelector: null,
845+
legend: {
846+
tokenTypes: [
847+
'class',
848+
],
849+
tokenModifiers: [],
850+
},
851+
full: true,
852+
range: true
853+
}
812854
},
813855
serverOptions,
814856
};

0 commit comments

Comments
 (0)