Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
211 additions
and
0 deletions.
There are no files selected for viewing
210 changes: 210 additions & 0 deletions
210
extensions/typescript-language-features/src/features/semanticColoring.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
This comment has been minimized.
Sorry, something went wrong. |
||
} | ||
|
||
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]; | ||
} | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FYI @aeschli I will remove this because VS Code is written to handle this correctly.
i.e. in the core, the document version when the request is made is remembered and then when the result comes in, the result will be "adjusted" with the same adjusting code running when typing.