This commit is contained in:
Martin Aeschlimann 2020-01-08 17:08:48 +01:00
parent 2d752fce92
commit ed4173796d
2 changed files with 211 additions and 0 deletions

View file

@ -0,0 +1,210 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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
}
class SemanticTokensProvider implements vscode.SemanticTokensProvider {
constructor(
private readonly client: ITypeScriptServiceClient
) {
}
getLegend(): vscode.SemanticTokensLegend {
const tokenTypes = [];
for (let i = 0; i < TokenType._sentinel; i++) {
tokenTypes.push(TokenType[i]);
}
const tokenModifiers = [];
for (let i = 0; i < TokenModifier._sentinel; i++) {
tokenModifiers.push(TokenModifier[i]);
}
return new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);
}
async provideSemanticTokens(document: vscode.TextDocument, _options: vscode.SemanticTokensRequestOptions, token: vscode.CancellationToken): Promise<vscode.SemanticTokens | null> {
const file = this.client.toOpenedFilePath(document);
if (!file) {
return null;
}
const args: ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs = {
file: file,
start: 0,
length: document.getText().length,
};
const versionBeforeRequest = document.version;
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();
const tsTokens = response.body.spans;
for (let i = 0, len = Math.floor(tsTokens.length / 3); i < len; i++) {
const tokenType = tokenTypeMap[tsTokens[3 * i + 2]];
if (typeof tokenType === 'number') {
console.log(TokenType[tokenType]);
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, 0);
}
}
}
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());
}
const tokenTypeMap: number[] = [];
tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = TokenType.class;
tokenTypeMap[ExperimentalProtocol.ClassificationType.enumName] = TokenType.enum;
tokenTypeMap[ExperimentalProtocol.ClassificationType.interfaceName] = TokenType.interface;
tokenTypeMap[ExperimentalProtocol.ClassificationType.moduleName] = TokenType.namespace;
tokenTypeMap[ExperimentalProtocol.ClassificationType.typeParameterName] = TokenType.typeParameter;
tokenTypeMap[ExperimentalProtocol.ClassificationType.typeAliasName] = TokenType.type;
tokenTypeMap[ExperimentalProtocol.ClassificationType.parameterName] = TokenType.parameter;
export namespace ExperimentalProtocol {
export interface IExtendedTypeScriptServiceClient {
execute<K extends keyof ExperimentalProtocol.ExtendedTsServerRequests>(
command: K,
args: ExperimentalProtocol.ExtendedTsServerRequests[K][0],
token: vscode.CancellationToken,
config?: ExecConfig
): Promise<ServerResponse.Response<ExperimentalProtocol.ExtendedTsServerRequests[K][1]>>;
}
/**
* A request to get encoded semantic classifications for a span in the file
*/
export interface EncodedSemanticClassificationsRequest extends Proto.FileRequest {
arguments: EncodedSemanticClassificationsRequestArgs;
}
/**
* Arguments for EncodedSemanticClassificationsRequest request.
*/
export interface EncodedSemanticClassificationsRequestArgs extends Proto.FileRequestArgs {
/**
* Start position of the span.
*/
start: number;
/**
* Length of the span.
*/
length: number;
}
export const enum EndOfLineState {
None,
InMultiLineCommentTrivia,
InSingleQuoteStringLiteral,
InDoubleQuoteStringLiteral,
InTemplateHeadOrNoSubstitutionTemplate,
InTemplateMiddleOrTail,
InTemplateSubstitutionPosition,
}
export const enum ClassificationType {
comment = 1,
identifier = 2,
keyword = 3,
numericLiteral = 4,
operator = 5,
stringLiteral = 6,
regularExpressionLiteral = 7,
whiteSpace = 8,
text = 9,
punctuation = 10,
className = 11,
enumName = 12,
interfaceName = 13,
moduleName = 14,
typeParameterName = 15,
typeAliasName = 16,
parameterName = 17,
docCommentTagName = 18,
jsxOpenTagName = 19,
jsxCloseTagName = 20,
jsxSelfClosingTagName = 21,
jsxAttribute = 22,
jsxText = 23,
jsxAttributeStringLiteralValue = 24,
bigintLiteral = 25,
}
export interface EncodedSemanticClassificationsResponse extends Proto.Response {
body?: {
endOfLineState: EndOfLineState;
spans: number[];
};
}
export interface ExtendedTsServerRequests {
'encodedSemanticClassifications-full': [ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs, ExperimentalProtocol.EncodedSemanticClassificationsResponse];
}
}

View file

@ -78,6 +78,7 @@ export default class LanguageProvider extends Disposable {
import('./features/signatureHelp').then(provider => this._register(provider.register(selector, this.client))),
import('./features/tagClosing').then(provider => this._register(provider.register(selector, this.description.id, this.client))),
import('./features/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))),
import('./features/semanticColoring').then(provider => this._register(provider.register(selector, this.client))),
]);
}