diff --git a/Readme.md b/Readme.md index ce776d5..6d2fc8a 100644 --- a/Readme.md +++ b/Readme.md @@ -145,6 +145,7 @@ for guide of coc.nvim's configuration. implementations, default: `true` - `typescript.referencesCodeLens.enable`:Enable codeLens for references, default: `true` +- `typescript.referencesCodeLens.showOnAllFunctions`: Enable/disable references CodeLens on all functions in typescript files. Default: `false` - `typescript.preferences.importModuleSpecifier` default: `"auto"` - `typescript.preferences.importModuleSpecifierEnding` default: `"auto"` - `typescript.preferences.quoteStyle` default: `"single"` @@ -209,6 +210,7 @@ for guide of coc.nvim's configuration. - `javascript.updateImportsOnFileMove.enable` default: `true` - `javascript.implementationsCodeLens.enable` default: `true` - `javascript.referencesCodeLens.enable` default: `true` +- `javascript.referencesCodeLens.showOnAllFunctions`: Enable/disable references CodeLens on all functions in JavaScript files default: `false` - `javascript.preferences.importModuleSpecifier` default: `"auto"` - `javascript.preferences.importModuleSpecifierEnding` default: `"auto"` - `javascript.preferences.quoteStyle` default: `"single"` diff --git a/package.json b/package.json index add89ef..eeaaad3 100644 --- a/package.json +++ b/package.json @@ -293,6 +293,12 @@ "default": true, "description": "Enable codeLens for references" }, + "typescript.referencesCodeLens.showOnAllFunctions": { + "type": "boolean", + "default": false, + "description": "Enable/disable references CodeLens on all functions in typescript files.", + "scope": "window" + }, "typescript.preferences.importModuleSpecifier": { "type": "string", "default": "shortest", @@ -547,6 +553,12 @@ "type": "boolean", "default": true }, + "javascript.referencesCodeLens.showOnAllFunctions": { + "type": "boolean", + "default": false, + "description": "Enable/disable references CodeLens on all functions in JavaScript files.", + "scope": "window" + }, "javascript.preferences.importModuleSpecifier": { "type": "string", "default": "shortest", diff --git a/src/server/features/baseCodeLensProvider.ts b/src/server/features/baseCodeLensProvider.ts index 74299d7..ff47b04 100644 --- a/src/server/features/baseCodeLensProvider.ts +++ b/src/server/features/baseCodeLensProvider.ts @@ -46,7 +46,8 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider public constructor( protected client: ITypeScriptServiceClient, - private cachedResponse: CachedNavTreeResponse + private cachedResponse: CachedNavTreeResponse, + protected modeId: string ) {} public get onDidChangeCodeLenses(): Event { @@ -116,38 +117,31 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider ) } } - protected getSymbolRange( - document: TextDocument, - item: Proto.NavigationTree - ): Range | null { - if (!item) { - return null - } +} - // TS 3.0+ provides a span for just the symbol - if ((item as any).nameSpan) { - return typeConverters.Range.fromTextSpan((item as any).nameSpan) - } +export function getSymbolRange( + document: TextDocument, + item: Proto.NavigationTree +): Range | null { + if (item.nameSpan) { + return typeConverters.Range.fromTextSpan(item.nameSpan) + } - // In older versions, we have to calculate this manually. See #23924 - const span = item.spans && item.spans[0] - if (!span) { - return null - } + // In older versions, we have to calculate this manually. See #23924 + const span = item.spans && item.spans[0] + if (!span) { + return null + } - const range = typeConverters.Range.fromTextSpan(span) - const text = document.getText(range) + const range = typeConverters.Range.fromTextSpan(span) + const text = document.getText(range) - const identifierMatch = new RegExp( - `^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, - 'gm' - ) - const match = identifierMatch.exec(text) - const prefixLength = match ? match.index + match[1].length : 0 - const startOffset = document.offsetAt(range.start) + prefixLength - return { - start: document.positionAt(startOffset), - end: document.positionAt(startOffset + item.text.length) - } + const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, 'gm') + const match = identifierMatch.exec(text) + const prefixLength = match ? match.index + match[1].length : 0 + const startOffset = document.offsetAt(range.start) + prefixLength + return { + start: document.positionAt(startOffset), + end: document.positionAt(startOffset + item.text.length) } } diff --git a/src/server/features/implementationsCodeLens.ts b/src/server/features/implementationsCodeLens.ts index 3e870c0..bb82ae9 100644 --- a/src/server/features/implementationsCodeLens.ts +++ b/src/server/features/implementationsCodeLens.ts @@ -7,7 +7,7 @@ import { TextDocument } from 'coc.nvim' import * as Proto from '../protocol' import * as PConst from '../protocol.const' import * as typeConverters from '../utils/typeConverters' -import { TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider' +import { TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider' export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider { public async resolveCodeLens( @@ -21,43 +21,39 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip filepath, codeLens.range.start ) - try { - const response = await this.client.execute('implementation', args, token, { lowPriority: true }) - if (response && response.type == 'response' && response.body) { - const locations = response.body - .map(reference => { - return { - uri: this.client.toResource(reference.file), - range: { - start: typeConverters.Position.fromLocation(reference.start), - end: { - line: reference.start.line, - character: 0 - } - } - } - }) - // Exclude original from implementations - .filter( - location => !( - location.uri.toString() === uri && - location.range.start.line === codeLens.range.start.line && - location.range.start.character === - codeLens.range.start.character - ) - ) - - codeLens.command = this.getCommand(locations, codeLens) - return codeLens + const response = await this.client.execute('implementation', args, token, { lowPriority: true }) + if (response.type !== 'response' || !response.body) { + codeLens.command = { + title: response.type === 'cancelled' + ? 'cancelled' + : 'could not determine implementation', + command: '' } - } catch { - // noop - } - - codeLens.command = { - title: '0 implementations', - command: '' + return codeLens } + const locations = response.body + .map(reference => { + return { + uri: this.client.toResource(reference.file), + range: { + start: typeConverters.Position.fromLocation(reference.start), + end: { + line: reference.start.line, + character: 0 + } + } + } + }) + // Exclude original from implementations + .filter( + location => !( + location.uri.toString() === uri && + location.range.start.line === codeLens.range.start.line && + location.range.start.character === + codeLens.range.start.character + ) + ) + codeLens.command = this.getCommand(locations, codeLens) return codeLens } @@ -84,7 +80,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip ): Range | null { switch (item.kind) { case PConst.Kind.interface: - return super.getSymbolRange(document, item) + return getSymbolRange(document, item) case PConst.Kind.class: case PConst.Kind.method: @@ -92,7 +88,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip case PConst.Kind.memberGetAccessor: case PConst.Kind.memberSetAccessor: if (item.kindModifiers.match(/\babstract\b/g)) { - return super.getSymbolRange(document, item) + return getSymbolRange(document, item) } break } diff --git a/src/server/features/referencesCodeLens.ts b/src/server/features/referencesCodeLens.ts index 5c31854..4d33193 100644 --- a/src/server/features/referencesCodeLens.ts +++ b/src/server/features/referencesCodeLens.ts @@ -2,15 +2,16 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CodeLens, Range } from 'vscode-languageserver-protocol' -import { TextDocument } from 'coc.nvim' +import { CancellationToken, Position, Range } from 'vscode-languageserver-protocol' +import { TextDocument, workspace, CodeLens } from 'coc.nvim' +import { ExecutionTarget } from '../typescriptService' import * as Proto from '../protocol' import * as PConst from '../protocol.const' import * as typeConverters from '../utils/typeConverters' -import { TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider' +import { TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider' export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider { - public resolveCodeLens( + public async resolveCodeLens( codeLens: CodeLens, token: CancellationToken ): Promise { @@ -20,47 +21,35 @@ export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBase filepath, codeLens.range.start ) - return this.client - .execute('references', args, token, { - lowPriority: true - }) - .then(response => { - if (!response || response.type != 'response' || !response.body) { - throw codeLens - } + let response = await this.client.execute('references', args, token, { + lowPriority: true, + executionTarget: ExecutionTarget.Semantic + }) + if (!response || response.type != 'response' || !response.body) { + codeLens.command = { + title: response.type === 'cancelled' + ? 'cancelled' + : 'could not determine references', + command: '' + } + return codeLens + } - const locations = response.body.refs - .map(reference => - typeConverters.Location.fromTextSpan( - this.client.toResource(reference.file), - reference - ) - ) - .filter( - location => - // Exclude original definition from references - !( - location.uri.toString() === uri && - location.range.start.line === codeLens.range.start.line && - location.range.start.character === - codeLens.range.start.character - ) - ) + const locations = response.body.refs + .filter(reference => !reference.isDefinition) + .map(reference => + typeConverters.Location.fromTextSpan( + this.client.toResource(reference.file), + reference + ) + ) - codeLens.command = { - title: locations.length === 1 ? '1 reference' : `${locations.length} references`, - command: locations.length ? 'editor.action.showReferences' : '', - arguments: [uri, codeLens.range.start, locations] - } - return codeLens - }) - .catch(() => { - codeLens.command = { - title: '0 references', - command: '' - } - return codeLens - }) + codeLens.command = { + title: locations.length === 1 ? '1 reference' : `${locations.length} references`, + command: locations.length ? 'editor.action.showReferences' : '', + arguments: [uri, codeLens.range.start, locations] + } + return codeLens } protected extractSymbol( @@ -69,37 +58,68 @@ export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBase parent: Proto.NavigationTree | null ): Range | null { if (parent && parent.kind === PConst.Kind.enum) { - return super.getSymbolRange(document, item) + return getSymbolRange(document, item) } switch (item.kind) { + case PConst.Kind.function: { + const showOnAllFunctions = workspace.getConfiguration(this.modeId).get('referencesCodeLens.showOnAllFunctions') + if (showOnAllFunctions) { + return getSymbolRange(document, item) + } + } + // fallthrough + case PConst.Kind.const: case PConst.Kind.let: case PConst.Kind.variable: - case PConst.Kind.function: // Only show references for exported variables - if (!item.kindModifiers.match(/\bexport\b/)) { - break + if (/\bexport\b/.test(item.kindModifiers)) { + return getSymbolRange(document, item) } - // fallthrough + break case PConst.Kind.class: if (item.text === '') { break } - // fallthrough + return getSymbolRange(document, item) - case PConst.Kind.method: - case PConst.Kind.memberVariable: - case PConst.Kind.memberGetAccessor: - case PConst.Kind.memberSetAccessor: - case PConst.Kind.constructorImplementation: case PConst.Kind.interface: case PConst.Kind.type: case PConst.Kind.enum: - return super.getSymbolRange(document, item) + return getSymbolRange(document, item) + + case PConst.Kind.method: + case PConst.Kind.memberGetAccessor: + case PConst.Kind.memberSetAccessor: + case PConst.Kind.constructorImplementation: + case PConst.Kind.memberVariable: + // Don't show if child and parent have same start + // For https://github.com/microsoft/vscode/issues/90396 + if (parent && + comparePosition(typeConverters.Position.fromLocation(parent.spans[0].start), typeConverters.Position.fromLocation(item.spans[0].start)) == 0 + ) { + return null + } + + // Only show if parent is a class type object (not a literal) + switch (parent?.kind) { + case PConst.Kind.class: + case PConst.Kind.interface: + case PConst.Kind.type: + return getSymbolRange(document, item) + } + break } return null } } + +export function comparePosition(position: Position, other: Position): number { + if (position.line > other.line) return 1 + if (other.line == position.line && position.character > other.character) return 1 + if (other.line == position.line && position.character == other.character) return 0 + return -1 +} diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 70d6230..c171b57 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -149,10 +149,10 @@ export default class LanguageProvider { 'tsserver', [CodeActionKind.QuickFix])) let cachedResponse = new CachedNavTreeResponse() if (this.client.apiVersion.gte(API.v206) && conf.get('referencesCodeLens.enable')) { - this._register(languages.registerCodeLensProvider(languageIds, new ReferencesCodeLensProvider(client, cachedResponse))) + this._register(languages.registerCodeLensProvider(languageIds, new ReferencesCodeLensProvider(client, cachedResponse, this.description.id))) } if (this.client.apiVersion.gte(API.v220) && conf.get('implementationsCodeLens.enable')) { - this._register(languages.registerCodeLensProvider(languageIds, new ImplementationsCodeLensProvider(client, cachedResponse))) + this._register(languages.registerCodeLensProvider(languageIds, new ImplementationsCodeLensProvider(client, cachedResponse, this.description.id))) } if (this.client.apiVersion.gte(API.v350)) { this._register(languages.registerSelectionRangeProvider(languageIds, new SmartSelection(this.client))) diff --git a/src/server/typescriptService.ts b/src/server/typescriptService.ts index 88a097b..f576567 100644 --- a/src/server/typescriptService.ts +++ b/src/server/typescriptService.ts @@ -34,7 +34,7 @@ export interface TypeScriptServerPlugin { readonly languages: string[] } -export enum ExectuionTarget { +export enum ExecutionTarget { Semantic, Syntax } @@ -43,7 +43,7 @@ export type ExecConfig = { readonly lowPriority?: boolean readonly nonRecoverable?: boolean readonly cancelOnResourceChange?: string - readonly executionTarget?: ExectuionTarget + readonly executionTarget?: ExecutionTarget } export interface TypeScriptRequestTypes {