import { DisposableCollection } from '@theia/core/lib/common/disposable'; import URI from '@theia/core/lib/common/uri'; import { inject, injectable } from '@theia/core/shared/inversify'; import type { ArduinoState } from 'vscode-arduino-api'; import { BoardsConfig, BoardsService, CompileSummary, PortIdentifier, resolveDetectedPort, } from '../../common/protocol'; import { toApiBoardDetails, toApiCompileSummary, toApiPort, } from '../../common/protocol/arduino-context-mapper'; import { BoardsDataStore } from '../boards/boards-data-store'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { HostedPluginSupport } from '../hosted/hosted-plugin-support'; import { CurrentSketch } from '../sketches-service-client-impl'; import { SketchContribution } from './contribution'; import { CompileSummaryProvider } from './verify-sketch'; /** * (non-API) exported for tests */ export interface UpdateStateParams { readonly key: keyof T; readonly value: T[keyof T]; } /** * Contribution for updating the Arduino state, such as the FQBN, selected port, and sketch path changes via commands, so other VS Code extensions can access it. * See [`vscode-arduino-api`](https://github.com/dankeboy36/vscode-arduino-api#api) for more details. */ @injectable() export class UpdateArduinoState extends SketchContribution { @inject(BoardsService) private readonly boardsService: BoardsService; @inject(BoardsServiceProvider) private readonly boardsServiceProvider: BoardsServiceProvider; @inject(BoardsDataStore) private readonly boardsDataStore: BoardsDataStore; @inject(HostedPluginSupport) private readonly hostedPluginSupport: HostedPluginSupport; @inject(CompileSummaryProvider) private readonly compileSummaryProvider: CompileSummaryProvider; private readonly toDispose = new DisposableCollection(); override onStart(): void { this.toDispose.pushAll([ this.boardsServiceProvider.onBoardsConfigDidChange(() => this.updateBoardsConfig(this.boardsServiceProvider.boardsConfig) ), this.sketchServiceClient.onCurrentSketchDidChange((sketch) => this.updateSketchPath(sketch) ), this.configService.onDidChangeDataDirUri((dataDirUri) => this.updateDataDirPath(dataDirUri) ), this.configService.onDidChangeSketchDirUri((userDirUri) => this.updateUserDirPath(userDirUri) ), this.compileSummaryProvider.onDidChangeCompileSummary( (compilerSummary) => { if (compilerSummary) { this.updateCompileSummary(compilerSummary); } } ), this.boardsDataStore.onDidChange((event) => { const selectedFqbn = this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn; if ( selectedFqbn && event.changes.find((change) => change.fqbn === selectedFqbn) ) { this.updateBoardDetails(selectedFqbn); } }), ]); } override onReady(): void { this.boardsServiceProvider.ready.then(() => { this.updateBoardsConfig(this.boardsServiceProvider.boardsConfig); }); this.updateSketchPath(this.sketchServiceClient.tryGetCurrentSketch()); this.updateUserDirPath(this.configService.tryGetSketchDirUri()); this.updateDataDirPath(this.configService.tryGetDataDirUri()); const { compileSummary } = this.compileSummaryProvider; if (compileSummary) { this.updateCompileSummary(compileSummary); } } onStop(): void { this.toDispose.dispose(); } private async updateSketchPath( sketch: CurrentSketch | undefined ): Promise { const sketchPath = CurrentSketch.isValid(sketch) ? new URI(sketch.uri).path.fsPath() : undefined; return this.updateState({ key: 'sketchPath', value: sketchPath }); } private async updateCompileSummary( compileSummary: CompileSummary ): Promise { const apiCompileSummary = toApiCompileSummary(compileSummary); return this.updateState({ key: 'compileSummary', value: apiCompileSummary, }); } private async updateBoardsConfig(boardsConfig: BoardsConfig): Promise { const fqbn = boardsConfig.selectedBoard?.fqbn; const port = boardsConfig.selectedPort; await this.updateFqbn(fqbn); await this.updateBoardDetails(fqbn); await this.updatePort(port); } private async updateFqbn(fqbn: string | undefined): Promise { await this.updateState({ key: 'fqbn', value: fqbn }); } private async updateBoardDetails(fqbn: string | undefined): Promise { const unset = () => this.updateState({ key: 'boardDetails', value: undefined }); if (!fqbn) { return unset(); } const [details, persistedData] = await Promise.all([ this.boardsService.getBoardDetails({ fqbn }), this.boardsDataStore.getData(fqbn), ]); if (!details) { return unset(); } const apiBoardDetails = toApiBoardDetails({ ...details, configOptions: BoardsDataStore.Data.EMPTY === persistedData ? details.configOptions : persistedData.configOptions.slice(), }); return this.updateState({ key: 'boardDetails', value: apiBoardDetails, }); } private async updatePort(port: PortIdentifier | undefined): Promise { const resolvedPort = port && resolveDetectedPort(port, this.boardsServiceProvider.detectedPorts); const apiPort = resolvedPort && toApiPort(resolvedPort); return this.updateState({ key: 'port', value: apiPort }); } private async updateUserDirPath(userDirUri: URI | undefined): Promise { const userDirPath = userDirUri?.path.fsPath(); return this.updateState({ key: 'userDirPath', value: userDirPath, }); } private async updateDataDirPath(dataDirUri: URI | undefined): Promise { const dataDirPath = dataDirUri?.path.fsPath(); return this.updateState({ key: 'dataDirPath', value: dataDirPath, }); } private async updateState( params: UpdateStateParams ): Promise { await this.hostedPluginSupport.didStart; return this.commandService.executeCommand('arduinoAPI.updateState', params); } }