diff --git a/arduino-ide-extension/src/browser/boards/boards-data-store.ts b/arduino-ide-extension/src/browser/boards/boards-data-store.ts index 30f2f7781..cdf73d18f 100644 --- a/arduino-ide-extension/src/browser/boards/boards-data-store.ts +++ b/arduino-ide-extension/src/browser/boards/boards-data-store.ts @@ -30,11 +30,11 @@ export class BoardsDataStore implements FrontendApplicationContribution { @inject(LocalStorageService) protected readonly storageService: LocalStorageService; - protected readonly onChangedEmitter = new Emitter(); + protected readonly onChangedEmitter = new Emitter(); onStart(): void { this.notificationCenter.onPlatformDidInstall(async ({ item }) => { - let shouldFireChanged = false; + const dataDidChangePerFqbn: string[] = []; for (const fqbn of item.boards .map(({ fqbn }) => fqbn) .filter(notEmpty) @@ -49,18 +49,18 @@ export class BoardsDataStore implements FrontendApplicationContribution { data = details.configOptions; if (data.length) { await this.storageService.setData(key, data); - shouldFireChanged = true; + dataDidChangePerFqbn.push(fqbn); } } } } - if (shouldFireChanged) { - this.fireChanged(); + if (dataDidChangePerFqbn.length) { + this.fireChanged(...dataDidChangePerFqbn); } }); } - get onChanged(): Event { + get onChanged(): Event { return this.onChangedEmitter.event; } @@ -116,7 +116,7 @@ export class BoardsDataStore implements FrontendApplicationContribution { fqbn, data: { ...data, selectedProgrammer }, }); - this.fireChanged(); + this.fireChanged(fqbn); return true; } @@ -146,7 +146,7 @@ export class BoardsDataStore implements FrontendApplicationContribution { return false; } await this.setData({ fqbn, data }); - this.fireChanged(); + this.fireChanged(fqbn); return true; } @@ -190,8 +190,8 @@ export class BoardsDataStore implements FrontendApplicationContribution { } } - protected fireChanged(): void { - this.onChangedEmitter.fire(); + protected fireChanged(...fqbn: string[]): void { + this.onChangedEmitter.fire(fqbn); } } diff --git a/arduino-ide-extension/src/browser/contributions/ino-language.ts b/arduino-ide-extension/src/browser/contributions/ino-language.ts index 1002b44bd..6a783b494 100644 --- a/arduino-ide-extension/src/browser/contributions/ino-language.ts +++ b/arduino-ide-extension/src/browser/contributions/ino-language.ts @@ -3,8 +3,10 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import { Mutex } from 'async-mutex'; import { ArduinoDaemon, + assertSanitizedFqbn, BoardsService, ExecutableService, + sanitizeFqbn, } from '../../common/protocol'; import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; import { BoardsConfig } from '../boards/boards-config'; @@ -12,6 +14,7 @@ import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { HostedPluginEvents } from '../hosted-plugin-events'; import { NotificationCenter } from '../notification-center'; import { SketchContribution, URI } from './contribution'; +import { BoardsDataStore } from '../boards/boards-data-store'; @injectable() export class InoLanguage extends SketchContribution { @@ -33,6 +36,9 @@ export class InoLanguage extends SketchContribution { @inject(NotificationCenter) private readonly notificationCenter: NotificationCenter; + @inject(BoardsDataStore) + private readonly boardDataStore: BoardsDataStore; + private readonly toDispose = new DisposableCollection(); private readonly languageServerStartMutex = new Mutex(); private languageServerFqbn?: string; @@ -76,6 +82,26 @@ export class InoLanguage extends SketchContribution { this.notificationCenter.onPlatformDidInstall(() => forceRestart()), this.notificationCenter.onPlatformDidUninstall(() => forceRestart()), this.notificationCenter.onDidReinitialize(() => forceRestart()), + this.boardDataStore.onChanged((dataChangePerFqbn) => { + if (this.languageServerFqbn) { + const sanitizedFqbn = sanitizeFqbn(this.languageServerFqbn); + if (!sanitizeFqbn) { + throw new Error( + `Failed to sanitize the FQBN of the running language server. FQBN with the board settings was: ${this.languageServerFqbn}` + ); + } + const matchingFqbn = dataChangePerFqbn.find( + (fqbn) => sanitizedFqbn === fqbn + ); + const { boardsConfig } = this.boardsServiceProvider; + if ( + matchingFqbn && + boardsConfig.selectedBoard?.fqbn === matchingFqbn + ) { + start(boardsConfig); + } + } + }), ]); start(this.boardsServiceProvider.boardsConfig); } @@ -121,11 +147,18 @@ export class InoLanguage extends SketchContribution { } return; } - if (!forceStart && fqbn === this.languageServerFqbn) { + assertSanitizedFqbn(fqbn); + const fqbnWithConfig = await this.boardDataStore.appendConfigToFqbn(fqbn); + if (!fqbnWithConfig) { + throw new Error( + `Failed to append boards config to the FQBN. Original FQBN was: ${fqbn}` + ); + } + if (!forceStart && fqbnWithConfig === this.languageServerFqbn) { // NOOP return; } - this.logger.info(`Starting language server: ${fqbn}`); + this.logger.info(`Starting language server: ${fqbnWithConfig}`); const log = this.preferences.get('arduino.language.log'); const realTimeDiagnostics = this.preferences.get( 'arduino.language.realTimeDiagnostics' @@ -161,7 +194,7 @@ export class InoLanguage extends SketchContribution { log: currentSketchPath ? currentSketchPath : log, cliDaemonInstance: '1', board: { - fqbn, + fqbn: fqbnWithConfig, name: name ? `"${name}"` : undefined, }, realTimeDiagnostics, @@ -170,7 +203,7 @@ export class InoLanguage extends SketchContribution { ), ]); } catch (e) { - console.log(`Failed to start language server for ${fqbn}`, e); + console.log(`Failed to start language server. Original FQBN: ${fqbn}`, e); this.languageServerFqbn = undefined; } finally { release(); diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 735379d84..3bef23f62 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -1,6 +1,6 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import { Emitter } from '@theia/core/lib/common/event'; -import { CoreService, Port } from '../../common/protocol'; +import { CoreService, Port, sanitizeFqbn } from '../../common/protocol'; import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { @@ -170,7 +170,7 @@ export class UploadSketch extends CoreServiceContribution { const [fqbn, { selectedProgrammer: programmer }, verify, verbose] = await Promise.all([ verifyOptions.fqbn, // already decorated FQBN - this.boardsDataStore.getData(this.sanitizeFqbn(verifyOptions.fqbn)), + this.boardsDataStore.getData(sanitizeFqbn(verifyOptions.fqbn)), this.preferences.get('arduino.upload.verify'), this.preferences.get('arduino.upload.verbose'), ]); @@ -207,19 +207,6 @@ export class UploadSketch extends CoreServiceContribution { } return port; } - - /** - * Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to - * `VENDOR:ARCHITECTURE:BOARD_ID` format. - * See the details of the `{build.fqbn}` entry in the [specs](https://arduino.github.io/arduino-cli/latest/platform-specification/#global-predefined-properties). - */ - private sanitizeFqbn(fqbn: string | undefined): string | undefined { - if (!fqbn) { - return undefined; - } - const [vendor, arch, id] = fqbn.split(':'); - return `${vendor}:${arch}:${id}`; - } } export namespace UploadSketch { diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index 146d53c06..d3add70d3 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -623,3 +623,27 @@ export namespace Board { })); } } + +/** + * Throws an error if the `fqbn` argument is not sanitized. A sanitized FQBN has the `VENDOR:ARCHITECTURE:BOARD_ID` construct. + */ +export function assertSanitizedFqbn(fqbn: string): void { + if (fqbn.split(':').length !== 3) { + throw new Error( + `Expected a sanitized FQBN with three segments in the following format: 'VENDOR:ARCHITECTURE:BOARD_ID'. Got ${fqbn} instead.` + ); + } +} + +/** + * Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to + * `VENDOR:ARCHITECTURE:BOARD_ID` format. + * See the details of the `{build.fqbn}` entry in the [specs](https://arduino.github.io/arduino-cli/latest/platform-specification/#global-predefined-properties). + */ +export function sanitizeFqbn(fqbn: string | undefined): string | undefined { + if (!fqbn) { + return undefined; + } + const [vendor, arch, id] = fqbn.split(':'); + return `${vendor}:${arch}:${id}`; +}