diff --git a/extensions/typescript-language-features/src/features/semanticColoring.ts b/extensions/typescript-language-features/src/features/semanticColoring.ts new file mode 100644 index 00000000000..d6f22e6ce17 --- /dev/null +++ b/extensions/typescript-language-features/src/features/semanticColoring.ts @@ -0,0 +1,154 @@ +/*--------------------------------------------------------------------------------------------- + * 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, ExperimentalProtocol } from '../typescriptService'; + +class SemanticColoringProvider implements vscode.SemanticColoringProvider { + + constructor( + private readonly client: ITypeScriptServiceClient + ) { + } + + getLegend(): vscode.SemanticColoringLegend { + const tokens: string[] = []; + + tokens[ExperimentalProtocol.ClassificationType.comment] = 'comment'; // ok + tokens[ExperimentalProtocol.ClassificationType.identifier] = 'identifier'; + tokens[ExperimentalProtocol.ClassificationType.keyword] = 'keyword'; + tokens[ExperimentalProtocol.ClassificationType.numericLiteral] = 'numericLiteral'; + tokens[ExperimentalProtocol.ClassificationType.operator] = 'operator'; + tokens[ExperimentalProtocol.ClassificationType.stringLiteral] = 'stringLiteral'; + tokens[ExperimentalProtocol.ClassificationType.regularExpressionLiteral] = 'regularExpressionLiteral'; + tokens[ExperimentalProtocol.ClassificationType.whiteSpace] = 'whiteSpace'; + tokens[ExperimentalProtocol.ClassificationType.text] = 'text'; + tokens[ExperimentalProtocol.ClassificationType.punctuation] = 'punctuation'; + tokens[ExperimentalProtocol.ClassificationType.className] = 'class'; // ok + tokens[ExperimentalProtocol.ClassificationType.enumName] = 'enum'; // ok + tokens[ExperimentalProtocol.ClassificationType.interfaceName] = 'interface'; // ok + tokens[ExperimentalProtocol.ClassificationType.moduleName] = 'moduleName'; + tokens[ExperimentalProtocol.ClassificationType.typeParameterName] = 'parameterType'; // ok + tokens[ExperimentalProtocol.ClassificationType.typeAliasName] = 'typeAliasName'; + tokens[ExperimentalProtocol.ClassificationType.parameterName] = 'parameter'; // ok + tokens[ExperimentalProtocol.ClassificationType.docCommentTagName] = 'docCommentTagName'; + tokens[ExperimentalProtocol.ClassificationType.jsxOpenTagName] = 'jsxOpenTagName'; + tokens[ExperimentalProtocol.ClassificationType.jsxCloseTagName] = 'jsxCloseTagName'; + tokens[ExperimentalProtocol.ClassificationType.jsxSelfClosingTagName] = 'jsxSelfClosingTagName'; + tokens[ExperimentalProtocol.ClassificationType.jsxAttribute] = 'jsxAttribute'; + tokens[ExperimentalProtocol.ClassificationType.jsxText] = 'jsxText'; + tokens[ExperimentalProtocol.ClassificationType.jsxAttributeStringLiteralValue] = 'jsxAttributeStringLiteralValue'; + tokens[ExperimentalProtocol.ClassificationType.bigintLiteral] = 'bigintLiteral'; + + return new vscode.SemanticColoringLegend(tokens, []); + } + + async provideSemanticColoring(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { + 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.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 tsTokens = response.body.spans; + + let result: number[] = []; + let resultLen = 0; + const pushResultToken = (line: number, startCharacter: number, endCharacter: number, tokenType: number): void => { + result[resultLen++] = line; + result[resultLen++] = startCharacter; + result[resultLen++] = endCharacter; + result[resultLen++] = tokenType; + result[resultLen++] = 0; + }; + + for (let i = 0, len = Math.floor(tsTokens.length / 3); i < len; i++) { + const offset = tsTokens[3 * i]; + const length = tsTokens[3 * i + 1]; + const tokenType = tsTokens[3 * i + 2]; + + // 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); + pushResultToken(line, startCharacter, endCharacter, tokenType); + } + } + + console.log(result); + return new vscode.SemanticColoring([new vscode.SemanticColoringArea(0, new Uint32Array(result))]); + } + +} + +export function register( + client: ITypeScriptServiceClient +) { + const provider = new SemanticColoringProvider(client); + // return vscode.languages.registerSemanticColoringProvider(selector, ) + + const run = async () => { + const ed = vscode.window.activeTextEditor; + if (!ed) { + return; + } + // const doc = ed.document; + const cancellationTokenSource = new vscode.CancellationTokenSource(); + provider.provideSemanticColoring(ed.document, cancellationTokenSource.token); + // const file = client.toOpenedFilePath(doc); + // if (!file) { + // return; + // } + // const args: ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs = { + // file: file, + // start: 0, + // length: doc.getText().length, + // }; + + // const response = await client.execute('encodedSemanticClassifications-full', args, cancellationTokenSource.token); + + // if (response.type !== 'response') { + // return; + // } + // if (!response.body) { + // return; + // } + // console.log(response.body); + }; + + vscode.window.onDidChangeActiveTextEditor(run); + run(); + + console.log(`I am running...`); + + return vscode.Disposable.from(); + // return vscode.languages.registerRenameProvider(selector, + // new TypeScriptRenameProvider(client, fileConfigurationManager)); +} diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 4210ba2ba20..6fdd2f3cb89 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -74,6 +74,7 @@ export default class LanguageProvider extends Disposable { import('./features/references').then(provider => this._register(provider.register(selector, this.client))), import('./features/referencesCodeLens').then(provider => this._register(provider.register(selector, this.description.id, this.client, cachedResponse))), import('./features/rename').then(provider => this._register(provider.register(selector, this.client, this.fileConfigurationManager))), + import('./features/semanticColoring').then(provider => this._register(provider.register(selector, this.client))), import('./features/smartSelect').then(provider => this._register(provider.register(selector, this.client))), 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))), @@ -141,4 +142,4 @@ export default class LanguageProvider extends Disposable { private get _diagnosticLanguage() { return this.description.diagnosticLanguage; } -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index ba396ad2fd2..3fd9b4fa32a 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -26,6 +26,74 @@ export namespace ServerResponse { export type Response = T | Cancelled | typeof NoContent; } +export namespace ExperimentalProtocol { + /** + * 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[]; + }; + } +} + interface StandardTsServerRequests { 'applyCodeActionCommand': [Proto.ApplyCodeActionCommandRequestArgs, Proto.ApplyCodeActionCommandResponse]; 'completionEntryDetails': [Proto.CompletionDetailsRequestArgs, Proto.CompletionDetailsResponse]; @@ -36,6 +104,7 @@ interface StandardTsServerRequests { 'definitionAndBoundSpan': [Proto.FileLocationRequestArgs, Proto.DefinitionInfoAndBoundSpanReponse]; 'docCommentTemplate': [Proto.FileLocationRequestArgs, Proto.DocCommandTemplateResponse]; 'documentHighlights': [Proto.DocumentHighlightsRequestArgs, Proto.DocumentHighlightsResponse]; + 'encodedSemanticClassifications-full': [ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs, ExperimentalProtocol.EncodedSemanticClassificationsResponse]; 'format': [Proto.FormatRequestArgs, Proto.FormatResponse]; 'formatonkey': [Proto.FormatOnKeyRequestArgs, Proto.FormatResponse]; 'getApplicableRefactors': [Proto.GetApplicableRefactorsRequestArgs, Proto.GetApplicableRefactorsResponse]; @@ -138,4 +207,4 @@ export interface ITypeScriptServiceClient { * Cancel on going geterr requests and re-queue them after `f` has been evaluated. */ interruptGetErr(f: () => R): R; -} \ No newline at end of file +} diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 2d518707ad5..ebae08b5d87 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -151,6 +151,63 @@ declare module 'vscode' { //#endregion + //#region Alex - semantic coloring + + export class SemanticColoringLegend { + public readonly tokenTypes: string[]; + public readonly tokenModifiers: string[]; + + constructor(tokenTypes: string[], tokenModifiers: string[]); + } + + export class SemanticColoringArea { + /** + * The zero-based line value where this token block begins. + */ + public readonly line: number; + /** + * The actual token block encoded data. + */ + public readonly data: Uint32Array; + + constructor(line: number, data: Uint32Array); + } + + export class SemanticColoring { + public readonly areas: SemanticColoringArea[]; + + constructor(areas: SemanticColoringArea[]); + } + + /** + * The semantic coloring provider interface defines the contract between extensions and + * semantic coloring. + * + * + */ + export interface SemanticColoringProvider { + + getLegend(): SemanticColoringLegend; + + provideSemanticColoring(document: TextDocument, token: CancellationToken): ProviderResult; + } + + export namespace languages { + /** + * Register a semantic coloring provider. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A semantic coloring provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerSemanticColoringProvider(selector: DocumentSelector, provider: SemanticColoringProvider): Disposable; + } + + //#endregion // #region Joh - code insets diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 59edb3be7bd..d8e9b0b82d1 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -350,6 +350,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, firstTriggerCharacter: string, ...moreTriggerCharacters: string[]): vscode.Disposable { return extHostLanguageFeatures.registerOnTypeFormattingEditProvider(extension, checkSelector(selector), provider, [firstTriggerCharacter].concat(moreTriggerCharacters)); }, + registerSemanticColoringProvider(selector: vscode.DocumentSelector, provider: vscode.SemanticColoringProvider): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerSemanticColoringProvider(extension, checkSelector(selector), provider); + }, registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, firstItem?: string | vscode.SignatureHelpProviderMetadata, ...remaining: string[]): vscode.Disposable { if (typeof firstItem === 'object') { return extHostLanguageFeatures.registerSignatureHelpProvider(extension, checkSelector(selector), provider, firstItem); @@ -869,6 +873,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I RelativePattern: extHostTypes.RelativePattern, ResolvedAuthority: extHostTypes.ResolvedAuthority, RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, + SemanticColoring: extHostTypes.SemanticColoring, + SemanticColoringArea: extHostTypes.SemanticColoringArea, + SemanticColoringLegend: extHostTypes.SemanticColoringLegend, Selection: extHostTypes.Selection, SelectionRange: extHostTypes.SelectionRange, ShellExecution: extHostTypes.ShellExecution, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f2bc8f0e717..ce5e86b8d28 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -346,6 +346,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; + $registerSemanticColoringProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 0a198614c55..65c37e5f6a6 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -613,6 +613,50 @@ class RenameAdapter { } } +class SemanticColoringAdapter { + + // private readonly _cache = new Cache('SignatureHelp'); + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.SemanticColoringProvider, + ) { } + + provideSignatureHelp(resource: URI, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); + const vscodeContext = this.reviveContext(context); + + return asPromise(() => this._provider.provideSignatureHelp(doc, pos, token, vscodeContext)).then(value => { + if (value) { + const id = this._cache.add([value]); + return { ...typeConvert.SignatureHelp.from(value), id }; + } + return undefined; + }); + } + + private reviveContext(context: extHostProtocol.ISignatureHelpContextDto): vscode.SignatureHelpContext { + let activeSignatureHelp: vscode.SignatureHelp | undefined = undefined; + if (context.activeSignatureHelp) { + const revivedSignatureHelp = typeConvert.SignatureHelp.to(context.activeSignatureHelp); + const saved = this._cache.get(context.activeSignatureHelp.id, 0); + if (saved) { + activeSignatureHelp = saved; + activeSignatureHelp.activeSignature = revivedSignatureHelp.activeSignature; + activeSignatureHelp.activeParameter = revivedSignatureHelp.activeParameter; + } else { + activeSignatureHelp = revivedSignatureHelp; + } + } + return { ...context, activeSignatureHelp }; + } + + releaseSignatureHelp(id: number): any { + this._cache.delete(id); + } +} + class SuggestAdapter { static supportsResolving(provider: vscode.CompletionItemProvider): boolean { @@ -768,6 +812,7 @@ class SuggestAdapter { } } + class SignatureHelpAdapter { private readonly _cache = new Cache('SignatureHelp'); @@ -1038,8 +1083,9 @@ class CallHierarchyAdapter { type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter - | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter - | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; + | SemanticColoringAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter + | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter + | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; class AdapterData { constructor( @@ -1363,6 +1409,16 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveRenameLocation(URI.revive(resource), position, token), undefined); } + //#region semantic coloring + + registerSemanticColoringProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SemanticColoringProvider): vscode.Disposable { + const handle = this._addNewAdapter(new SemanticColoringAdapter(this._documents, provider), extension); + this._proxy.$registerSemanticColoringProvider(handle, this._transformDocumentSelector(selector)); + return this._createDisposable(handle); + } + + //#endregion + // --- suggestion registerCompletionItemProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index e32da709002..d8a91af0024 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2352,6 +2352,38 @@ export enum CommentMode { //#endregion +//#region Semantic Coloring + +export class SemanticColoringLegend { + public readonly tokenTypes: string[]; + public readonly tokenModifiers: string[]; + + constructor(tokenTypes: string[], tokenModifiers: string[]) { + this.tokenTypes = tokenTypes; + this.tokenModifiers = tokenModifiers; + } +} + +export class SemanticColoringArea { + public readonly line: number; + public readonly data: Uint32Array; + + constructor(line: number, data: Uint32Array) { + this.line = line; + this.data = data; + } +} + +export class SemanticColoring { + public readonly areas: SemanticColoringArea[]; + + constructor(areas: SemanticColoringArea[]) { + this.areas = areas; + } +} + +//#endregion + //#region debug export enum DebugConsoleMode { /**