From 229dd232020b9b7b14b06f42d7ee87a1d80a5e7b Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Thu, 5 Oct 2023 14:03:29 +0800 Subject: [PATCH 1/8] references/implementation codelens --- packages/language-server/src/ls-config.ts | 17 ++ .../language-server/src/plugins/PluginHost.ts | 45 ++- .../language-server/src/plugins/interfaces.ts | 22 +- .../plugins/typescript/TypeScriptPlugin.ts | 31 +- .../typescript/features/CodeLensProvider.ts | 286 ++++++++++++++++++ .../features/FindReferencesProvider.ts | 45 ++- .../features/ImplementationProvider.ts | 17 +- packages/language-server/src/server.ts | 25 +- packages/svelte-vscode/src/extension.ts | 4 + packages/svelte-vscode/src/middlewares.ts | 43 +++ 10 files changed, 510 insertions(+), 25 deletions(-) create mode 100644 packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts create mode 100644 packages/svelte-vscode/src/middlewares.ts diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index 623a139b8..66cc53612 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -201,6 +201,8 @@ export interface TSUserConfig { suggest?: TSSuggestConfig; format?: TsFormatConfig; inlayHints?: TsInlayHintsConfig; + referencesCodeLens?: TsReferenceCodeLensConfig; + implementationCodeLens?: { enabled: boolean }; } /** @@ -250,6 +252,11 @@ export interface TsInlayHintsConfig { variableTypes: { enabled: boolean } | undefined; } +export interface TsReferenceCodeLensConfig { + showOnAllFunctions?: boolean | undefined; + enabled: boolean; +} + export type TsUserConfigLang = 'typescript' | 'javascript'; /** @@ -283,6 +290,11 @@ export class LSConfigManager { typescript: {}, javascript: {} }; + private rawTsUserConfig: Record = { + typescript: {}, + javascript: {} + } + private resolvedAutoImportExcludeCache = new FileMap(); private tsFormatCodeOptions: Record = { typescript: this.getDefaultFormatCodeOptions(), @@ -394,6 +406,7 @@ export class LSConfigManager { (['typescript', 'javascript'] as const).forEach((lang) => { if (config[lang]) { this._updateTsUserPreferences(lang, config[lang]); + this.rawTsUserConfig[lang] = config[lang]; } }); this.notifyListeners(); @@ -484,6 +497,10 @@ export class LSConfigManager { }; } + getRawTsUserConfig(lang: TsUserConfigLang): TSUserConfig { + return this.rawTsUserConfig[lang]; + } + updateCssConfig(config: CssConfig | undefined): void { this.cssConfig = config; this.notifyListeners(); diff --git a/packages/language-server/src/plugins/PluginHost.ts b/packages/language-server/src/plugins/PluginHost.ts index c0bededfc..b1ef9136f 100644 --- a/packages/language-server/src/plugins/PluginHost.ts +++ b/packages/language-server/src/plugins/PluginHost.ts @@ -7,6 +7,7 @@ import { CancellationToken, CodeAction, CodeActionContext, + CodeLens, Color, ColorInformation, ColorPresentation, @@ -407,13 +408,14 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { async findReferences( textDocument: TextDocumentIdentifier, position: Position, - context: ReferenceContext + context: ReferenceContext, + cancellationToken?: CancellationToken ): Promise { const document = this.getDocument(textDocument.uri); return await this.execute( 'findReferences', - [document, position, context], + [document, position, context, cancellationToken], ExecuteMode.FirstNonNull, 'high' ); @@ -515,13 +517,14 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { getImplementation( textDocument: TextDocumentIdentifier, - position: Position + position: Position, + cancellationToken?: CancellationToken ): Promise { const document = this.getDocument(textDocument.uri); return this.execute( 'getImplementation', - [document, position], + [document, position, cancellationToken], ExecuteMode.FirstNonNull, 'high' ); @@ -595,6 +598,40 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { ); } + async getCodeLens(textDocument: TextDocumentIdentifier) { + const document = this.getDocument(textDocument.uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } + + return await this.execute( + 'getCodeLens', + [document], + ExecuteMode.FirstNonNull, + 'smart' + ); + } + + async resolveCodeLens( + textDocument: TextDocumentIdentifier, + codeLens: CodeLens, + cancellationToken: CancellationToken + ) { + const document = this.getDocument(textDocument.uri); + if (!document) { + throw new Error('Cannot call methods on an unopened document'); + } + + return ( + (await this.execute( + 'resolveCodeLens', + [document, codeLens, cancellationToken], + ExecuteMode.FirstNonNull, + 'high' + )) ?? codeLens + ); + } + onWatchFileChanges(onWatchFileChangesParas: OnWatchFileChangesPara[]): void { for (const support of this.plugins) { support.onWatchFileChanges?.(onWatchFileChangesParas); diff --git a/packages/language-server/src/plugins/interfaces.ts b/packages/language-server/src/plugins/interfaces.ts index b95836c65..278578a06 100644 --- a/packages/language-server/src/plugins/interfaces.ts +++ b/packages/language-server/src/plugins/interfaces.ts @@ -13,6 +13,7 @@ import { CallHierarchyOutgoingCall, CodeAction, CodeActionContext, + CodeLens, Color, ColorInformation, ColorPresentation, @@ -149,7 +150,8 @@ export interface FindReferencesProvider { findReferences( document: Document, position: Position, - context: ReferenceContext + context: ReferenceContext, + cancellationToken?: CancellationToken ): Promise; } @@ -186,7 +188,11 @@ export interface LinkedEditingRangesProvider { } export interface ImplementationProvider { - getImplementation(document: Document, position: Position): Resolvable; + getImplementation( + document: Document, + position: Position, + cancellationToken?: CancellationToken + ): Resolvable; } export interface TypeDefinitionProvider { @@ -210,6 +216,15 @@ export interface CallHierarchyProvider { ): Resolvable; } +export interface CodeLensProvider { + getCodeLens(document: Document): Resolvable; + resolveCodeLens( + document: Document, + codeLensToResolve: CodeLens, + cancellationToken?: CancellationToken + ): Resolvable; +} + export interface OnWatchFileChangesPara { fileName: string; changeType: FileChangeType; @@ -251,7 +266,8 @@ type ProviderBase = DiagnosticsProvider & ImplementationProvider & TypeDefinitionProvider & InlayHintProvider & - CallHierarchyProvider; + CallHierarchyProvider & + CodeLensProvider; export type LSProvider = ProviderBase & BackwardsCompatibleDefinitionsProvider; diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts index fa7709bea..2dc21e8db 100644 --- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts +++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts @@ -6,6 +6,7 @@ import { CancellationToken, CodeAction, CodeActionContext, + CodeLens, CompletionContext, CompletionList, DefinitionLink, @@ -54,7 +55,8 @@ import { TypeDefinitionProvider, UpdateImportsProvider, UpdateTsOrJsFile, - CallHierarchyProvider + CallHierarchyProvider, + CodeLensProvider } from '../interfaces'; import { CodeActionsProviderImpl } from './features/CodeActionsProvider'; import { @@ -91,6 +93,7 @@ import { symbolKindFromString } from './utils'; import { CallHierarchyProviderImpl } from './features/CallHierarchyProvider'; +import { CodeLensProviderImpl } from './features/CodeLensProvider'; export class TypeScriptPlugin implements @@ -111,6 +114,7 @@ export class TypeScriptPlugin TypeDefinitionProvider, InlayHintProvider, CallHierarchyProvider, + CodeLensProvider, OnWatchFileChanges, CompletionsProvider, UpdateTsOrJsFile @@ -135,6 +139,7 @@ export class TypeScriptPlugin private readonly typeDefinitionProvider: TypeDefinitionProviderImpl; private readonly inlayHintProvider: InlayHintProviderImpl; private readonly callHierarchyProvider: CallHierarchyProviderImpl; + private readonly codLensProvider: CodeLensProviderImpl; constructor( configManager: LSConfigManager, @@ -179,6 +184,12 @@ export class TypeScriptPlugin this.lsAndTsDocResolver, workspaceUris ); + this.codLensProvider = new CodeLensProviderImpl( + this.lsAndTsDocResolver, + this.findReferencesProvider, + this.implementationProvider, + this.configManager + ); } async getDiagnostics( @@ -585,8 +596,12 @@ export class TypeScriptPlugin ); } - async getImplementation(document: Document, position: Position): Promise { - return this.implementationProvider.getImplementation(document, position); + async getImplementation( + document: Document, + position: Position, + cancellationToken?: CancellationToken + ): Promise { + return this.implementationProvider.getImplementation(document, position, cancellationToken); } async getTypeDefinition(document: Document, position: Position): Promise { @@ -631,7 +646,15 @@ export class TypeScriptPlugin return this.callHierarchyProvider.getOutgoingCalls(item, cancellationToken); } - private async getLSAndTSDoc(document: Document) { + getCodeLens(document: Document): Promise { + return this.codLensProvider.getCodeLens(document); + } + + resolveCodeLens(document: Document, codeLensToResolve: CodeLens, cancellationToken?: CancellationToken): Promise { + return this.codLensProvider.resolveCodeLens(document, codeLensToResolve, cancellationToken); + } + + private getLSAndTSDoc(document: Document) { return this.lsAndTsDocResolver.getLSAndTSDoc(document); } diff --git a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts new file mode 100644 index 000000000..dd31d6f3e --- /dev/null +++ b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts @@ -0,0 +1,286 @@ +import ts from 'typescript'; +import { CancellationToken, CodeLens, Range } from 'vscode-languageserver'; +import { Document, mapRangeToOriginal } from '../../../lib/documents'; +import { LSConfigManager, TSUserConfig } from '../../../ls-config'; +import { CodeLensProvider, FindReferencesProvider, ImplementationProvider } from '../../interfaces'; +import { SvelteDocumentSnapshot } from '../DocumentSnapshot'; +import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; +import { convertRange, isGeneratedSvelteComponentName } from '../utils'; + +type CodeLensType = 'reference' | 'implementation'; + +interface CodeLensCollector { + type: CodeLensType; + collect: ( + tsDoc: SvelteDocumentSnapshot, + item: ts.NavigationTree, + parent: ts.NavigationTree | undefined + ) => Range | undefined; +} + +export class CodeLensProviderImpl implements CodeLensProvider { + constructor( + private readonly lsAndTsDocResolver: LSAndTSDocResolver, + private readonly referenceProvider: FindReferencesProvider, + private readonly implementationProvider: ImplementationProvider, + private readonly configManager: LSConfigManager + ) {} + + async getCodeLens(document: Document): Promise { + const { lang, tsDoc } = await this.lsAndTsDocResolver.getLsForSyntheticOperations(document); + + const results: [CodeLensType, Range][] = []; + const navigationTree = lang.getNavigationTree(tsDoc.filePath); + + const collectors: CodeLensCollector[] = []; + + const vscodeTsConfig = this.configManager.getRawTsUserConfig( + tsDoc.scriptKind === ts.ScriptKind.TS ? 'typescript' : 'javascript' + ); + + if (vscodeTsConfig.referencesCodeLens?.enabled) { + collectors.push({ + type: 'reference', + collect: (tsDoc, item, parent) => + this.extractReferenceLocation(tsDoc, item, parent, vscodeTsConfig) + }); + } + + if ( + tsDoc.scriptKind === ts.ScriptKind.TS && + vscodeTsConfig.implementationCodeLens?.enabled + ) { + collectors.push({ + type: 'implementation', + collect: (tsDoc, item) => this.extractImplementationLocation(tsDoc, item) + }); + } + + if (!collectors.length) { + return null; + } + + const renderFunction = navigationTree?.childItems?.find((item) => item.text === 'render'); + if (renderFunction) { + // pretty rare that there is anything to show in the template, so we skip it + const notTemplate = renderFunction.childItems?.filter( + (item) => item.text !== '' + ); + renderFunction.childItems = notTemplate; + } + + this.walkTree(tsDoc, navigationTree, undefined, results, collectors); + + const uri = document.uri; + return results.map(([type, range]) => CodeLens.create(range, { type, uri })); + } + + /** + * https://github.com/microsoft/vscode/blob/062ba1ed6c2b9ff4819f4f7dad76de3fde0044ab/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts#L61 + */ + private extractReferenceLocation( + tsDoc: SvelteDocumentSnapshot, + item: ts.NavigationTree, + parent: ts.NavigationTree | undefined, + config: TSUserConfig + ): Range | undefined { + if (parent && parent.kind === ts.ScriptElementKind.enumElement) { + return this.getSymbolRange(tsDoc, item); + } + + switch (item.kind) { + case ts.ScriptElementKind.functionElement: { + const showOnAllFunctions = config.referencesCodeLens?.showOnAllFunctions; + + if (showOnAllFunctions) { + return this.getSymbolRange(tsDoc, item); + } + + if (this.isExported(item, tsDoc)) { + return this.getSymbolRange(tsDoc, item); + } + break; + } + + case ts.ScriptElementKind.constElement: + case ts.ScriptElementKind.letElement: + case ts.ScriptElementKind.variableElement: + // Only show references for exported variables + if (this.isExported(item, tsDoc)) { + return this.getSymbolRange(tsDoc, item); + } + break; + + case ts.ScriptElementKind.classElement: + if (item.text === '') { + break; + } + return this.getSymbolRange(tsDoc, item); + + case ts.ScriptElementKind.interfaceElement: + case ts.ScriptElementKind.typeElement: + case ts.ScriptElementKind.enumElement: + return this.getSymbolRange(tsDoc, item); + + case ts.ScriptElementKind.memberFunctionElement: + case ts.ScriptElementKind.memberGetAccessorElement: + case ts.ScriptElementKind.memberSetAccessorElement: + case ts.ScriptElementKind.constructorImplementationElement: + case ts.ScriptElementKind.memberVariableElement: + if (parent?.spans[0].start === item.spans[0].start) { + return undefined; + } + + // Only show if parent is a class type object (not a literal) + switch (parent?.kind) { + case ts.ScriptElementKind.classElement: + case ts.ScriptElementKind.interfaceElement: + case ts.ScriptElementKind.typeElement: + return this.getSymbolRange(tsDoc, item); + } + break; + } + + return undefined; + } + + private isExported(item: ts.NavigationTree, tsDoc: SvelteDocumentSnapshot): boolean { + return !tsDoc.parserError && item.kindModifiers.match(/\bexport\b/g) !== null; + } + + /** + * https://github.com/microsoft/vscode/blob/062ba1ed6c2b9ff4819f4f7dad76de3fde0044ab/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts#L66 + */ + private extractImplementationLocation( + tsDoc: SvelteDocumentSnapshot, + item: ts.NavigationTree + ): Range | undefined { + switch (item.kind) { + case ts.ScriptElementKind.interfaceElement: + return this.getSymbolRange(tsDoc, item); + + case ts.ScriptElementKind.classElement: + case ts.ScriptElementKind.memberFunctionElement: + case ts.ScriptElementKind.memberVariableElement: + case ts.ScriptElementKind.memberGetAccessorElement: + case ts.ScriptElementKind.memberSetAccessorElement: + if (item.kindModifiers.match(/\babstract\b/g)) { + return this.getSymbolRange(tsDoc, item); + } + break; + } + return undefined; + } + + private getSymbolRange( + tsDoc: SvelteDocumentSnapshot, + item: ts.NavigationTree + ): Range | undefined { + if (!item.nameSpan) { + return; + } + + const range = mapRangeToOriginal(tsDoc, convertRange(tsDoc, item.spans[0])); + + if (range.start.line >= 0 && range.end.line >= 0) { + return range; + } + + // only map to the start of file if it's a generated component + if (isGeneratedSvelteComponentName(item.text)) { + return { + start: { line: 0, character: 0 }, + end: { line: 0, character: 0 } + }; + } + } + + private walkTree( + tsDoc: SvelteDocumentSnapshot, + item: ts.NavigationTree, + parent: ts.NavigationTree | undefined, + results: [CodeLensType, Range][], + collectors: CodeLensCollector[] + ) { + for (const collector of collectors) { + const range = collector.collect(tsDoc, item, parent); + if (range) { + results.push([collector.type, range]); + } + } + + item.childItems?.forEach((child) => this.walkTree(tsDoc, child, item, results, collectors)); + } + + async resolveCodeLens( + textDocument: Document, + codeLensToResolve: CodeLens, + cancellationToken?: CancellationToken + ): Promise { + if (codeLensToResolve.data.type === 'reference') { + return await this.resolveReferenceCodeLens( + textDocument, + codeLensToResolve, + cancellationToken + ); + } + + if (codeLensToResolve.data.type === 'implementation') { + return await this.resolveImplementationCodeLens( + textDocument, + codeLensToResolve, + cancellationToken + ); + } + + return codeLensToResolve; + } + + private async resolveReferenceCodeLens( + textDocument: Document, + codeLensToResolve: CodeLens, + cancellationToken?: CancellationToken + ) { + const references = + (await this.referenceProvider.findReferences( + textDocument, + codeLensToResolve.range.start, + { includeDeclaration: false }, + cancellationToken + )) ?? []; + + codeLensToResolve.command = { + title: references.length === 1 ? `1 reference` : `${references.length} references`, + // language clients need to map this to the corresponding command in each editor + // see example in svelte-vscode/src/middlewares.ts + command: '', + arguments: [textDocument.uri, codeLensToResolve.range.start, references] + }; + + return codeLensToResolve; + } + + private async resolveImplementationCodeLens( + textDocument: Document, + codeLensToResolve: CodeLens, + cancellationToken?: CancellationToken + ) { + const implementations = + (await this.implementationProvider.getImplementation( + textDocument, + codeLensToResolve.range.start, + cancellationToken + )) ?? []; + + codeLensToResolve.command = { + title: + implementations.length === 1 + ? `1 implementation` + : `${implementations.length} implementations`, + command: '', + arguments: [textDocument.uri, codeLensToResolve.range.start, implementations] + }; + + return codeLensToResolve; + } +} diff --git a/packages/language-server/src/plugins/typescript/features/FindReferencesProvider.ts b/packages/language-server/src/plugins/typescript/features/FindReferencesProvider.ts index c530398ea..ff1d99772 100644 --- a/packages/language-server/src/plugins/typescript/features/FindReferencesProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/FindReferencesProvider.ts @@ -1,5 +1,5 @@ import ts from 'typescript'; -import { Location, Position, ReferenceContext } from 'vscode-languageserver'; +import { CancellationToken, Location, Position, ReferenceContext } from 'vscode-languageserver'; import { Document } from '../../../lib/documents'; import { flatten, isNotNullOrUndefined, pathToUrl } from '../../../utils'; import { FindComponentReferencesProvider, FindReferencesProvider } from '../../interfaces'; @@ -28,13 +28,20 @@ export class FindReferencesProviderImpl implements FindReferencesProvider { async findReferences( document: Document, position: Position, - context: ReferenceContext + context: ReferenceContext, + cancellationToken?: CancellationToken ): Promise { - if (this.isScriptStartOrEndTag(position, document)) { + if ( + this.isPositionForComponentCodeLens(position) || + this.isScriptStartOrEndTag(position, document) + ) { return this.componentReferencesProvider.findComponentReferences(document.uri); } const { lang, tsDoc } = await this.getLSAndTSDoc(document); + if (cancellationToken?.isCancellationRequested) { + return null; + } const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position)); const rawReferences = lang.findReferences( @@ -61,10 +68,20 @@ export class FindReferencesProviderImpl implements FindReferencesProvider { } const references = flatten(rawReferences.map((ref) => ref.references)); - references.push(...(await this.getStoreReferences(references, tsDoc, snapshots, lang))); + references.push( + ...(await this.getStoreReferences( + references, + tsDoc, + snapshots, + lang, + cancellationToken + )) + ); const locations = await Promise.all( - references.map(async (ref) => this.mapReference(ref, context, snapshots)) + references.map(async (ref) => + this.mapReference(ref, context, snapshots, cancellationToken) + ) ); return ( @@ -88,6 +105,10 @@ export class FindReferencesProviderImpl implements FindReferencesProvider { ); } + private isPositionForComponentCodeLens(position: Position) { + return position.line === 0 && position.character === 0; + } + /** * If references of a $store are searched, also find references for the corresponding store * and vice versa. @@ -96,7 +117,8 @@ export class FindReferencesProviderImpl implements FindReferencesProvider { references: ts.ReferencedSymbolEntry[], tsDoc: SvelteDocumentSnapshot, snapshots: SnapshotMap, - lang: ts.LanguageService + lang: ts.LanguageService, + cancellationToken: CancellationToken | undefined ): Promise { // If user started finding references at $store, find references for store, too let storeReferences: ts.ReferencedSymbolEntry[] = []; @@ -123,6 +145,10 @@ export class FindReferencesProviderImpl implements FindReferencesProvider { const $storeReferences: ts.ReferencedSymbolEntry[] = []; for (const ref of [...references, ...storeReferences]) { const snapshot = await snapshots.retrieve(ref.fileName); + if (cancellationToken?.isCancellationRequested) { + return []; + } + if ( !( isTextSpanInGeneratedCode(snapshot.getFullText(), ref.textSpan) && @@ -174,7 +200,8 @@ export class FindReferencesProviderImpl implements FindReferencesProvider { private async mapReference( ref: ts.ReferencedSymbolEntry, context: ReferenceContext, - snapshots: SnapshotMap + snapshots: SnapshotMap, + cancellationToken: CancellationToken | undefined ) { if (!context.includeDeclaration && ref.isDefinition) { return null; @@ -182,6 +209,10 @@ export class FindReferencesProviderImpl implements FindReferencesProvider { const snapshot = await snapshots.retrieve(ref.fileName); + if (cancellationToken?.isCancellationRequested) { + return null; + } + if (isTextSpanInGeneratedCode(snapshot.getFullText(), ref.textSpan)) { return null; } diff --git a/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts b/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts index a74bb550d..e4a91c1bb 100644 --- a/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/ImplementationProvider.ts @@ -1,4 +1,4 @@ -import { Position, Location } from 'vscode-languageserver-protocol'; +import { Position, Location, CancellationToken } from 'vscode-languageserver-protocol'; import { Document, mapLocationToOriginal } from '../../../lib/documents'; import { isNotNullOrUndefined } from '../../../utils'; import { ImplementationProvider } from '../../interfaces'; @@ -13,8 +13,17 @@ import { export class ImplementationProviderImpl implements ImplementationProvider { constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {} - async getImplementation(document: Document, position: Position): Promise { + async getImplementation( + document: Document, + position: Position, + cancellationToken?: CancellationToken + ): Promise { const { tsDoc, lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); + + if (cancellationToken?.isCancellationRequested) { + return null; + } + const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position)); const implementations = lang.getImplementationAtPosition(tsDoc.filePath, offset); @@ -47,6 +56,10 @@ export class ImplementationProviderImpl implements ImplementationProvider { snapshot = await snapshots.retrieve(implementation.fileName); } + if (cancellationToken?.isCancellationRequested) { + return null; + } + const location = mapLocationToOriginal( snapshot, convertRange(snapshot, implementation.textSpan) diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 3a27f5a35..85db7805b 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -292,7 +292,10 @@ export function startServer(options?: LSOptions) { implementationProvider: true, typeDefinitionProvider: true, inlayHintProvider: true, - callHierarchyProvider: true + callHierarchyProvider: true, + codeLensProvider: { + resolveProvider: true + } } }; }); @@ -373,8 +376,8 @@ export function startServer(options?: LSOptions) { pluginHost.getDocumentSymbols(evt.textDocument, cancellationToken) ); connection.onDefinition((evt) => pluginHost.getDefinitions(evt.textDocument, evt.position)); - connection.onReferences((evt) => - pluginHost.findReferences(evt.textDocument, evt.position, evt.context) + connection.onReferences((evt, cancellationToken) => + pluginHost.findReferences(evt.textDocument, evt.position, evt.context, cancellationToken) ); connection.onCodeAction((evt, cancellationToken) => @@ -419,14 +422,26 @@ export function startServer(options?: LSOptions) { pluginHost.getSelectionRanges(evt.textDocument, evt.positions) ); - connection.onImplementation((evt) => - pluginHost.getImplementation(evt.textDocument, evt.position) + connection.onImplementation((evt, cancellationToken) => + pluginHost.getImplementation(evt.textDocument, evt.position, cancellationToken) ); connection.onTypeDefinition((evt) => pluginHost.getTypeDefinition(evt.textDocument, evt.position) ); + connection.onCodeLens((evt) => pluginHost.getCodeLens(evt.textDocument)); + + connection.onCodeLensResolve((codeLens, token) => { + const data = codeLens.data as TextDocumentIdentifier; + + if (!data) { + return codeLens; + } + + return pluginHost.resolveCodeLens(data, codeLens, token); + }); + const diagnosticsManager = new DiagnosticsManager( connection.sendDiagnostics, docManager, diff --git a/packages/svelte-vscode/src/extension.ts b/packages/svelte-vscode/src/extension.ts index 986cc264c..2a5df31eb 100644 --- a/packages/svelte-vscode/src/extension.ts +++ b/packages/svelte-vscode/src/extension.ts @@ -32,6 +32,7 @@ import { TsPlugin } from './tsplugin'; import { addFindComponentReferencesListener } from './typescript/findComponentReferences'; import { addFindFileReferencesListener } from './typescript/findFileReferences'; import { setupSvelteKit } from './sveltekit'; +import { resolveCodeLensMiddleware } from './middlewares'; namespace TagCloseRequest { export const type: RequestType = new RequestType( @@ -167,6 +168,9 @@ export function activateSvelteLanguageServer(context: ExtensionContext) { }, dontFilterIncompleteCompletions: true, // VSCode filters client side and is smarter at it than us isTrusted: workspace.isTrusted + }, + middleware: { + resolveCodeLens: resolveCodeLensMiddleware } }; diff --git a/packages/svelte-vscode/src/middlewares.ts b/packages/svelte-vscode/src/middlewares.ts new file mode 100644 index 000000000..69d309663 --- /dev/null +++ b/packages/svelte-vscode/src/middlewares.ts @@ -0,0 +1,43 @@ +import { Location, Range, Uri } from 'vscode'; +import { Middleware, Location as LSLocation } from 'vscode-languageclient'; + +/** + * Reference-like code lens require a client command to be executed. + * There isn't a way to request client to show references from the server. + * If other clients want to show references, they need to have a similar middleware to resolve the code lens. + */ +export const resolveCodeLensMiddleware: Middleware['resolveCodeLens'] = async function ( + resolving, + token, + next +) { + const codeLen = await next(resolving, token); + if (!codeLen) { + return resolving; + } + + if (codeLen.command?.arguments?.length !== 3) { + return codeLen; + } + + const locations = codeLen.command.arguments[2] as LSLocation[]; + codeLen.command.command = locations.length > 0 ? 'editor.action.showReferences' : ''; + codeLen.command.arguments = [ + Uri.parse(codeLen?.command?.arguments[0]), + codeLen.range.start, + locations.map( + (l) => + new Location( + Uri.parse(l.uri), + new Range( + l.range.start.line, + l.range.start.character, + l.range.end.line, + l.range.end.character + ) + ) + ) + ]; + + return codeLen; +}; From 9294a3587cd009f43b1d93dae221671904938527 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Tue, 14 May 2024 11:33:32 +0800 Subject: [PATCH 2/8] perf: skip a bit earlier --- packages/language-server/src/ls-config.ts | 2 +- .../typescript/features/CodeLensProvider.ts | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index 62c56a31e..e9ee34c4f 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -511,7 +511,7 @@ export class LSConfigManager { }; } - getRawTsUserConfig(lang: TsUserConfigLang): TSUserConfig { + getVSCodeTsUserConfig(lang: TsUserConfigLang): TSUserConfig { return this.rawTsUserConfig[lang]; } diff --git a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts index dd31d6f3e..68c03250e 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts @@ -27,14 +27,17 @@ export class CodeLensProviderImpl implements CodeLensProvider { ) {} async getCodeLens(document: Document): Promise { + if (!this.anyCodeLensEnabled('typescript') && !this.anyCodeLensEnabled('javascript')) { + return null; + } + const { lang, tsDoc } = await this.lsAndTsDocResolver.getLsForSyntheticOperations(document); const results: [CodeLensType, Range][] = []; - const navigationTree = lang.getNavigationTree(tsDoc.filePath); const collectors: CodeLensCollector[] = []; - const vscodeTsConfig = this.configManager.getRawTsUserConfig( + const vscodeTsConfig = this.configManager.getVSCodeTsUserConfig( tsDoc.scriptKind === ts.ScriptKind.TS ? 'typescript' : 'javascript' ); @@ -60,6 +63,7 @@ export class CodeLensProviderImpl implements CodeLensProvider { return null; } + const navigationTree = lang.getNavigationTree(tsDoc.filePath); const renderFunction = navigationTree?.childItems?.find((item) => item.text === 'render'); if (renderFunction) { // pretty rare that there is anything to show in the template, so we skip it @@ -75,6 +79,14 @@ export class CodeLensProviderImpl implements CodeLensProvider { return results.map(([type, range]) => CodeLens.create(range, { type, uri })); } + private anyCodeLensEnabled(lang: 'typescript' | 'javascript') { + const vscodeTsConfig = this.configManager.getVSCodeTsUserConfig(lang); + return ( + vscodeTsConfig.referencesCodeLens?.enabled || + vscodeTsConfig.implementationCodeLens?.enabled + ); + } + /** * https://github.com/microsoft/vscode/blob/062ba1ed6c2b9ff4819f4f7dad76de3fde0044ab/extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts#L61 */ From a1a8ec2d738c764b6b4de8e0844f52240490a6fa Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Tue, 14 May 2024 11:44:20 +0800 Subject: [PATCH 3/8] typescript.implementationsCodeLens.showOnInterfaceMethods --- packages/language-server/src/ls-config.ts | 7 ++++++- .../typescript/features/CodeLensProvider.ts | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index e9ee34c4f..94b598911 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -202,7 +202,7 @@ export interface TSUserConfig { format?: TsFormatConfig; inlayHints?: TsInlayHintsConfig; referencesCodeLens?: TsReferenceCodeLensConfig; - implementationCodeLens?: { enabled: boolean }; + implementationCodeLens?: TsImplementationCodeLensConfig; } /** @@ -259,6 +259,11 @@ export interface TsReferenceCodeLensConfig { enabled: boolean; } +export interface TsImplementationCodeLensConfig { + enabled: boolean; + showOnInterfaceMethods?: boolean | undefined; +} + export type TsUserConfigLang = 'typescript' | 'javascript'; /** diff --git a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts index 68c03250e..46558b355 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts @@ -55,7 +55,8 @@ export class CodeLensProviderImpl implements CodeLensProvider { ) { collectors.push({ type: 'implementation', - collect: (tsDoc, item) => this.extractImplementationLocation(tsDoc, item) + collect: (tsDoc, item, parent) => + this.extractImplementationLocation(tsDoc, item, vscodeTsConfig, parent) }); } @@ -165,8 +166,18 @@ export class CodeLensProviderImpl implements CodeLensProvider { */ private extractImplementationLocation( tsDoc: SvelteDocumentSnapshot, - item: ts.NavigationTree + item: ts.NavigationTree, + config: TSUserConfig, + parent?: ts.NavigationTree ): Range | undefined { + if ( + item.kind === ts.ScriptElementKind.memberFunctionElement && + parent && + parent.kind === ts.ScriptElementKind.interfaceElement && + config.implementationCodeLens?.showOnInterfaceMethods === true + ) { + return this.getSymbolRange(tsDoc, item); + } switch (item.kind) { case ts.ScriptElementKind.interfaceElement: return this.getSymbolRange(tsDoc, item); From a37a994089eebd455f49cfd2e4be44504682d8d7 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Tue, 21 May 2024 12:01:48 +0800 Subject: [PATCH 4/8] test --- packages/language-server/src/ls-config.ts | 6 +- .../language-server/src/plugins/PluginHost.ts | 2 +- .../typescript/features/CodeLensProvider.ts | 28 ++- packages/language-server/src/server.ts | 3 +- .../features/CodeLensProvider.test.ts | 209 ++++++++++++++++++ .../testfiles/codelens/importing.svelte | 5 + .../testfiles/codelens/references.svelte | 7 + 7 files changed, 243 insertions(+), 17 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/features/CodeLensProvider.test.ts create mode 100644 packages/language-server/test/plugins/typescript/testfiles/codelens/importing.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/codelens/references.svelte diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index 94b598911..df35129c8 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -300,8 +300,8 @@ export class LSConfigManager { private rawTsUserConfig: Record = { typescript: {}, javascript: {} - } - + }; + private resolvedAutoImportExcludeCache = new FileMap(); private tsFormatCodeOptions: Record = { typescript: this.getDefaultFormatCodeOptions(), @@ -516,7 +516,7 @@ export class LSConfigManager { }; } - getVSCodeTsUserConfig(lang: TsUserConfigLang): TSUserConfig { + getClientTsUserConfig(lang: TsUserConfigLang): TSUserConfig { return this.rawTsUserConfig[lang]; } diff --git a/packages/language-server/src/plugins/PluginHost.ts b/packages/language-server/src/plugins/PluginHost.ts index 01b88fc92..e9363e6d8 100644 --- a/packages/language-server/src/plugins/PluginHost.ts +++ b/packages/language-server/src/plugins/PluginHost.ts @@ -646,7 +646,7 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { 'resolveCodeLens', [document, codeLens, cancellationToken], ExecuteMode.FirstNonNull, - 'high' + 'smart' )) ?? codeLens ); } diff --git a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts index 46558b355..2b69a7937 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts @@ -6,6 +6,7 @@ import { CodeLensProvider, FindReferencesProvider, ImplementationProvider } from import { SvelteDocumentSnapshot } from '../DocumentSnapshot'; import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; import { convertRange, isGeneratedSvelteComponentName } from '../utils'; +import { isTextSpanInGeneratedCode } from './utils'; type CodeLensType = 'reference' | 'implementation'; @@ -37,26 +38,26 @@ export class CodeLensProviderImpl implements CodeLensProvider { const collectors: CodeLensCollector[] = []; - const vscodeTsConfig = this.configManager.getVSCodeTsUserConfig( + const clientTsConfig = this.configManager.getClientTsUserConfig( tsDoc.scriptKind === ts.ScriptKind.TS ? 'typescript' : 'javascript' ); - if (vscodeTsConfig.referencesCodeLens?.enabled) { + if (clientTsConfig.referencesCodeLens?.enabled) { collectors.push({ type: 'reference', collect: (tsDoc, item, parent) => - this.extractReferenceLocation(tsDoc, item, parent, vscodeTsConfig) + this.extractReferenceLocation(tsDoc, item, parent, clientTsConfig) }); } if ( tsDoc.scriptKind === ts.ScriptKind.TS && - vscodeTsConfig.implementationCodeLens?.enabled + clientTsConfig.implementationCodeLens?.enabled ) { collectors.push({ type: 'implementation', collect: (tsDoc, item, parent) => - this.extractImplementationLocation(tsDoc, item, vscodeTsConfig, parent) + this.extractImplementationLocation(tsDoc, item, clientTsConfig, parent) }); } @@ -81,7 +82,7 @@ export class CodeLensProviderImpl implements CodeLensProvider { } private anyCodeLensEnabled(lang: 'typescript' | 'javascript') { - const vscodeTsConfig = this.configManager.getVSCodeTsUserConfig(lang); + const vscodeTsConfig = this.configManager.getClientTsUserConfig(lang); return ( vscodeTsConfig.referencesCodeLens?.enabled || vscodeTsConfig.implementationCodeLens?.enabled @@ -199,21 +200,22 @@ export class CodeLensProviderImpl implements CodeLensProvider { tsDoc: SvelteDocumentSnapshot, item: ts.NavigationTree ): Range | undefined { - if (!item.nameSpan) { + if (!item.nameSpan || isTextSpanInGeneratedCode(tsDoc.getFullText(), item.nameSpan)) { return; } - const range = mapRangeToOriginal(tsDoc, convertRange(tsDoc, item.spans[0])); + const range = mapRangeToOriginal(tsDoc, convertRange(tsDoc, item.nameSpan)); if (range.start.line >= 0 && range.end.line >= 0) { - return range; + return this.isEmptyRange(range) ? undefined : range; } - // only map to the start of file if it's a generated component + // unlike references, only map to the start of file if it's a generated component if (isGeneratedSvelteComponentName(item.text)) { return { start: { line: 0, character: 0 }, - end: { line: 0, character: 0 } + // some client refused to resolve the code lens if the start is the same as the end + end: { line: 0, character: 1 } }; } } @@ -259,6 +261,10 @@ export class CodeLensProviderImpl implements CodeLensProvider { return codeLensToResolve; } + private isEmptyRange(range: Range): boolean { + return range.start.line === range.end.line && range.start.character === range.end.character; + } + private async resolveReferenceCodeLens( textDocument: Document, codeLensToResolve: CodeLens, diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index e087fd60e..af5ebfbc3 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -432,9 +432,8 @@ export function startServer(options?: LSOptions) { pluginHost.getTypeDefinition(evt.textDocument, evt.position) ); - connection.onFoldingRanges((evt) => pluginHost.getFoldingRanges(evt.textDocument)); - + connection.onCodeLens((evt) => pluginHost.getCodeLens(evt.textDocument)); connection.onCodeLensResolve((codeLens, token) => { const data = codeLens.data as TextDocumentIdentifier; diff --git a/packages/language-server/test/plugins/typescript/features/CodeLensProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeLensProvider.test.ts new file mode 100644 index 000000000..9afc33d51 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/CodeLensProvider.test.ts @@ -0,0 +1,209 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import ts from 'typescript'; +import { Document, DocumentManager } from '../../../../src/lib/documents'; +import { LSConfigManager } from '../../../../src/ls-config'; +import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDocResolver'; +import { CodeLensProviderImpl } from '../../../../src/plugins/typescript/features/CodeLensProvider'; +import { FindComponentReferencesProviderImpl } from '../../../../src/plugins/typescript/features/FindComponentReferencesProvider'; +import { FindReferencesProviderImpl } from '../../../../src/plugins/typescript/features/FindReferencesProvider'; +import { ImplementationProviderImpl } from '../../../../src/plugins/typescript/features/ImplementationProvider'; +import { pathToUrl } from '../../../../src/utils'; +import { serviceWarmup } from '../test-utils'; + +const testDir = path.join(__dirname, '..'); + +describe('CodeLensProvider', function () { + serviceWarmup(this, path.join(testDir, 'testfiles', 'codelens'), pathToUrl(testDir)); + + function getFullPath(filename: string) { + return path.join(testDir, 'testfiles', 'codelens', filename); + } + + function getUri(filename: string) { + return pathToUrl(getFullPath(filename)); + } + + function setup(filename: string) { + const docManager = new DocumentManager( + (textDocument) => new Document(textDocument.uri, textDocument.text) + ); + const lsConfigManager = new LSConfigManager(); + const lsAndTsDocResolver = new LSAndTSDocResolver( + docManager, + [pathToUrl(testDir)], + lsConfigManager + ); + const componentReferencesProvider = new FindComponentReferencesProviderImpl( + lsAndTsDocResolver + ); + const referenceProvider = new FindReferencesProviderImpl( + lsAndTsDocResolver, + componentReferencesProvider + ); + const implementationProvider = new ImplementationProviderImpl(lsAndTsDocResolver); + const provider = new CodeLensProviderImpl( + lsAndTsDocResolver, + referenceProvider, + implementationProvider, + lsConfigManager + ); + const filePath = getFullPath(filename); + const document = docManager.openClientDocument({ + uri: pathToUrl(filePath), + text: ts.sys.readFile(filePath) || '' + }); + return { provider, document, lsConfigManager }; + } + + it('provides reference codelens', async () => { + const { provider, document, lsConfigManager } = setup('references.svelte'); + + lsConfigManager.updateTsJsUserPreferences({ + typescript: { referencesCodeLens: { enabled: true } }, + javascript: {} + }); + + const codeLenses = await provider.getCodeLens(document); + + const references = codeLenses?.filter((lens) => lens.data.type === 'reference'); + + assert.deepStrictEqual(references, [ + { + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 1 } + }, + data: { type: 'reference', uri: getUri('references.svelte') } + }, + { + range: { + start: { line: 1, character: 14 }, + end: { line: 1, character: 17 } + }, + data: { type: 'reference', uri: getUri('references.svelte') } + }, + { + range: { + start: { line: 2, character: 8 }, + end: { line: 2, character: 11 } + }, + data: { type: 'reference', uri: getUri('references.svelte') } + } + ]); + }); + + it('resolve reference codelens', async () => { + const { provider, document } = setup('references.svelte'); + const codeLens = await provider.resolveCodeLens(document, { + range: { + start: { line: 1, character: 14 }, + end: { line: 1, character: 17 } + }, + data: { type: 'reference', uri: getUri('references.svelte') } + }); + + assert.deepStrictEqual(codeLens.command, { + title: '1 reference', + command: '', + arguments: [ + getUri('references.svelte'), + { line: 1, character: 14 }, + [ + { + uri: getUri('references.svelte'), + range: { + start: { line: 5, character: 13 }, + end: { line: 5, character: 16 } + } + } + ] + ] + }); + }); + + it('resolve component reference codelens', async () => { + const { provider, document } = setup('references.svelte'); + const codeLens = await provider.resolveCodeLens(document, { + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 1 } + }, + data: { type: 'reference', uri: getUri('references.svelte') } + }); + + assert.deepStrictEqual(codeLens.command, { + title: '2 references', + command: '', + arguments: [ + getUri('references.svelte'), + { line: 0, character: 0 }, + [ + { + uri: getUri('importing.svelte'), + range: { + start: { line: 1, character: 11 }, + end: { line: 1, character: 21 } + } + }, + { + uri: getUri('importing.svelte'), + range: { start: { line: 4, character: 1 }, end: { line: 4, character: 11 } } + } + ] + ] + }); + }); + + it('provides implementation codelens', async () => { + const { provider, document, lsConfigManager } = setup('references.svelte'); + + lsConfigManager.updateTsJsUserPreferences({ + typescript: { implementationCodeLens: { enabled: true } }, + javascript: {} + }); + + const codeLenses = await provider.getCodeLens(document); + + const references = codeLenses?.filter((lens) => lens.data.type === 'implementation'); + + assert.deepStrictEqual(references, [ + { + range: { + start: { line: 1, character: 14 }, + end: { line: 1, character: 17 } + }, + data: { type: 'implementation', uri: getUri('references.svelte') } + } + ]); + }); + + it('resolve implementation codelens', async () => { + const { provider, document } = setup('references.svelte'); + const codeLens = await provider.resolveCodeLens(document, { + range: { + start: { line: 1, character: 14 }, + end: { line: 1, character: 17 } + }, + data: { type: 'implementation', uri: getUri('references.svelte') } + }); + + assert.deepStrictEqual(codeLens.command, { + title: '1 implementation', + command: '', + arguments: [ + getUri('references.svelte'), + { line: 1, character: 14 }, + [ + { + uri: getUri('references.svelte'), + range: { + start: { line: 5, character: 19 }, + end: { line: 5, character: 33 } + } + } + ] + ] + }); + }); +}); diff --git a/packages/language-server/test/plugins/typescript/testfiles/codelens/importing.svelte b/packages/language-server/test/plugins/typescript/testfiles/codelens/importing.svelte new file mode 100644 index 000000000..bd6b4c514 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/codelens/importing.svelte @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/codelens/references.svelte b/packages/language-server/test/plugins/typescript/testfiles/codelens/references.svelte new file mode 100644 index 000000000..b45c05a78 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/codelens/references.svelte @@ -0,0 +1,7 @@ + \ No newline at end of file From 402f24e5ecefca4b563771ff9808579f160c70d0 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Tue, 21 May 2024 12:22:47 +0800 Subject: [PATCH 5/8] oops --- packages/language-server/src/ls-config.ts | 2 +- .../src/plugins/typescript/features/CodeLensProvider.ts | 6 +++--- .../plugins/typescript/features/CodeLensProvider.test.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index df35129c8..39e098ab3 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -202,7 +202,7 @@ export interface TSUserConfig { format?: TsFormatConfig; inlayHints?: TsInlayHintsConfig; referencesCodeLens?: TsReferenceCodeLensConfig; - implementationCodeLens?: TsImplementationCodeLensConfig; + implementationsCodeLens?: TsImplementationCodeLensConfig; } /** diff --git a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts index 2b69a7937..ca09ed7b5 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts @@ -52,7 +52,7 @@ export class CodeLensProviderImpl implements CodeLensProvider { if ( tsDoc.scriptKind === ts.ScriptKind.TS && - clientTsConfig.implementationCodeLens?.enabled + clientTsConfig.implementationsCodeLens?.enabled ) { collectors.push({ type: 'implementation', @@ -85,7 +85,7 @@ export class CodeLensProviderImpl implements CodeLensProvider { const vscodeTsConfig = this.configManager.getClientTsUserConfig(lang); return ( vscodeTsConfig.referencesCodeLens?.enabled || - vscodeTsConfig.implementationCodeLens?.enabled + vscodeTsConfig.implementationsCodeLens?.enabled ); } @@ -175,7 +175,7 @@ export class CodeLensProviderImpl implements CodeLensProvider { item.kind === ts.ScriptElementKind.memberFunctionElement && parent && parent.kind === ts.ScriptElementKind.interfaceElement && - config.implementationCodeLens?.showOnInterfaceMethods === true + config.implementationsCodeLens?.showOnInterfaceMethods === true ) { return this.getSymbolRange(tsDoc, item); } diff --git a/packages/language-server/test/plugins/typescript/features/CodeLensProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeLensProvider.test.ts index 9afc33d51..95459ddf6 100644 --- a/packages/language-server/test/plugins/typescript/features/CodeLensProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CodeLensProvider.test.ts @@ -159,7 +159,7 @@ describe('CodeLensProvider', function () { const { provider, document, lsConfigManager } = setup('references.svelte'); lsConfigManager.updateTsJsUserPreferences({ - typescript: { implementationCodeLens: { enabled: true } }, + typescript: { implementationsCodeLens: { enabled: true } }, javascript: {} }); From d5c4dc8b55383c7c50dbf2d911b8c168c27ae464 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Wed, 22 May 2024 13:50:10 +0800 Subject: [PATCH 6/8] use existing utility --- .../src/plugins/typescript/features/CodeLensProvider.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts index ca09ed7b5..ed22a36a3 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts @@ -7,6 +7,7 @@ import { SvelteDocumentSnapshot } from '../DocumentSnapshot'; import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; import { convertRange, isGeneratedSvelteComponentName } from '../utils'; import { isTextSpanInGeneratedCode } from './utils'; +import { isZeroLengthRange } from '../../../utils'; type CodeLensType = 'reference' | 'implementation'; @@ -207,7 +208,7 @@ export class CodeLensProviderImpl implements CodeLensProvider { const range = mapRangeToOriginal(tsDoc, convertRange(tsDoc, item.nameSpan)); if (range.start.line >= 0 && range.end.line >= 0) { - return this.isEmptyRange(range) ? undefined : range; + return isZeroLengthRange(range) ? undefined : range; } // unlike references, only map to the start of file if it's a generated component @@ -261,10 +262,6 @@ export class CodeLensProviderImpl implements CodeLensProvider { return codeLensToResolve; } - private isEmptyRange(range: Range): boolean { - return range.start.line === range.end.line && range.start.character === range.end.character; - } - private async resolveReferenceCodeLens( textDocument: Document, codeLensToResolve: CodeLens, From 76f458916083fa1fde2c34589e439f3f3a87d29a Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Sat, 27 Jul 2024 12:32:57 +0800 Subject: [PATCH 7/8] always add component reference --- .../typescript/features/CodeLensProvider.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts index ed22a36a3..42ab6a5a2 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts @@ -49,6 +49,18 @@ export class CodeLensProviderImpl implements CodeLensProvider { collect: (tsDoc, item, parent) => this.extractReferenceLocation(tsDoc, item, parent, clientTsConfig) }); + + if (!tsDoc.parserError) { + // always add a reference code lens for the generated component + results.push([ + 'reference', + { + start: { line: 0, character: 0 }, + // some client refused to resolve the code lens if the start is the same as the end + end: { line: 0, character: 1 } + } + ]); + } } if ( @@ -210,15 +222,6 @@ export class CodeLensProviderImpl implements CodeLensProvider { if (range.start.line >= 0 && range.end.line >= 0) { return isZeroLengthRange(range) ? undefined : range; } - - // unlike references, only map to the start of file if it's a generated component - if (isGeneratedSvelteComponentName(item.text)) { - return { - start: { line: 0, character: 0 }, - // some client refused to resolve the code lens if the start is the same as the end - end: { line: 0, character: 1 } - }; - } } private walkTree( From 3dc05a5d36df29483bc1294e415f6b2b30349a35 Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Sat, 27 Jul 2024 12:53:54 +0800 Subject: [PATCH 8/8] cleanup --- .../src/plugins/typescript/features/CodeLensProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts index 42ab6a5a2..99484b3d1 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeLensProvider.ts @@ -2,12 +2,12 @@ import ts from 'typescript'; import { CancellationToken, CodeLens, Range } from 'vscode-languageserver'; import { Document, mapRangeToOriginal } from '../../../lib/documents'; import { LSConfigManager, TSUserConfig } from '../../../ls-config'; +import { isZeroLengthRange } from '../../../utils'; import { CodeLensProvider, FindReferencesProvider, ImplementationProvider } from '../../interfaces'; import { SvelteDocumentSnapshot } from '../DocumentSnapshot'; import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; -import { convertRange, isGeneratedSvelteComponentName } from '../utils'; +import { convertRange } from '../utils'; import { isTextSpanInGeneratedCode } from './utils'; -import { isZeroLengthRange } from '../../../utils'; type CodeLensType = 'reference' | 'implementation';