This commit is contained in:
Martin Aeschlimann 2020-01-10 15:37:37 +01:00
parent 900100b745
commit 4032ce7241

View file

@ -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];
}
}