diff --git a/extensions/typescript-language-features/src/features/semanticTokens.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts index cc9a8ba582a..17623605687 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -7,35 +7,19 @@ import * as vscode from 'vscode'; import { ITypeScriptServiceClient, ExecConfig, ServerResponse } from '../typescriptService'; import * as Proto from '../protocol'; -enum TokenType { - 'class', - 'enum', - 'interface', - 'namespace', - 'typeParameter', - 'type', - 'parameter', - 'variable', - 'property', - 'constant', - 'function', - 'member', - _sentinel -} - - -enum TokenModifier { - 'declaration', - 'static', - 'async', - _sentinel +export function register(selector: vscode.DocumentSelector, client: ITypeScriptServiceClient) { + const provider = new SemanticTokensProvider(client); + return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); + } +/* + * Prototype of a SemanticTokensProvider, relying on the experimental `encodedSemanticClassifications-full` request from the TypeScript server. + * As the results retured by the TypeScript server are limited, we also add a Typescript plugin (typescript-vscode-sh-plugin) to enrich the returned token. + */ class SemanticTokensProvider implements vscode.SemanticTokensProvider { - constructor( - private readonly client: ITypeScriptServiceClient - ) { + constructor(private readonly client: ITypeScriptServiceClient) { } getLegend(): vscode.SemanticTokensLegend { @@ -58,79 +42,91 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { const versionBeforeRequest = document.version; - if (_options.ranges) { + const allTokenSpans: number[][] = []; - // const allArgs = _options.ranges.map(r => ({file, start: document.offsetAt(r.start), length: document.offsetAt(r.end) - document.offsetAt(r.start)})); + let requestArgs: ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs[] = []; + if (_options.ranges) { + requestArgs = _options.ranges.map(r => { const start = document.offsetAt(r.start); const length = document.offsetAt(r.end) - start; return { file, start, length }; }); + requestArgs = requestArgs.sort((a1, a2) => a1.start - a2.start); + } else { + requestArgs = [{ file, start: 0, length: document.getText().length }]; // full file + } + for (const requestArg of requestArgs) { + const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', requestArg, token); + if (response.type === 'response' && response.body) { + allTokenSpans.push(response.body.spans); + } else { + return null; + } } - const args: ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs = { - file: file, - start: 0, - length: document.getText().length, - }; - - const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', args, token); const versionAfterRequest = document.version; - if (versionBeforeRequest !== versionAfterRequest) { // A new request will come in soon... return null; } - if (response.type !== 'response') { - return null; - } - if (!response.body) { - return null; - } - const builder = new vscode.SemanticTokensBuilder(); + for (const tokenSpan of allTokenSpans) { + for (let i = 0, len = Math.floor(tokenSpan.length / 3); i < len; i++) { - const tsTokens = response.body.spans; - for (let i = 0, len = Math.floor(tsTokens.length / 3); i < len; i++) { + const tsClassification = tokenSpan[3 * i + 2]; + let tokenType = 0; + let tokenModifiers = 0; + if (tsClassification >= 0x100) { + // exendend classifications as returned by the typescript-vscode-sh-plugin + tokenType = (tsClassification >> 8) - 1; + tokenModifiers = tsClassification & 0xFF; + } else { + tokenType = tokenTypeMap[tsClassification]; + if (tokenType === undefined) { + continue; + } + } - const tsClassification = tsTokens[3 * i + 2]; - let tokenType = 0; - let tokenModifiers = 0; - if (tsClassification > 0xFF) { - // classifications as returned by the typescript-vscode-sh-plugin - tokenType = (tsClassification >> 8) - 1; - tokenModifiers = tsClassification & 0xFF; - } else { - tokenType = tokenTypeMap[tsClassification]; - if (tokenType === undefined) { - continue; + const offset = tokenSpan[3 * i]; + const length = tokenSpan[3 * i + 1]; + + // we can use the document's range conversion methods because the result is at the same version as the document + const startPos = document.positionAt(offset); + const endPos = document.positionAt(offset + length); + + for (let line = startPos.line; line <= endPos.line; line++) { + const startCharacter = (line === startPos.line ? startPos.character : 0); + const endCharacter = (line === endPos.line ? endPos.character : document.lineAt(line).text.length); + builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers); } } - - const offset = tsTokens[3 * i]; - const length = tsTokens[3 * i + 1]; - - // we can use the document's range conversion methods because - // the result is at the same version as the document - const startPos = document.positionAt(offset); - const endPos = document.positionAt(offset + length); - - for (let line = startPos.line; line <= endPos.line; line++) { - const startCharacter = (line === startPos.line ? startPos.character : 0); - const endCharacter = (line === endPos.line ? endPos.character : document.lineAt(line).text.length); - builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers); - } } - return new vscode.SemanticTokens(builder.build()); } - } -export function register( - selector: vscode.DocumentSelector, - client: ITypeScriptServiceClient -) { - const provider = new SemanticTokensProvider(client); - return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); +enum TokenType { + 'class', + 'enum', + 'interface', + 'namespace', + 'typeParameter', + 'type', + 'parameter', + 'variable', + 'property', + 'constant', + 'function', + 'member', + _sentinel } +enum TokenModifier { + 'declaration', + 'static', + 'async', + _sentinel +} + +// mapping for the original ExperimentalProtocol.ClassificationType from TypeScript (only used when plugin is not available) + const tokenTypeMap: number[] = []; tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = TokenType.class; tokenTypeMap[ExperimentalProtocol.ClassificationType.enumName] = TokenType.enum; @@ -221,5 +217,3 @@ export namespace ExperimentalProtocol { 'encodedSemanticClassifications-full': [ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs, ExperimentalProtocol.EncodedSemanticClassificationsResponse]; } } - -