diff --git a/client/src/ui/BitbakeCommands.ts b/client/src/ui/BitbakeCommands.ts index d8dc6877..501b19f2 100644 --- a/client/src/ui/BitbakeCommands.ts +++ b/client/src/ui/BitbakeCommands.ts @@ -52,6 +52,8 @@ export function registerBitbakeCommands (context: vscode.ExtensionContext, bitba vscode.commands.registerCommand('bitbake.stop-toaster', async () => { await stopToaster(bitBakeProjectScanner.bitbakeDriver) }), vscode.commands.registerCommand('bitbake.clear-workspace-state', async () => { await clearAllWorkspaceState(context) }), vscode.commands.registerCommand('bitbake.examine-dependency-taskexp', async (uri) => { await examineDependenciesTaskexp(bitbakeWorkspace, bitBakeProjectScanner, uri) }), + vscode.commands.registerCommand('bitbake.codeLens.showReferences', async (uri, position) => { await showReferences(uri, position) }), + // Handles enqueued parsing requests (onSave) vscode.tasks.onDidEndTask((e) => { if (e.execution.task.name === 'Bitbake: Parse') { @@ -70,6 +72,21 @@ export function registerBitbakeCommands (context: vscode.ExtensionContext, bitba ) } +async function showReferences (uri: any, position: any): Promise { + if (typeof uri !== 'string' || position === undefined) { + return + } + + let _position: vscode.Position + try { + _position = new vscode.Position(position.line, position.character) + const locations = await vscode.commands.executeCommand('vscode.executeReferenceProvider', vscode.Uri.parse(uri), position) + await vscode.commands.executeCommand('editor.action.showReferences', vscode.Uri.parse(uri), _position, locations) + } catch (error: any) { + void vscode.window.showErrorMessage('Failed to show references: ' + JSON.stringify(error)) + } +} + async function clearAllWorkspaceState (context: vscode.ExtensionContext, key?: string): Promise { for (const key of context.workspaceState.keys()) { await context.workspaceState.update(key, undefined).then( diff --git a/package.json b/package.json index cb695297..6edbd107 100644 --- a/package.json +++ b/package.json @@ -206,6 +206,11 @@ "type": "boolean", "default": false, "description": "Turn on this setting to stop this extension from modifying the VS Code configuration of the workspace. Note that this will also disable some features related to embedded Python and Shell code." + }, + "bitbake.enableCodeLensReferencesOnFunctions": { + "type": "boolean", + "default": false, + "description": "Enable or disable the CodeLens references for functions in BitBake files." } } }, diff --git a/server/src/__tests__/analyzer.test.ts b/server/src/__tests__/analyzer.test.ts index 6cb9c5aa..757ac34c 100644 --- a/server/src/__tests__/analyzer.test.ts +++ b/server/src/__tests__/analyzer.test.ts @@ -46,7 +46,7 @@ describe('analyze', () => { document: FIXTURE_DOCUMENT.DECLARATION }) - const globalDeclarations = analyzer.getGlobalDeclarationSymbols(DUMMY_URI) + const globalDeclarations = analyzer.getGlobalDeclarationSymbolsForUri(DUMMY_URI) expect(globalDeclarations).toEqual( expect.arrayContaining([ @@ -261,7 +261,7 @@ describe('sourceIncludeFiles', () => { analyzer.extractIncludeFileUris(uri) const symbols = analyzer.getIncludeUrisForUri(uri).map((includeUri) => { - return analyzer.getGlobalDeclarationSymbols(includeUri) + return analyzer.getGlobalDeclarationSymbolsForUri(includeUri) }).flat() expect(symbols).toEqual( @@ -334,7 +334,7 @@ describe('declarations', () => { uri }) - const symbols = analyzer.getGlobalDeclarationSymbols(uri) + const symbols = analyzer.getGlobalDeclarationSymbolsForUri(uri) let occurances = 0 symbols.forEach((symbol) => { diff --git a/server/src/connectionHandlers/onCodeLens.ts b/server/src/connectionHandlers/onCodeLens.ts new file mode 100644 index 00000000..65760eab --- /dev/null +++ b/server/src/connectionHandlers/onCodeLens.ts @@ -0,0 +1,38 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2023 Savoir-faire Linux. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as LSP from 'vscode-languageserver/node' +import { analyzer } from '../tree-sitter/analyzer' + +export async function onCodeLensHandler (params: LSP.CodeLensParams, enableCodeLensReferencesOnFunctions: boolean): Promise { + const codeLenses: LSP.CodeLens[] = [] + const uri = params.textDocument.uri + + if (!enableCodeLensReferencesOnFunctions) { + return [] + } + + const allSymbols = analyzer.getGlobalDeclarationSymbolsForUri(uri) + allSymbols.forEach((symbol) => { + if (symbol.kind === LSP.SymbolKind.Function) { + const codeLens = LSP.CodeLens.create(symbol.location.range) + + codeLens.command = { + title: 'Show References', + command: 'bitbake.codeLens.showReferences', + arguments: [uri, symbol.location.range.start] + } + + codeLens.data = { uri, position: symbol.location.range.start } + + codeLenses.push(codeLens) + } + }) + return codeLenses +} + +export function onCodeLensResolveHandler (codeLens: LSP.CodeLens): LSP.CodeLens { + return codeLens +} diff --git a/server/src/connectionHandlers/onCompletion.ts b/server/src/connectionHandlers/onCompletion.ts index 9ececda7..0535411d 100644 --- a/server/src/connectionHandlers/onCompletion.ts +++ b/server/src/connectionHandlers/onCompletion.ts @@ -238,7 +238,7 @@ const allCommonDirectoriesCompletionItems: CompletionItem[] = Array.from(commonD function getSymbolCompletionItems (word: string | null): CompletionItem[] { if (word !== null) { const uniqueSymbolSet = new Set() - const globalDeclarationSymbols = analyzer.getGlobalDeclarationSymbols(documentUri).filter(symbol => { + const globalDeclarationSymbols = analyzer.getGlobalDeclarationSymbolsForUri(documentUri).filter(symbol => { if (!uniqueSymbolSet.has(symbol.name)) { uniqueSymbolSet.add(symbol.name) return true @@ -397,7 +397,7 @@ function convertExtraSymbolsToCompletionItems (uri: string): CompletionItem[] { logger.debug(`[onCompletion] convertSymbolsToCompletionItems: ${uri}`) let completionItems: CompletionItem[] = [] analyzer.getIncludeUrisForUri(uri).map((includeUri) => { - return analyzer.getGlobalDeclarationSymbols(includeUri) + return analyzer.getGlobalDeclarationSymbolsForUri(includeUri) }) .flat() .reduce((acc, symbol) => { diff --git a/server/src/connectionHandlers/onDefinition.ts b/server/src/connectionHandlers/onDefinition.ts index 3d07c90a..668134bb 100644 --- a/server/src/connectionHandlers/onDefinition.ts +++ b/server/src/connectionHandlers/onDefinition.ts @@ -162,10 +162,10 @@ export function getAllDefinitionSymbolsForSymbolAtPoint (uri: string, word: stri return [] } const allDeclarationSymbols = [ - ...analyzer.getGlobalDeclarationSymbols(uri) + ...analyzer.getGlobalDeclarationSymbolsForUri(uri) ] analyzer.getIncludeUrisForUri(uri)?.forEach((includeFileUri) => { - allDeclarationSymbols.push(...analyzer.getGlobalDeclarationSymbols(includeFileUri)) + allDeclarationSymbols.push(...analyzer.getGlobalDeclarationSymbolsForUri(includeFileUri)) }) return allDeclarationSymbols.filter(symbol => symbol.name === word && symbol.kind === symbolAtPoint?.kind) diff --git a/server/src/connectionHandlers/onHover.ts b/server/src/connectionHandlers/onHover.ts index d6fcc7c2..f5eee0ba 100644 --- a/server/src/connectionHandlers/onHover.ts +++ b/server/src/connectionHandlers/onHover.ts @@ -28,7 +28,7 @@ export async function onHoverHandler (params: HoverParams): Promise symbol.name === word) && analyzer.isIdentifierOfVariableAssignment(params)) || analyzer.isVariableExpansion(textDocument.uri, position.line, position.character) || analyzer.isPythonDatastoreVariable(textDocument.uri, position.line, position.character) || analyzer.isBashVariableExpansion(textDocument.uri, position.line, position.character) + const canShowHoverDefinitionForVariableName: boolean = (analyzer.getGlobalDeclarationSymbolsForUri(textDocument.uri).some((symbol) => symbol.name === word) && analyzer.isIdentifierOfVariableAssignment(params)) || analyzer.isVariableExpansion(textDocument.uri, position.line, position.character) || analyzer.isPythonDatastoreVariable(textDocument.uri, position.line, position.character) || analyzer.isBashVariableExpansion(textDocument.uri, position.line, position.character) if (canShowHoverDefinitionForVariableName) { const found = [ ...bitBakeDocScanner.bitbakeVariableInfo.filter((bitbakeVariable) => !bitBakeDocScanner.yoctoVariableInfo.some(yoctoVariable => yoctoVariable.name === bitbakeVariable.name)), @@ -121,11 +121,11 @@ export async function onHoverHandler (params: HoverParams): Promise symbol.name === word).filter((symbol) => symbol.commentsAbove.length > 0) + const localSymbolsWithComments = analyzer.getGlobalDeclarationSymbolsForUri(uri).filter((symbol) => symbol.name === word).filter((symbol) => symbol.commentsAbove.length > 0) const externalSymbolsWithComments: BitbakeSymbolInformation[] = [] analyzer.getIncludeUrisForUri(uri).forEach((includeFileUri) => { - externalSymbolsWithComments.push(...analyzer.getGlobalDeclarationSymbols(includeFileUri).filter((symbol) => symbol.name === word).filter((symbol) => symbol.commentsAbove.length > 0)) + externalSymbolsWithComments.push(...analyzer.getGlobalDeclarationSymbolsForUri(includeFileUri).filter((symbol) => symbol.name === word).filter((symbol) => symbol.commentsAbove.length > 0)) }) const priority = ['.bbclass', '.conf', '.inc', '.bb', '.bbappend'] diff --git a/server/src/server.ts b/server/src/server.ts index 9f50d8d6..88a0f2a4 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -35,6 +35,7 @@ import { expandSettingPath } from './lib/src/BitbakeSettings' import { onReferenceHandler } from './connectionHandlers/onReference' import { type BitbakeScanResult } from './lib/src/types/BitbakeScanResult' import { onPrepareRenameHandler, onRenameRequestHandler } from './connectionHandlers/onRename' +import { onCodeLensHandler, onCodeLensResolveHandler } from './connectionHandlers/onCodeLens' // Create a connection for the server. The connection uses Node's IPC as a transport export const connection: Connection = createConnection(ProposedFeatures.all) @@ -42,7 +43,7 @@ setDefinitionsConnection(connection) const documents = new TextDocuments(TextDocument) let workspaceFolder: string | undefined let pokyFolder: string | undefined - +let enableCodeLensReferencesOnFunctions: boolean = false const disposables: Disposable[] = [] let currentActiveTextDocument: TextDocument = TextDocument.create( @@ -111,6 +112,9 @@ disposables.push( }, renameProvider: { prepareProvider: true + }, + codeLensProvider: { + resolveProvider: true } } } @@ -118,6 +122,7 @@ disposables.push( connection.onDidChangeConfiguration((change) => { logger.level = change.settings.bitbake?.loggingLevel ?? logger.level + enableCodeLensReferencesOnFunctions = change.settings.bitbake?.enableCodeLensReferencesOnFunctions ?? enableCodeLensReferencesOnFunctions const bitbakeFolder = expandSettingPath(change.settings.bitbake?.pathToBitbakeFolder, { workspaceFolder }) if (bitbakeFolder !== undefined) { pokyFolder = path.join(bitbakeFolder, '..') // We assume BitBake is into Poky @@ -156,6 +161,12 @@ disposables.push( analyzer.clearRecipeLocalFiles() }), + connection.onCodeLens( + async (params) => await onCodeLensHandler(params, enableCodeLensReferencesOnFunctions) + ), + + connection.onCodeLensResolve(onCodeLensResolveHandler), + connection.onRequest( RequestMethod.EmbeddedLanguageTypeOnPosition, async ({ uriString, position }: RequestParams['EmbeddedLanguageTypeOnPosition']): RequestResult['EmbeddedLanguageTypeOnPosition'] => { diff --git a/server/src/tree-sitter/analyzer.ts b/server/src/tree-sitter/analyzer.ts index ad53fe45..f62062d0 100644 --- a/server/src/tree-sitter/analyzer.ts +++ b/server/src/tree-sitter/analyzer.ts @@ -90,7 +90,7 @@ export default class Analyzer { public getAllSymbols (uri: string): BitbakeSymbolInformation[] { return [ - ...this.getGlobalDeclarationSymbols(uri), + ...this.getGlobalDeclarationSymbolsForUri(uri), ...this.getVariableExpansionSymbols(uri), ...this.getPythonDatastoreVariableSymbols(uri) ] @@ -185,7 +185,7 @@ export default class Analyzer { return { variableExpansionSymbols, pythonDatastoreVariableSymbols } } - public getGlobalDeclarationSymbols (uri: string): BitbakeSymbolInformation[] { + public getGlobalDeclarationSymbolsForUri (uri: string): BitbakeSymbolInformation[] { const analyzedDocument = this.uriToAnalyzedDocument[uri] if (analyzedDocument !== undefined) { const { globalDeclarations } = analyzedDocument