diff --git a/client/src/ClientNotificationManager.ts b/client/src/ClientNotificationManager.ts new file mode 100644 index 00000000..ddbe1ffd --- /dev/null +++ b/client/src/ClientNotificationManager.ts @@ -0,0 +1,50 @@ +import { type Memento, commands, window } from 'vscode' +import { type LanguageClient, type Disposable } from 'vscode-languageclient/node' + +export class ClientNotificationManager { + private readonly _client: LanguageClient + private readonly _memento: Memento + + constructor (client: LanguageClient, memento: Memento) { + this._client = client + this._memento = memento + } + + buildHandlers (): Disposable[] { + const handlers = [ + this.buildBitBakeNotFoundHandler() + ] + + return handlers + } + + private buildBitBakeNotFoundHandler (): Disposable { + const isNeverShowAgain = this.checkIsNeverShowAgain('custom/bitBakeNotFound') + if (isNeverShowAgain) { + return { dispose: () => {} } + } + return this._client.onNotification('custom/bitBakeNotFound', () => { + void window.showErrorMessage( + 'BitBake folder could not be found. Please set its path in the settings. Optionally, also set an environment script.', + 'Open Settings', + 'Close', + 'Never Show Again' + ) + .then((item) => { + if (item === 'Open Settings') { + void commands.executeCommand('workbench.action.openSettings', 'bitbake') + } else if (item === 'Never Show Again') { + void this.neverShowAgain('custom/bitBakeNotFound') + } + }) + }) + } + + private neverShowAgain (method: string): Thenable { + return this._memento.update(`neverShowAgain/${method}`, true) + } + + private checkIsNeverShowAgain (method: string): boolean { + return this._memento.get(`neverShowAgain/${method}`, false) + } +} diff --git a/client/src/extension.ts b/client/src/extension.ts index 5e42a6c3..98d01a10 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -15,6 +15,7 @@ import type { ExtensionContext } from 'vscode' import type { LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node' // import { legend, BitBakeDocumentSemanticTokensProvider } from './BitBakeDocumentSemanticTokensProvider' +import { ClientNotificationManager } from './ClientNotificationManager' let client: LanguageClient export async function activate (context: ExtensionContext): Promise { @@ -55,6 +56,8 @@ export async function activate (context: ExtensionContext): Promise { await client.start() // context.subscriptions.push(languages.registerDocumentSemanticTokensProvider({ language: 'bitbake', scheme: 'file' }, new BitBakeDocumentSemanticTokensProvider(), legend)) + const notificationManager = new ClientNotificationManager(client, context.globalState) + context.subscriptions.push(...notificationManager.buildHandlers()) } export function deactivate (): Thenable | undefined { diff --git a/server/src/BitBakeDocScanner.ts b/server/src/BitBakeDocScanner.ts index 9b2995e0..e135e27e 100644 --- a/server/src/BitBakeDocScanner.ts +++ b/server/src/BitBakeDocScanner.ts @@ -1,5 +1,6 @@ import path from 'path' import fs from 'fs' +import logger from 'winston' type SuffixType = 'layer' | 'providedItem' | undefined @@ -50,7 +51,13 @@ export class BitBakeDocScanner { } parse (pathToBitbakeFolder: string): void { - const file = fs.readFileSync(path.join(pathToBitbakeFolder, variablesFolder), 'utf8') + let file = '' + const docPath = path.join(pathToBitbakeFolder, variablesFolder) + try { + file = fs.readFileSync(docPath, 'utf8') + } catch { + logger.warn(`BitBake doc file not found at ${docPath}`) + } for (const match of file.matchAll(variablesRegexForDoc)) { const name = match.groups?.name // Naive silly inneficient incomplete conversion to markdown diff --git a/server/src/BitBakeProjectScanner.ts b/server/src/BitBakeProjectScanner.ts index 0e12b261..420066ba 100644 --- a/server/src/BitBakeProjectScanner.ts +++ b/server/src/BitBakeProjectScanner.ts @@ -22,6 +22,7 @@ import type { import { OutputParser } from './OutputParser' +import { ServerNotificationManager } from './ServerNotificationManager' interface ScannStatus { scanIsRunning: boolean @@ -42,6 +43,7 @@ export class BitBakeProjectScanner { private _includes: ElementInfo[] = new Array < ElementInfo >() private _recipes: ElementInfo[] = new Array < ElementInfo >() private readonly _outputParser: OutputParser + private readonly _notificationManager: ServerNotificationManager private _shouldDeepExamine: boolean = false private _pathToBuildFolder: string = 'build' private _pathToEnvScript: string = 'oe-init-build-env' @@ -49,6 +51,7 @@ export class BitBakeProjectScanner { constructor (connection: Connection) { this._outputParser = new OutputParser(connection) + this._notificationManager = new ServerNotificationManager(connection) } private readonly _scanStatus: ScannStatus = { @@ -347,9 +350,13 @@ export class BitBakeProjectScanner { const commandResult = this.executeCommand(scriptContent) if (commandResult.status === 127) { - // Likely "bitbake: not found" - // TODO: Show a proper error message to help the user configuring the extension - throw new Error(commandResult.output.toString()) + const output = commandResult.stderr.toString() + if (output.includes('bitbake')) { + // Seems like Bitbake could not be found. + // Are we sure this is the actual error? + this._notificationManager.sendBitBakeNotFound() + throw new Error(output) + } } return commandResult diff --git a/server/src/ServerNotificationManager.ts b/server/src/ServerNotificationManager.ts new file mode 100644 index 00000000..9c0579f7 --- /dev/null +++ b/server/src/ServerNotificationManager.ts @@ -0,0 +1,20 @@ +import { type Connection } from 'vscode-languageserver' + +export type NotificationType = + 'custom/bitBakeNotFound' + +export class ServerNotificationManager { + private readonly _connection: Connection + + constructor (connection: Connection) { + this._connection = connection + } + + send (type: NotificationType, message?: string): void { + void this._connection.sendNotification(type, message) + } + + sendBitBakeNotFound (): void { + this.send('custom/bitBakeNotFound') + } +}