diff --git a/.vscode/launch.json b/.vscode/launch.json index 3c54efee9..11c115550 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -94,6 +94,13 @@ "--colors", "**/${fileBasenameNoExtension}.js" ], + "outFiles": [ + "${workspaceRoot}/electron-app/src-gen/backend/*.js", + "${workspaceRoot}/electron-app/src-gen/frontend/*.js", + "${workspaceRoot}/electron-app/lib/**/*.js", + "${workspaceRoot}/arduino-ide-extension/lib/**/*.js", + "${workspaceRoot}/node_modules/@theia/**/*.js" + ], "env": { "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", "IDE2_TEST": "true" diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 05c44e0c3..fa38215ef 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -123,14 +123,14 @@ "mockdate": "^3.0.5", "moment": "^2.24.0", "ncp": "^2.0.0", - "protoc": "^1.0.4", "rimraf": "^2.6.1", "shelljs": "^0.8.3", "uuid": "^3.2.1", "yargs": "^11.1.0" }, "optionalDependencies": { - "grpc-tools": "^1.9.0" + "grpc-tools": "^1.9.0", + "protoc": "^1.0.4" }, "mocha": { "require": [ @@ -172,10 +172,14 @@ ], "arduino": { "arduino-cli": { - "version": "0.33.1" + "version": { + "owner": "cmaglie", + "repo": "arduino-cli", + "commitish": "862bbe2" + } }, "arduino-fwuploader": { - "version": "2.2.2" + "version": "2.3.0" }, "arduino-language-server": { "version": "0.7.4" diff --git a/arduino-ide-extension/scripts/utils.js b/arduino-ide-extension/scripts/utils.js index eeeee37eb..b932b444c 100644 --- a/arduino-ide-extension/scripts/utils.js +++ b/arduino-ide-extension/scripts/utils.js @@ -113,6 +113,8 @@ function buildFromGit(command, version, destinationPath, taskName) { shell.echo(`<<< Checked out ${commitish}.`); } + exec('git', ['-C', tempRepoPath, 'rev-parse', '--short', 'HEAD'], shell); + shell.echo(`>>> Building the ${taskName}...`); exec(command, ['build'], shell, { cwd: tempRepoPath, encoding: 'utf8' }); shell.echo(`<<< Done ${taskName} build.`); diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index e29e98ca3..9c787d814 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -27,7 +27,10 @@ import { SketchesServiceClientImpl } from './sketches-service-client-impl'; import { CoreService, CoreServicePath } from '../common/protocol/core-service'; import { BoardsListWidget } from './boards/boards-list-widget'; import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution'; -import { BoardsServiceProvider } from './boards/boards-service-provider'; +import { + BoardListDumper, + BoardsServiceProvider, +} from './boards/boards-service-provider'; import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { WorkspaceService } from './theia/workspace/workspace-service'; import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution'; @@ -61,7 +64,6 @@ import { BoardsConfigDialog, BoardsConfigDialogProps, } from './boards/boards-config-dialog'; -import { BoardsConfigDialogWidget } from './boards/boards-config-dialog-widget'; import { ScmContribution as TheiaScmContribution } from '@theia/scm/lib/browser/scm-contribution'; import { ScmContribution } from './theia/scm/scm-contribution'; import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; @@ -100,7 +102,7 @@ import { FrontendConnectionStatusService as TheiaFrontendConnectionStatusService, ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution, } from '@theia/core/lib/browser/connection-status-service'; -import { BoardsDataMenuUpdater } from './boards/boards-data-menu-updater'; +import { BoardsDataMenuUpdater } from './contributions/boards-data-menu-updater'; import { BoardsDataStore } from './boards/boards-data-store'; import { ILogger } from '@theia/core/lib/common/logger'; import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider'; @@ -445,11 +447,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BoardsServiceProvider).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).toService(BoardsServiceProvider); bind(CommandContribution).toService(BoardsServiceProvider); + bind(BoardListDumper).toSelf().inSingletonScope(); // To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board. - bind(FrontendApplicationContribution) - .to(BoardsDataMenuUpdater) - .inSingletonScope(); bind(BoardsDataStore).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).toService(BoardsDataStore); // Logger for the Arduino daemon @@ -478,7 +478,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(OpenHandler).toService(BoardsListWidgetFrontendContribution); // Board select dialog - bind(BoardsConfigDialogWidget).toSelf().inSingletonScope(); bind(BoardsConfigDialog).toSelf().inSingletonScope(); bind(BoardsConfigDialogProps).toConstantValue({ title: nls.localize( @@ -751,6 +750,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { Contribution.configure(bind, CloudSketchbookContribution); Contribution.configure(bind, CreateCloudCopy); Contribution.configure(bind, UpdateArduinoState); + Contribution.configure(bind, BoardsDataMenuUpdater); bindContributionProvider(bind, StartupTaskProvider); bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window diff --git a/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts b/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts index 6d95ea661..4b0f45e69 100644 --- a/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts +++ b/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts @@ -1,281 +1,229 @@ -import { injectable, inject } from '@theia/core/shared/inversify'; -import { MessageService } from '@theia/core/lib/common/message-service'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; +import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import { MessageService } from '@theia/core/lib/common/message-service'; +import { MessageType } from '@theia/core/lib/common/message-service-protocol'; +import { nls } from '@theia/core/lib/common/nls'; +import { notEmpty } from '@theia/core/lib/common/objects'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager'; +import { InstallManually } from '../../common/nls'; +import { Installable, ResponseServiceClient } from '../../common/protocol'; import { - BoardsService, + BoardIdentifier, BoardsPackage, - Board, - Port, + BoardsService, + createPlatformIdentifier, + isBoardIdentifierChangeEvent, + PlatformIdentifier, + platformIdentifierEquals, + serializePlatformIdentifier, } from '../../common/protocol/boards-service'; +import { NotificationCenter } from '../notification-center'; import { BoardsServiceProvider } from './boards-service-provider'; -import { Installable, ResponseServiceClient } from '../../common/protocol'; import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution'; -import { nls } from '@theia/core/lib/common'; -import { NotificationCenter } from '../notification-center'; -import { InstallManually } from '../../common/nls'; - -interface AutoInstallPromptAction { - // isAcceptance, whether or not the action indicates acceptance of auto-install proposal - isAcceptance?: boolean; - key: string; - handler: (...args: unknown[]) => unknown; -} - -type AutoInstallPromptActions = AutoInstallPromptAction[]; /** - * Listens on `BoardsConfig.Config` changes, if a board is selected which does not + * Listens on `BoardsConfigChangeEvent`s, if a board is selected which does not * have the corresponding core installed, it proposes the user to install the core. */ - -// * Cases in which we do not show the auto-install prompt: -// 1. When a related platform is already installed -// 2. When a prompt is already showing in the UI -// 3. When a board is unplugged @injectable() export class BoardsAutoInstaller implements FrontendApplicationContribution { @inject(NotificationCenter) private readonly notificationCenter: NotificationCenter; - @inject(MessageService) - protected readonly messageService: MessageService; - + private readonly messageService: MessageService; + @inject(NotificationManager) + private readonly notificationManager: NotificationManager; @inject(BoardsService) - protected readonly boardsService: BoardsService; - + private readonly boardsService: BoardsService; @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; - + private readonly boardsServiceProvider: BoardsServiceProvider; @inject(ResponseServiceClient) - protected readonly responseService: ResponseServiceClient; - + private readonly responseService: ResponseServiceClient; @inject(BoardsListWidgetFrontendContribution) - protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution; + private readonly boardsManagerWidgetContribution: BoardsListWidgetFrontendContribution; // Workaround for https://github.com/eclipse-theia/theia/issues/9349 - protected notifications: Board[] = []; - - // * "refusal" meaning a "prompt action" not accepting the auto-install offer ("X" or "install manually") - // we can use "portSelectedOnLastRefusal" to deduce when a board is unplugged after a user has "refused" - // an auto-install prompt. Important to know as we do not want "an unplug" to trigger a "refused" prompt - // showing again - private portSelectedOnLastRefusal: Port | undefined; - private lastRefusedPackageId: string | undefined; + private readonly installNotificationInfos: Readonly<{ + boardName: string; + platformId: string; + notificationId: string; + }>[] = []; + private readonly toDispose = new DisposableCollection(); onStart(): void { - const setEventListeners = () => { - this.boardsServiceClient.onBoardsConfigChanged((config) => { - const { selectedBoard, selectedPort } = config; - - const boardWasUnplugged = - !selectedPort && this.portSelectedOnLastRefusal; - - this.clearLastRefusedPromptInfo(); - - if ( - boardWasUnplugged || - !selectedBoard || - this.promptAlreadyShowingForBoard(selectedBoard) - ) { - return; - } - - this.ensureCoreExists(selectedBoard, selectedPort); - }); - - // we "clearRefusedPackageInfo" if a "refused" package is eventually - // installed, though this is not strictly necessary. It's more of a - // cleanup, to ensure the related variables are representative of - // current state. - this.notificationCenter.onPlatformDidInstall((installed) => { - if (this.lastRefusedPackageId === installed.item.id) { - this.clearLastRefusedPromptInfo(); + this.toDispose.pushAll([ + this.boardsServiceProvider.onBoardsConfigDidChange((event) => { + if (isBoardIdentifierChangeEvent(event)) { + this.ensureCoreExists(event.selectedBoard); } - }); - }; - - // we should invoke this.ensureCoreExists only once we're sure - // everything has been reconciled - this.boardsServiceClient.reconciled.then(() => { - const { selectedBoard, selectedPort } = - this.boardsServiceClient.boardsConfig; - - if (selectedBoard) { - this.ensureCoreExists(selectedBoard, selectedPort); - } - - setEventListeners(); + }), + this.notificationCenter.onPlatformDidInstall((event) => + this.clearAllNotificationForPlatform(event.item.id) + ), + ]); + this.boardsServiceProvider.ready.then(() => { + const { selectedBoard } = this.boardsServiceProvider.boardsConfig; + this.ensureCoreExists(selectedBoard); }); } - private removeNotificationByBoard(selectedBoard: Board): void { - const index = this.notifications.findIndex((notification) => - Board.sameAs(notification, selectedBoard) - ); - if (index !== -1) { - this.notifications.splice(index, 1); + private async findPlatformToInstall( + selectedBoard: BoardIdentifier + ): Promise { + const platformId = await this.findPlatformIdToInstall(selectedBoard); + if (!platformId) { + return undefined; } + const id = serializePlatformIdentifier(platformId); + const platform = await this.boardsService.getBoardPackage({ id }); + if (!platform) { + console.warn(`Could not resolve platform for ID: ${id}`); + return undefined; + } + if (platform.installedVersion) { + return undefined; + } + return platform; } - private clearLastRefusedPromptInfo(): void { - this.lastRefusedPackageId = undefined; - this.portSelectedOnLastRefusal = undefined; - } - - private setLastRefusedPromptInfo( - packageId: string, - selectedPort?: Port - ): void { - this.lastRefusedPackageId = packageId; - this.portSelectedOnLastRefusal = selectedPort; - } - - private promptAlreadyShowingForBoard(board: Board): boolean { - return Boolean( - this.notifications.find((notification) => - Board.sameAs(notification, board) - ) - ); - } - - protected ensureCoreExists(selectedBoard: Board, selectedPort?: Port): void { - this.notifications.push(selectedBoard); - this.boardsService.search({}).then((packages) => { - const candidate = this.getInstallCandidate(packages, selectedBoard); - - if (candidate) { - this.showAutoInstallPrompt(candidate, selectedBoard, selectedPort); - } else { - this.removeNotificationByBoard(selectedBoard); + private async findPlatformIdToInstall( + selectedBoard: BoardIdentifier + ): Promise { + const selectedBoardPlatformId = createPlatformIdentifier(selectedBoard); + // The board is installed or the FQBN is available from the `board list watch` for Arduino boards. The latter might change! + if (selectedBoardPlatformId) { + const installedPlatforms = + await this.boardsService.getInstalledPlatforms(); + const installedPlatformIds = installedPlatforms + .map((platform) => createPlatformIdentifier(platform.id)) + .filter(notEmpty); + if ( + installedPlatformIds.every( + (installedPlatformId) => + !platformIdentifierEquals( + installedPlatformId, + selectedBoardPlatformId + ) + ) + ) { + return selectedBoardPlatformId; } - }); + } else { + // IDE2 knows that selected board is not installed. Look for board `name` match in not yet installed platforms. + // The order should be correct when there is a board name collision (e.g. Arduino Nano RP2040 from Arduino Mbed OS Nano Boards, [DEPRECATED] Arduino Mbed OS Nano Boards). The CLI boosts the platforms, so picking the first name match should be fine. + const platforms = await this.boardsService.search({}); + for (const platform of platforms) { + // Ignore installed platforms + if (platform.installedVersion) { + continue; + } + if ( + platform.boards.some((board) => board.name === selectedBoard.name) + ) { + const platformId = createPlatformIdentifier(platform.id); + if (platformId) { + return platformId; + } + } + } + } + return undefined; } - private getInstallCandidate( - packages: BoardsPackage[], - selectedBoard: Board - ): BoardsPackage | undefined { - // filter packagesForBoard selecting matches from the cli (installed packages) - // and matches based on the board name - // NOTE: this ensures the Deprecated & new packages are all in the array - // so that we can check if any of the valid packages is already installed - const packagesForBoard = packages.filter( - (pkg) => - BoardsPackage.contains(selectedBoard, pkg) || - pkg.boards.some((board) => board.name === selectedBoard.name) - ); - - // check if one of the packages for the board is already installed. if so, no hint - if (packagesForBoard.some(({ installedVersion }) => !!installedVersion)) { + private async ensureCoreExists( + selectedBoard: BoardIdentifier | undefined + ): Promise { + if (!selectedBoard) { return; } + const candidate = await this.findPlatformToInstall(selectedBoard); + if (!candidate) { + return; + } + const platformIdToInstall = candidate.id; + const selectedBoardName = selectedBoard.name; + if ( + this.installNotificationInfos.some( + ({ boardName, platformId }) => + platformIdToInstall === platformId && selectedBoardName === boardName + ) + ) { + // Already has a notification for the board with the same platform. Nothing to do. + return; + } + this.clearAllNotificationForPlatform(platformIdToInstall); - // filter the installable (not installed) packages, - // CLI returns the packages already sorted with the deprecated ones at the end of the list - // in order to ensure the new ones are preferred - const candidates = packagesForBoard.filter( - ({ installedVersion }) => !installedVersion - ); - - return candidates[0]; - } - - private showAutoInstallPrompt( - candidate: BoardsPackage, - selectedBoard: Board, - selectedPort?: Port - ): void { - const candidateName = candidate.name; const version = candidate.availableVersions[0] ? `[v ${candidate.availableVersions[0]}]` : ''; - - const info = this.generatePromptInfoText( - candidateName, - version, - selectedBoard.name - ); - - const actions = this.createPromptActions(candidate); - - const onRefuse = () => { - this.setLastRefusedPromptInfo(candidate.id, selectedPort); - }; - const handleAction = this.createOnAnswerHandler(actions, onRefuse); - - const onAnswer = (answer: string) => { - this.removeNotificationByBoard(selectedBoard); - - handleAction(answer); - }; - - this.messageService - .info(info, ...actions.map((action) => action.key)) - .then(onAnswer); - } - - private generatePromptInfoText( - candidateName: string, - version: string, - boardName: string - ): string { - return nls.localize( + const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes'); + const message = nls.localize( 'arduino/board/installNow', 'The "{0} {1}" core has to be installed for the currently selected "{2}" board. Do you want to install it now?', - candidateName, + candidate.name, version, - boardName + selectedBoard.name ); + const notificationId = this.notificationId(message, InstallManually, yes); + this.installNotificationInfos.push({ + boardName: selectedBoardName, + platformId: platformIdToInstall, + notificationId, + }); + const answer = await this.messageService.info( + message, + InstallManually, + yes + ); + if (answer) { + const index = this.installNotificationInfos.findIndex( + ({ boardName, platformId }) => + platformIdToInstall === platformId && selectedBoardName === boardName + ); + if (index !== -1) { + this.installNotificationInfos.splice(index, 1); + } + if (answer === yes) { + await Installable.installWithProgress({ + installable: this.boardsService, + item: candidate, + messageService: this.messageService, + responseService: this.responseService, + version: candidate.availableVersions[0], + }); + return; + } + if (answer === InstallManually) { + this.boardsManagerWidgetContribution + .openView({ reveal: true }) + .then((widget) => + widget.refresh({ + query: candidate.name.toLocaleLowerCase(), + type: 'All', + }) + ); + } + } } - private createPromptActions( - candidate: BoardsPackage - ): AutoInstallPromptActions { - const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes'); - - const actions: AutoInstallPromptActions = [ - { - key: InstallManually, - handler: () => { - this.boardsManagerFrontendContribution - .openView({ reveal: true }) - .then((widget) => - widget.refresh({ - query: candidate.name.toLocaleLowerCase(), - type: 'All', - }) - ); - }, - }, - { - isAcceptance: true, - key: yes, - handler: () => { - return Installable.installWithProgress({ - installable: this.boardsService, - item: candidate, - messageService: this.messageService, - responseService: this.responseService, - version: candidate.availableVersions[0], - }); - }, - }, - ]; - - return actions; + private clearAllNotificationForPlatform(predicatePlatformId: string): void { + // Discard all install notifications for the same platform. + const notificationsLength = this.installNotificationInfos.length; + for (let i = notificationsLength - 1; i >= 0; i--) { + const { notificationId, platformId } = this.installNotificationInfos[i]; + if (platformId === predicatePlatformId) { + this.installNotificationInfos.splice(i, 1); + this.notificationManager.clear(notificationId); + } + } } - private createOnAnswerHandler( - actions: AutoInstallPromptActions, - onRefuse?: () => void - ): (answer: string) => void { - return (answer) => { - const actionToHandle = actions.find((action) => action.key === answer); - actionToHandle?.handler(); - - if (!actionToHandle?.isAcceptance && onRefuse) { - onRefuse(); - } - }; + private notificationId(message: string, ...actions: string[]): string { + return this.notificationManager['getMessageId']({ + text: message, + actions, + type: MessageType.Info, + }); } } diff --git a/arduino-ide-extension/src/browser/boards/boards-config-component.tsx b/arduino-ide-extension/src/browser/boards/boards-config-component.tsx new file mode 100644 index 000000000..b43a1cb38 --- /dev/null +++ b/arduino-ide-extension/src/browser/boards/boards-config-component.tsx @@ -0,0 +1,328 @@ +import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import { Event } from '@theia/core/lib/common/event'; +import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state'; +import { nls } from '@theia/core/lib/common/nls'; +import * as React from '@theia/core/shared/react'; +import { EditBoardsConfigActionParams } from '../../common/protocol/board-list'; +import { + Board, + BoardIdentifier, + BoardWithPackage, + DetectedPort, + findMatchingPortIndex, + Port, + PortIdentifier, +} from '../../common/protocol/boards-service'; +import { Defined } from '../../common/types'; +import { NotificationCenter } from '../notification-center'; +import { BoardsConfigDialogState } from './boards-config-dialog'; + +namespace BoardsConfigComponent { + export interface Props { + /** + * This is not the real config, it's only living in the dialog. Users can change it without update and can cancel any modifications. + */ + readonly boardsConfig: BoardsConfigDialogState; + readonly searchSet: BoardIdentifier[] | undefined; + readonly notificationCenter: NotificationCenter; + readonly appState: FrontendApplicationState; + readonly onFocusNodeSet: (element: HTMLElement | undefined) => void; + readonly onFilteredTextDidChangeEvent: Event< + Defined + >; + readonly onAppStateDidChange: Event; + readonly onBoardSelected: (board: BoardIdentifier) => void; + readonly onPortSelected: (port: PortIdentifier) => void; + readonly searchBoards: (query?: { + query?: string; + }) => Promise; + readonly ports: ( + predicate?: (port: DetectedPort) => boolean + ) => readonly DetectedPort[]; + } + + export interface State { + searchResults: Array; + showAllPorts: boolean; + query: string; + } +} + +export abstract class Item extends React.Component<{ + item: T; + label: string; + selected: boolean; + onClick: (item: T) => void; + missing?: boolean; + details?: string; +}> { + override render(): React.ReactNode { + const { selected, label, missing, details } = this.props; + const classNames = ['item']; + if (selected) { + classNames.push('selected'); + } + if (missing === true) { + classNames.push('missing'); + } + return ( +
+
{label}
+ {!details ? '' :
{details}
} + {!selected ? ( + '' + ) : ( +
+ +
+ )} +
+ ); + } + + private readonly onClick = () => { + this.props.onClick(this.props.item); + }; +} + +export class BoardsConfigComponent extends React.Component< + BoardsConfigComponent.Props, + BoardsConfigComponent.State +> { + private readonly toDispose: DisposableCollection; + + constructor(props: BoardsConfigComponent.Props) { + super(props); + this.state = { + searchResults: [], + showAllPorts: false, + query: '', + }; + this.toDispose = new DisposableCollection(); + } + + override componentDidMount(): void { + this.toDispose.pushAll([ + this.props.onAppStateDidChange(async (state) => { + if (state === 'ready') { + const searchResults = await this.queryBoards({}); + this.setState({ searchResults }); + } + }), + this.props.notificationCenter.onPlatformDidInstall(() => + this.updateBoards(this.state.query) + ), + this.props.notificationCenter.onPlatformDidUninstall(() => + this.updateBoards(this.state.query) + ), + this.props.notificationCenter.onIndexUpdateDidComplete(() => + this.updateBoards(this.state.query) + ), + this.props.notificationCenter.onDaemonDidStart(() => + this.updateBoards(this.state.query) + ), + this.props.notificationCenter.onDaemonDidStop(() => + this.setState({ searchResults: [] }) + ), + this.props.onFilteredTextDidChangeEvent((query) => { + if (typeof query === 'string') { + this.setState({ query }, () => this.updateBoards(this.state.query)); + } + }), + ]); + } + + override componentWillUnmount(): void { + this.toDispose.dispose(); + } + + private readonly updateBoards = ( + eventOrQuery: React.ChangeEvent | string = '' + ) => { + const query = + typeof eventOrQuery === 'string' + ? eventOrQuery + : eventOrQuery.target.value.toLowerCase(); + this.setState({ query }); + this.queryBoards({ query }).then((searchResults) => + this.setState({ searchResults }) + ); + }; + + private readonly queryBoards = async ( + options: { query?: string } = {} + ): Promise> => { + const result = await this.props.searchBoards(options); + const { searchSet } = this.props; + if (searchSet) { + return result.filter((board) => + searchSet.some( + (restriction) => + restriction.fqbn === board.fqbn || restriction.name === board.fqbn + ) + ); + } + return result; + }; + + private readonly toggleFilterPorts = () => { + this.setState({ showAllPorts: !this.state.showAllPorts }); + }; + + private readonly selectPort = (selectedPort: PortIdentifier) => { + this.props.onPortSelected(selectedPort); + }; + + private readonly selectBoard = (selectedBoard: BoardWithPackage) => { + this.props.onBoardSelected(selectedBoard); + }; + + private readonly focusNodeSet = (element: HTMLElement | null) => { + this.props.onFocusNodeSet(element || undefined); + }; + + override render(): React.ReactNode { + return ( + <> + {this.renderContainer( + nls.localize('arduino/board/boards', 'boards'), + this.renderBoards.bind(this) + )} + {this.renderContainer( + nls.localize('arduino/board/ports', 'ports'), + this.renderPorts.bind(this), + this.renderPortsFooter.bind(this) + )} + + ); + } + + private renderContainer( + title: string, + contentRenderer: () => React.ReactNode, + footerRenderer?: () => React.ReactNode + ): React.ReactNode { + return ( +
+
+
{title}
+ {contentRenderer()} +
{footerRenderer ? footerRenderer() : ''}
+
+
+ ); + } + + private renderBoards(): React.ReactNode { + const { boardsConfig } = this.props; + const { searchResults, query } = this.state; + // Board names are not unique per core https://github.com/arduino/arduino-pro-ide/issues/262#issuecomment-661019560 + // It is tricky when the core is not yet installed, no FQBNs are available. + const distinctBoards = new Map(); + const toKey = ({ name, packageName, fqbn }: Board.Detailed) => + !!fqbn ? `${name}-${packageName}-${fqbn}` : `${name}-${packageName}`; + for (const board of Board.decorateBoards( + boardsConfig.selectedBoard, + searchResults + )) { + const key = toKey(board); + if (!distinctBoards.has(key)) { + distinctBoards.set(key, board); + } + } + + const boardsList = Array.from(distinctBoards.values()).map((board) => ( + + key={toKey(board)} + item={board} + label={board.name} + details={board.details} + selected={board.selected} + onClick={this.selectBoard} + missing={board.missing} + /> + )); + + return ( + +
+ + +
+ {boardsList.length > 0 ? ( +
{boardsList}
+ ) : ( +
+ {nls.localize( + 'arduino/board/noBoardsFound', + 'No boards found for "{0}"', + query + )} +
+ )} +
+ ); + } + + private renderPorts(): React.ReactNode { + const predicate = this.state.showAllPorts ? undefined : Port.isVisiblePort; + const detectedPorts = this.props.ports(predicate); + const matchingIndex = findMatchingPortIndex( + this.props.boardsConfig.selectedPort, + detectedPorts + ); + return !detectedPorts.length ? ( +
+ {nls.localize('arduino/board/noPortsDiscovered', 'No ports discovered')} +
+ ) : ( +
+ {detectedPorts.map((detectedPort, index) => ( + + key={`${Port.keyOf(detectedPort.port)}`} + item={detectedPort.port} + label={Port.toString(detectedPort.port)} + selected={index === matchingIndex} + onClick={this.selectPort} + /> + ))} +
+ ); + } + + private renderPortsFooter(): React.ReactNode { + return ( +
+ +
+ ); + } +} diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx b/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx deleted file mode 100644 index ec39e6b30..000000000 --- a/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from '@theia/core/shared/react'; -import { injectable, inject } from '@theia/core/shared/inversify'; -import { Emitter } from '@theia/core/lib/common/event'; -import { ReactWidget, Message } from '@theia/core/lib/browser'; -import { BoardsService } from '../../common/protocol/boards-service'; -import { BoardsConfig } from './boards-config'; -import { BoardsServiceProvider } from './boards-service-provider'; -import { NotificationCenter } from '../notification-center'; - -@injectable() -export class BoardsConfigDialogWidget extends ReactWidget { - @inject(BoardsService) - protected readonly boardsService: BoardsService; - - @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; - - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; - - protected readonly onFilterTextDidChangeEmitter = new Emitter(); - protected readonly onBoardConfigChangedEmitter = - new Emitter(); - readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event; - - protected focusNode: HTMLElement | undefined; - - constructor() { - super(); - this.id = 'select-board-dialog'; - this.toDispose.pushAll([ - this.onBoardConfigChangedEmitter, - this.onFilterTextDidChangeEmitter, - ]); - } - - search(query: string): void { - this.onFilterTextDidChangeEmitter.fire(query); - } - - protected fireConfigChanged = (config: BoardsConfig.Config) => { - this.onBoardConfigChangedEmitter.fire(config); - }; - - protected setFocusNode = (element: HTMLElement | undefined) => { - this.focusNode = element; - }; - - protected render(): React.ReactNode { - return ( -
- -
- ); - } - - protected override onActivateRequest(msg: Message): void { - super.onActivateRequest(msg); - if (this.focusNode instanceof HTMLInputElement) { - this.focusNode.select(); - } - (this.focusNode || this.node).focus(); - } -} diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts deleted file mode 100644 index b08c6de36..000000000 --- a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { - injectable, - inject, - postConstruct, -} from '@theia/core/shared/inversify'; -import { Message } from '@theia/core/shared/@phosphor/messaging'; -import { DialogProps, Widget, DialogError } from '@theia/core/lib/browser'; -import { AbstractDialog } from '../theia/dialogs/dialogs'; -import { BoardsConfig } from './boards-config'; -import { BoardsService } from '../../common/protocol/boards-service'; -import { BoardsServiceProvider } from './boards-service-provider'; -import { BoardsConfigDialogWidget } from './boards-config-dialog-widget'; -import { nls } from '@theia/core/lib/common'; - -@injectable() -export class BoardsConfigDialogProps extends DialogProps {} - -@injectable() -export class BoardsConfigDialog extends AbstractDialog { - @inject(BoardsConfigDialogWidget) - protected readonly widget: BoardsConfigDialogWidget; - - @inject(BoardsService) - protected readonly boardService: BoardsService; - - @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; - - protected config: BoardsConfig.Config = {}; - - constructor( - @inject(BoardsConfigDialogProps) - protected override readonly props: BoardsConfigDialogProps - ) { - super({ ...props, maxWidth: 500 }); - - this.node.id = 'select-board-dialog-container'; - this.contentNode.classList.add('select-board-dialog'); - this.contentNode.appendChild(this.createDescription()); - - this.appendCloseButton( - nls.localize('vscode/issueMainService/cancel', 'Cancel') - ); - this.appendAcceptButton(nls.localize('vscode/issueMainService/ok', 'OK')); - } - - @postConstruct() - protected init(): void { - this.toDispose.push( - this.boardsServiceClient.onBoardsConfigChanged((config) => { - this.config = config; - this.update(); - }) - ); - } - - /** - * Pass in an empty string if you want to reset the search term. Using `undefined` has no effect. - */ - override async open( - query: string | undefined = undefined - ): Promise { - if (typeof query === 'string') { - this.widget.search(query); - } - return super.open(); - } - - protected createDescription(): HTMLElement { - const head = document.createElement('div'); - head.classList.add('head'); - - const text = document.createElement('div'); - text.classList.add('text'); - head.appendChild(text); - - for (const paragraph of [ - nls.localize( - 'arduino/board/configDialog1', - 'Select both a Board and a Port if you want to upload a sketch.' - ), - nls.localize( - 'arduino/board/configDialog2', - 'If you only select a Board you will be able to compile, but not to upload your sketch.' - ), - ]) { - const p = document.createElement('div'); - p.textContent = paragraph; - text.appendChild(p); - } - - return head; - } - - protected override onAfterAttach(msg: Message): void { - if (this.widget.isAttached) { - Widget.detach(this.widget); - } - Widget.attach(this.widget, this.contentNode); - this.toDisposeOnDetach.push( - this.widget.onBoardConfigChanged((config) => { - this.config = config; - this.update(); - }) - ); - super.onAfterAttach(msg); - this.update(); - } - - protected override onUpdateRequest(msg: Message): void { - super.onUpdateRequest(msg); - this.widget.update(); - } - - protected override onActivateRequest(msg: Message): void { - super.onActivateRequest(msg); - this.widget.activate(); - } - - protected override handleEnter(event: KeyboardEvent): boolean | void { - if (event.target instanceof HTMLTextAreaElement) { - return false; - } - } - - protected override isValid(value: BoardsConfig.Config): DialogError { - if (!value.selectedBoard) { - if (value.selectedPort) { - return nls.localize( - 'arduino/board/pleasePickBoard', - 'Please pick a board connected to the port you have selected.' - ); - } - return false; - } - return ''; - } - - get value(): BoardsConfig.Config { - return this.config; - } -} diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog.tsx b/arduino-ide-extension/src/browser/boards/boards-config-dialog.tsx new file mode 100644 index 000000000..4254de6ad --- /dev/null +++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog.tsx @@ -0,0 +1,202 @@ +import { DialogError, DialogProps } from '@theia/core/lib/browser/dialogs'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { Emitter } from '@theia/core/lib/common/event'; +import { nls } from '@theia/core/lib/common/nls'; +import { deepClone } from '@theia/core/lib/common/objects'; +import type { Message } from '@theia/core/shared/@phosphor/messaging'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; +import React from '@theia/core/shared/react'; +import type { ReactNode } from '@theia/core/shared/react/index'; +import { EditBoardsConfigActionParams } from '../../common/protocol/board-list'; +import { + BoardIdentifier, + BoardsConfig, + BoardWithPackage, + DetectedPort, + emptyBoardsConfig, + PortIdentifier, +} from '../../common/protocol/boards-service'; +import { Defined } from '../../common/types'; +import { NotificationCenter } from '../notification-center'; +import { ReactDialog } from '../theia/dialogs/dialogs'; +import { BoardsConfigComponent } from './boards-config-component'; +import { BoardsServiceProvider } from './boards-service-provider'; + +@injectable() +export class BoardsConfigDialogProps extends DialogProps {} + +export type BoardsConfigDialogState = Omit & { + selectedBoard: BoardsConfig['selectedBoard'] | BoardWithPackage; +}; + +@injectable() +export class BoardsConfigDialog extends ReactDialog { + @inject(BoardsServiceProvider) + private readonly boardsServiceProvider: BoardsServiceProvider; + @inject(NotificationCenter) + private readonly notificationCenter: NotificationCenter; + @inject(FrontendApplicationStateService) + private readonly appStateService: FrontendApplicationStateService; + + private readonly onFilterTextDidChangeEmitter: Emitter< + Defined + >; + private readonly onBoardSelected = (board: BoardWithPackage): void => { + this._boardsConfig.selectedBoard = board; + this.update(); + }; + private readonly onPortSelected = (port: PortIdentifier): void => { + this._boardsConfig.selectedPort = port; + this.update(); + }; + private readonly setFocusNode = (element: HTMLElement | undefined): void => { + this.focusNode = element; + }; + private readonly searchBoards = (options: { + query?: string; + }): Promise => { + return this.boardsServiceProvider.searchBoards(options); + }; + private readonly ports = ( + predicate?: (port: DetectedPort) => boolean + ): readonly DetectedPort[] => { + return this.boardsServiceProvider.boardList.ports(predicate); + }; + private _boardsConfig: BoardsConfigDialogState; + /** + * When the dialog's boards result set is limited to a subset of boards when searching, this field is set. + */ + private _searchSet: BoardIdentifier[] | undefined; + private focusNode: HTMLElement | undefined; + + constructor( + @inject(BoardsConfigDialogProps) + protected override readonly props: BoardsConfigDialogProps + ) { + super({ ...props, maxWidth: 500 }); + this.node.id = 'select-board-dialog-container'; + this.contentNode.classList.add('select-board-dialog'); + this.appendCloseButton( + nls.localize('vscode/issueMainService/cancel', 'Cancel') + ); + this.appendAcceptButton(nls.localize('vscode/issueMainService/ok', 'OK')); + this._boardsConfig = emptyBoardsConfig(); + this.onFilterTextDidChangeEmitter = new Emitter(); + } + + @postConstruct() + protected init(): void { + this.boardsServiceProvider.onBoardListDidChange(() => { + this._boardsConfig = deepClone(this.boardsServiceProvider.boardsConfig); + this.update(); + }); + this._boardsConfig = deepClone(this.boardsServiceProvider.boardsConfig); + } + + override async open( + params?: EditBoardsConfigActionParams + ): Promise { + this._searchSet = undefined; + this._boardsConfig.selectedBoard = + this.boardsServiceProvider.boardsConfig.selectedBoard; + this._boardsConfig.selectedPort = + this.boardsServiceProvider.boardsConfig.selectedPort; + if (params) { + if (typeof params.query === 'string') { + this.onFilterTextDidChangeEmitter.fire(params.query); + } + if (params.portToSelect) { + this._boardsConfig.selectedPort = params.portToSelect; + } + if (params.boardToSelect) { + this._boardsConfig.selectedBoard = params.boardToSelect; + } + if (params.searchSet) { + this._searchSet = params.searchSet.slice(); + } + } + return super.open(); + } + + protected override onAfterAttach(msg: Message): void { + super.onAfterAttach(msg); + this.update(); + } + + protected override render(): ReactNode { + return ( + <> +
+
+
+ {nls.localize( + 'arduino/board/configDialog1', + 'Select both a Board and a Port if you want to upload a sketch.' + )} +
+
+ {nls.localize( + 'arduino/board/configDialog2', + 'If you only select a Board you will be able to compile, but not to upload your sketch.' + )} +
+
+
+
+
+ +
+
+ + ); + } + + protected override onActivateRequest(msg: Message): void { + super.onActivateRequest(msg); + if (this.focusNode instanceof HTMLInputElement) { + this.focusNode.select(); + } + (this.focusNode || this.node).focus(); + } + + protected override handleEnter(event: KeyboardEvent): boolean | void { + if (event.target instanceof HTMLTextAreaElement) { + return false; + } + } + + protected override isValid(value: BoardsConfig): DialogError { + if (!value.selectedBoard) { + if (value.selectedPort) { + return nls.localize( + 'arduino/board/pleasePickBoard', + 'Please pick a board connected to the port you have selected.' + ); + } + return false; + } + return ''; + } + + get value(): BoardsConfigDialogState { + return this._boardsConfig; + } +} diff --git a/arduino-ide-extension/src/browser/boards/boards-config.tsx b/arduino-ide-extension/src/browser/boards/boards-config.tsx deleted file mode 100644 index af62368fe..000000000 --- a/arduino-ide-extension/src/browser/boards/boards-config.tsx +++ /dev/null @@ -1,432 +0,0 @@ -import React from '@theia/core/shared/react'; -import { Event } from '@theia/core/lib/common/event'; -import { notEmpty } from '@theia/core/lib/common/objects'; -import { MaybePromise } from '@theia/core/lib/common/types'; -import { DisposableCollection } from '@theia/core/lib/common/disposable'; -import { - Board, - Port, - BoardConfig as ProtocolBoardConfig, - BoardWithPackage, -} from '../../common/protocol/boards-service'; -import { NotificationCenter } from '../notification-center'; -import { - AvailableBoard, - BoardsServiceProvider, -} from './boards-service-provider'; -import { naturalCompare } from '../../common/utils'; -import { nls } from '@theia/core/lib/common'; -import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state'; - -export namespace BoardsConfig { - export type Config = ProtocolBoardConfig; - - export interface Props { - readonly boardsServiceProvider: BoardsServiceProvider; - readonly notificationCenter: NotificationCenter; - readonly onConfigChange: (config: Config) => void; - readonly onFocusNodeSet: (element: HTMLElement | undefined) => void; - readonly onFilteredTextDidChangeEvent: Event; - readonly onAppStateDidChange: Event; - } - - export interface State extends Config { - searchResults: Array; - knownPorts: Port[]; - showAllPorts: boolean; - query: string; - } -} - -export abstract class Item extends React.Component<{ - item: T; - label: string; - selected: boolean; - onClick: (item: T) => void; - missing?: boolean; - details?: string; -}> { - override render(): React.ReactNode { - const { selected, label, missing, details } = this.props; - const classNames = ['item']; - if (selected) { - classNames.push('selected'); - } - if (missing === true) { - classNames.push('missing'); - } - return ( -
-
{label}
- {!details ? '' :
{details}
} - {!selected ? ( - '' - ) : ( -
- -
- )} -
- ); - } - - protected onClick = () => { - this.props.onClick(this.props.item); - }; -} - -export class BoardsConfig extends React.Component< - BoardsConfig.Props, - BoardsConfig.State -> { - protected toDispose = new DisposableCollection(); - - constructor(props: BoardsConfig.Props) { - super(props); - - const { boardsConfig } = props.boardsServiceProvider; - this.state = { - searchResults: [], - knownPorts: [], - showAllPorts: false, - query: '', - ...boardsConfig, - }; - } - - override componentDidMount(): void { - this.toDispose.pushAll([ - this.props.onAppStateDidChange((state) => { - if (state === 'ready') { - this.updateBoards(); - this.updatePorts( - this.props.boardsServiceProvider.availableBoards - .map(({ port }) => port) - .filter(notEmpty) - ); - } - }), - this.props.boardsServiceProvider.onAvailablePortsChanged( - ({ newState, oldState }) => { - const removedPorts = oldState.filter( - (oldPort) => - !newState.find((newPort) => Port.sameAs(newPort, oldPort)) - ); - this.updatePorts(newState, removedPorts); - } - ), - this.props.boardsServiceProvider.onBoardsConfigChanged( - ({ selectedBoard, selectedPort }) => { - this.setState({ selectedBoard, selectedPort }, () => - this.fireConfigChanged() - ); - } - ), - this.props.notificationCenter.onPlatformDidInstall(() => - this.updateBoards(this.state.query) - ), - this.props.notificationCenter.onPlatformDidUninstall(() => - this.updateBoards(this.state.query) - ), - this.props.notificationCenter.onIndexUpdateDidComplete(() => - this.updateBoards(this.state.query) - ), - this.props.notificationCenter.onDaemonDidStart(() => - this.updateBoards(this.state.query) - ), - this.props.notificationCenter.onDaemonDidStop(() => - this.setState({ searchResults: [] }) - ), - this.props.onFilteredTextDidChangeEvent((query) => - this.setState({ query }, () => this.updateBoards(this.state.query)) - ), - ]); - } - - override componentWillUnmount(): void { - this.toDispose.dispose(); - } - - protected fireConfigChanged(): void { - const { selectedBoard, selectedPort } = this.state; - this.props.onConfigChange({ selectedBoard, selectedPort }); - } - - protected updateBoards = ( - eventOrQuery: React.ChangeEvent | string = '' - ) => { - const query = - typeof eventOrQuery === 'string' - ? eventOrQuery - : eventOrQuery.target.value.toLowerCase(); - this.setState({ query }); - this.queryBoards({ query }).then((searchResults) => - this.setState({ searchResults }) - ); - }; - - protected updatePorts = (ports: Port[] = [], removedPorts: Port[] = []) => { - this.queryPorts(Promise.resolve(ports)).then(({ knownPorts }) => { - let { selectedPort } = this.state; - // If the currently selected port is not available anymore, unset the selected port. - if (removedPorts.some((port) => Port.sameAs(port, selectedPort))) { - selectedPort = undefined; - } - this.setState({ knownPorts, selectedPort }, () => - this.fireConfigChanged() - ); - }); - }; - - protected queryBoards = ( - options: { query?: string } = {} - ): Promise> => { - return this.props.boardsServiceProvider.searchBoards(options); - }; - - protected get availablePorts(): MaybePromise { - return this.props.boardsServiceProvider.availableBoards - .map(({ port }) => port) - .filter(notEmpty); - } - - protected get availableBoards(): AvailableBoard[] { - return this.props.boardsServiceProvider.availableBoards; - } - - protected queryPorts = async ( - availablePorts: MaybePromise = this.availablePorts - ) => { - // Available ports must be sorted in this order: - // 1. Serial with recognized boards - // 2. Serial with guessed boards - // 3. Serial with incomplete boards - // 4. Network with recognized boards - // 5. Other protocols with recognized boards - const ports = (await availablePorts).sort((left: Port, right: Port) => { - if (left.protocol === 'serial' && right.protocol !== 'serial') { - return -1; - } else if (left.protocol !== 'serial' && right.protocol === 'serial') { - return 1; - } else if (left.protocol === 'network' && right.protocol !== 'network') { - return -1; - } else if (left.protocol !== 'network' && right.protocol === 'network') { - return 1; - } else if (left.protocol === right.protocol) { - // We show ports, including those that have guessed - // or unrecognized boards, so we must sort those too. - const leftBoard = this.availableBoards.find( - (board) => board.port === left - ); - const rightBoard = this.availableBoards.find( - (board) => board.port === right - ); - if (leftBoard && !rightBoard) { - return -1; - } else if (!leftBoard && rightBoard) { - return 1; - } else if (leftBoard?.state! < rightBoard?.state!) { - return -1; - } else if (leftBoard?.state! > rightBoard?.state!) { - return 1; - } - } - return naturalCompare(left.address, right.address); - }); - return { knownPorts: ports }; - }; - - protected toggleFilterPorts = () => { - this.setState({ showAllPorts: !this.state.showAllPorts }); - }; - - protected selectPort = (selectedPort: Port | undefined) => { - this.setState({ selectedPort }, () => this.fireConfigChanged()); - }; - - protected selectBoard = (selectedBoard: BoardWithPackage | undefined) => { - this.setState({ selectedBoard }, () => this.fireConfigChanged()); - }; - - protected focusNodeSet = (element: HTMLElement | null) => { - this.props.onFocusNodeSet(element || undefined); - }; - - override render(): React.ReactNode { - return ( - <> - {this.renderContainer( - nls.localize('arduino/board/boards', 'boards'), - this.renderBoards.bind(this) - )} - {this.renderContainer( - nls.localize('arduino/board/ports', 'ports'), - this.renderPorts.bind(this), - this.renderPortsFooter.bind(this) - )} - - ); - } - - protected renderContainer( - title: string, - contentRenderer: () => React.ReactNode, - footerRenderer?: () => React.ReactNode - ): React.ReactNode { - return ( -
-
-
{title}
- {contentRenderer()} -
{footerRenderer ? footerRenderer() : ''}
-
-
- ); - } - - protected renderBoards(): React.ReactNode { - const { selectedBoard, searchResults, query } = this.state; - // Board names are not unique per core https://github.com/arduino/arduino-pro-ide/issues/262#issuecomment-661019560 - // It is tricky when the core is not yet installed, no FQBNs are available. - const distinctBoards = new Map(); - const toKey = ({ name, packageName, fqbn }: Board.Detailed) => - !!fqbn ? `${name}-${packageName}-${fqbn}` : `${name}-${packageName}`; - for (const board of Board.decorateBoards(selectedBoard, searchResults)) { - const key = toKey(board); - if (!distinctBoards.has(key)) { - distinctBoards.set(key, board); - } - } - - const boardsList = Array.from(distinctBoards.values()).map((board) => ( - - key={toKey(board)} - item={board} - label={board.name} - details={board.details} - selected={board.selected} - onClick={this.selectBoard} - missing={board.missing} - /> - )); - - return ( - -
- - -
- {boardsList.length > 0 ? ( -
{boardsList}
- ) : ( -
- {nls.localize( - 'arduino/board/noBoardsFound', - 'No boards found for "{0}"', - query - )} -
- )} -
- ); - } - - protected renderPorts(): React.ReactNode { - let ports = [] as Port[]; - if (this.state.showAllPorts) { - ports = this.state.knownPorts; - } else { - ports = this.state.knownPorts.filter( - Port.visiblePorts(this.availableBoards) - ); - } - return !ports.length ? ( -
- {nls.localize('arduino/board/noPortsDiscovered', 'No ports discovered')} -
- ) : ( -
- {ports.map((port) => ( - - key={`${Port.keyOf(port)}`} - item={port} - label={Port.toString(port)} - selected={Port.sameAs(this.state.selectedPort, port)} - onClick={this.selectPort} - /> - ))} -
- ); - } - - protected renderPortsFooter(): React.ReactNode { - return ( -
- -
- ); - } -} - -export namespace BoardsConfig { - export namespace Config { - export function sameAs(config: Config, other: Config | Board): boolean { - const { selectedBoard, selectedPort } = config; - if (Board.is(other)) { - return ( - !!selectedBoard && - Board.equals(other, selectedBoard) && - Port.sameAs(selectedPort, other.port) - ); - } - return sameAs(config, other); - } - - export function equals(left: Config, right: Config): boolean { - return ( - left.selectedBoard === right.selectedBoard && - left.selectedPort === right.selectedPort - ); - } - - export function toString( - config: Config, - options: { default: string } = { default: '' } - ): string { - const { selectedBoard, selectedPort: port } = config; - if (!selectedBoard) { - return options.default; - } - const { name } = selectedBoard; - return `${name}${port ? ` at ${port.address}` : ''}`; - } - } -} 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 cdf73d18f..52507a577 100644 --- a/arduino-ide-extension/src/browser/boards/boards-data-store.ts +++ b/arduino-ide-extension/src/browser/boards/boards-data-store.ts @@ -1,63 +1,64 @@ -import { injectable, inject, named } from '@theia/core/shared/inversify'; +import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; +import { LocalStorageService } from '@theia/core/lib/browser/storage-service'; +import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import { Emitter, Event } from '@theia/core/lib/common/event'; import { ILogger } from '@theia/core/lib/common/logger'; import { deepClone } from '@theia/core/lib/common/objects'; -import { Event, Emitter } from '@theia/core/lib/common/event'; -import { - FrontendApplicationContribution, - LocalStorageService, -} from '@theia/core/lib/browser'; -import { notEmpty } from '../../common/utils'; +import { inject, injectable, named } from '@theia/core/shared/inversify'; import { + BoardDetails, BoardsService, ConfigOption, - BoardDetails, Programmer, } from '../../common/protocol'; +import { notEmpty } from '../../common/utils'; import { NotificationCenter } from '../notification-center'; @injectable() export class BoardsDataStore implements FrontendApplicationContribution { @inject(ILogger) @named('store') - protected readonly logger: ILogger; - + private readonly logger: ILogger; @inject(BoardsService) - protected readonly boardsService: BoardsService; - + private readonly boardsService: BoardsService; @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; - + private readonly notificationCenter: NotificationCenter; @inject(LocalStorageService) - protected readonly storageService: LocalStorageService; + private readonly storageService: LocalStorageService; - protected readonly onChangedEmitter = new Emitter(); + private readonly onChangedEmitter = new Emitter(); + private readonly toDispose = new DisposableCollection(this.onChangedEmitter); onStart(): void { - this.notificationCenter.onPlatformDidInstall(async ({ item }) => { - const dataDidChangePerFqbn: string[] = []; - for (const fqbn of item.boards - .map(({ fqbn }) => fqbn) - .filter(notEmpty) - .filter((fqbn) => !!fqbn)) { - const key = this.getStorageKey(fqbn); - let data = await this.storageService.getData< - ConfigOption[] | undefined - >(key); - if (!data || !data.length) { - const details = await this.getBoardDetailsSafe(fqbn); - if (details) { - data = details.configOptions; - if (data.length) { - await this.storageService.setData(key, data); - dataDidChangePerFqbn.push(fqbn); + this.toDispose.push( + this.notificationCenter.onPlatformDidInstall(async ({ item }) => { + const dataDidChangePerFqbn: string[] = []; + for (const fqbn of item.boards + .map(({ fqbn }) => fqbn) + .filter(notEmpty) + .filter((fqbn) => !!fqbn)) { + const key = this.getStorageKey(fqbn); + let data = await this.storageService.getData(key); + if (!data || !data.length) { + const details = await this.getBoardDetailsSafe(fqbn); + if (details) { + data = details.configOptions; + if (data.length) { + await this.storageService.setData(key, data); + dataDidChangePerFqbn.push(fqbn); + } } } } - } - if (dataDidChangePerFqbn.length) { - this.fireChanged(...dataDidChangePerFqbn); - } - }); + if (dataDidChangePerFqbn.length) { + this.fireChanged(...dataDidChangePerFqbn); + } + }) + ); + } + + onStop(): void { + this.toDispose.dispose(); } get onChanged(): Event { @@ -65,7 +66,7 @@ export class BoardsDataStore implements FrontendApplicationContribution { } async appendConfigToFqbn( - fqbn: string | undefined, + fqbn: string | undefined ): Promise { if (!fqbn) { return undefined; @@ -100,12 +101,13 @@ export class BoardsDataStore implements FrontendApplicationContribution { return data; } - async selectProgrammer( - { - fqbn, - selectedProgrammer, - }: { fqbn: string; selectedProgrammer: Programmer }, - ): Promise { + async selectProgrammer({ + fqbn, + selectedProgrammer, + }: { + fqbn: string; + selectedProgrammer: Programmer; + }): Promise { const data = deepClone(await this.getData(fqbn)); const { programmers } = data; if (!programmers.find((p) => Programmer.equals(selectedProgrammer, p))) { @@ -120,13 +122,15 @@ export class BoardsDataStore implements FrontendApplicationContribution { return true; } - async selectConfigOption( - { - fqbn, - option, - selectedValue, - }: { fqbn: string; option: string; selectedValue: string } - ): Promise { + async selectConfigOption({ + fqbn, + option, + selectedValue, + }: { + fqbn: string; + option: string; + selectedValue: string; + }): Promise { const data = deepClone(await this.getData(fqbn)); const { configOptions } = data; const configOption = configOptions.find((c) => c.option === option); diff --git a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts index 279a2e3fa..f1182adbf 100644 --- a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts +++ b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts @@ -1,37 +1,162 @@ -import { injectable, inject } from '@theia/core/shared/inversify'; -import { Emitter } from '@theia/core/lib/common/event'; -import { ILogger } from '@theia/core/lib/common/logger'; +import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { StorageService } from '@theia/core/lib/browser/storage-service'; import { Command, CommandContribution, CommandRegistry, CommandService, } from '@theia/core/lib/common/command'; +import type { Disposable } from '@theia/core/lib/common/disposable'; +import { Emitter } from '@theia/core/lib/common/event'; +import { ILogger } from '@theia/core/lib/common/logger'; import { MessageService } from '@theia/core/lib/common/message-service'; -import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; -import { RecursiveRequired } from '../../common/types'; +import { nls } from '@theia/core/lib/common/nls'; +import { Deferred } from '@theia/core/lib/common/promise-util'; +import type { Mutable } from '@theia/core/lib/common/types'; +import { inject, injectable, optional } from '@theia/core/shared/inversify'; import { - Port, - Board, - BoardsService, + OutputChannel, + OutputChannelManager, +} from '@theia/output/lib/browser/output-channel'; +import { + BoardIdentifier, + boardIdentifierEquals, + BoardsConfig, + BoardsConfigChangeEvent, BoardsPackage, - AttachedBoardsChangeEvent, - BoardWithPackage, + BoardsService, BoardUserField, - AvailablePorts, + BoardWithPackage, + DetectedPorts, + emptyBoardsConfig, + isBoardIdentifier, + isBoardIdentifierChangeEvent, + isPortIdentifier, + isPortIdentifierChangeEvent, + Port, + PortIdentifier, + portIdentifierEquals, + serializePlatformIdentifier, } from '../../common/protocol'; -import { BoardsConfig } from './boards-config'; -import { naturalCompare } from '../../common/utils'; -import { NotificationCenter } from '../notification-center'; -import { StorageWrapper } from '../storage-wrapper'; -import { nls } from '@theia/core/lib/common'; -import { Deferred } from '@theia/core/lib/common/promise-util'; -import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; -import { Unknown } from '../../common/nls'; import { + BoardList, + BoardListHistory, + createBoardList, + EditBoardsConfigActionParams, + isBoardListHistory, + SelectBoardsConfigActionParams, +} from '../../common/protocol/board-list'; +import type { Defined } from '../../common/types'; +import type { StartupTask, StartupTaskProvider, } from '../../electron-common/startup-task'; +import { NotificationCenter } from '../notification-center'; + +const boardListHistoryStorageKey = 'arduino-ide:boardListHistory'; +const selectedPortStorageKey = 'arduino-ide:selectedPort'; +const selectedBoardStorageKey = 'arduino-ide:selectedBoard'; + +type UpdateBoardsConfigReason = + /** + * Restore previous state at IDE startup. + */ + | 'restore' + /** + * The board and the optional port were changed from the dialog. + */ + | 'dialog' + /** + * The board and the port were updated from the board select toolbar. + */ + | 'toolbar' + /** + * The board and port configuration was inherited from another window. + */ + | 'inherit'; + +interface RefreshBoardListParams { + detectedPorts?: DetectedPorts; + boardsConfig?: BoardsConfig; + boardListHistory?: BoardListHistory; +} + +export type UpdateBoardsConfigParams = + | BoardIdentifier + /** + * `'unset-board'` is special case when a non installed board is selected (no FQBN), the platform is installed, + * but there is no way to determine the FQBN from the previous partial data, and IDE2 unsets the board. + */ + | 'unset-board' + | PortIdentifier + | (Readonly> & + Readonly<{ reason?: UpdateBoardsConfigReason }>); + +type HistoryDidNotChange = undefined; +type HistoryDidDelete = Readonly<{ [portKey: string]: undefined }>; +type HistoryDidUpdate = Readonly<{ [portKey: string]: BoardIdentifier }>; +type BoardListHistoryUpdateResult = + | HistoryDidNotChange + | HistoryDidDelete + | HistoryDidUpdate; + +type BoardToSelect = BoardIdentifier | undefined | 'ignore-board'; +type PortToSelect = PortIdentifier | undefined | 'ignore-port'; + +interface UpdateBoardListHistoryParams { + readonly portToSelect: PortToSelect; + readonly boardToSelect: BoardToSelect; +} + +interface UpdateBoardsDataParams { + readonly boardToSelect: BoardToSelect; + readonly reason?: UpdateBoardsConfigReason; +} + +export interface SelectBoardsConfigAction { + (params: SelectBoardsConfigActionParams): void; +} + +export interface EditBoardsConfigAction { + (params?: EditBoardsConfigActionParams): void; +} + +export interface BoardListUIActions { + /** + * Sets the frontend's port and board configuration according to the params. + */ + readonly select: SelectBoardsConfigAction; + /** + * Opens up the boards config dialog with the port and (optional) board to select in the dialog. + * Calling this function does not immediately change the frontend's port and board config, but + * preselects items in the dialog. + */ + readonly edit: EditBoardsConfigAction; +} +export type BoardListUI = BoardList & BoardListUIActions; + +@injectable() +export class BoardListDumper implements Disposable { + @inject(OutputChannelManager) + private readonly outputChannelManager: OutputChannelManager; + + private outputChannel: OutputChannel | undefined; + + dump(boardList: BoardList): void { + if (!this.outputChannel) { + this.outputChannel = this.outputChannelManager.getChannel( + 'Developer (Arduino)' + ); + } + this.outputChannel.show({ preserveFocus: true }); + this.outputChannel.append(boardList.toString() + '\n'); + } + + dispose(): void { + this.outputChannel?.dispose(); + } +} @injectable() export class BoardsServiceProvider @@ -41,402 +166,280 @@ export class BoardsServiceProvider CommandContribution { @inject(ILogger) - protected logger: ILogger; - + private readonly logger: ILogger; @inject(MessageService) - protected messageService: MessageService; - + private readonly messageService: MessageService; @inject(BoardsService) - protected boardsService: BoardsService; - + private readonly boardsService: BoardsService; @inject(CommandService) - protected commandService: CommandService; - + private readonly commandService: CommandService; + @inject(StorageService) + private readonly storageService: StorageService; @inject(NotificationCenter) - protected notificationCenter: NotificationCenter; - + private readonly notificationCenter: NotificationCenter; @inject(FrontendApplicationStateService) private readonly appStateService: FrontendApplicationStateService; + @optional() + @inject(BoardListDumper) + private readonly boardListDumper?: BoardListDumper; - protected readonly onBoardsConfigChangedEmitter = - new Emitter(); - protected readonly onAvailableBoardsChangedEmitter = new Emitter< - AvailableBoard[] - >(); - protected readonly onAvailablePortsChangedEmitter = new Emitter<{ - newState: Port[]; - oldState: Port[]; - }>(); - private readonly inheritedConfig = new Deferred(); + private _boardsConfig = emptyBoardsConfig(); + private _detectedPorts: DetectedPorts = {}; + private _boardList = this.createBoardListUI(createBoardList({})); + private _boardListHistory: Mutable = {}; + private _ready = new Deferred(); - /** - * Used for the auto-reconnecting. Sometimes, the attached board gets disconnected after uploading something to it. - * It happens with certain boards on Windows. For example, the `MKR1000` boards is selected on post `COM5` on Windows, - * perform an upload, the board automatically disconnects and reconnects, but on another port, `COM10`. - * We have to listen on such changes and auto-reconnect the same board on another port. - * See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ - */ - protected latestValidBoardsConfig: - | RecursiveRequired - | undefined = undefined; - protected latestBoardsConfig: BoardsConfig.Config | undefined = undefined; - protected _boardsConfig: BoardsConfig.Config = {}; - protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only. - protected _availablePorts: Port[] = []; - protected _availableBoards: AvailableBoard[] = []; - - private lastBoardsConfigOnUpload: BoardsConfig.Config | undefined; - private lastAvailablePortsOnUpload: Port[] | undefined; - private boardConfigToAutoSelect: BoardsConfig.Config | undefined; + private readonly boardsConfigDidChangeEmitter = + new Emitter(); + readonly onBoardsConfigDidChange = this.boardsConfigDidChangeEmitter.event; + private readonly boardListDidChangeEmitter = new Emitter(); /** - * Unlike `onAttachedBoardsChanged` this event fires when the user modifies the selected board in the IDE.\ - * This event also fires, when the boards package was not available for the currently selected board, - * and the user installs the board package. Note: installing a board package will set the `fqbn` of the - * currently selected board. - * - * This event is also emitted when the board package for the currently selected board was uninstalled. + * Emits an event on board config (port or board) change, and when the discovery (`board list --watch`) detected any changes. */ - readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event; - readonly onAvailableBoardsChanged = - this.onAvailableBoardsChangedEmitter.event; - readonly onAvailablePortsChanged = this.onAvailablePortsChangedEmitter.event; - - private readonly _reconciled = new Deferred(); + readonly onBoardListDidChange = this.boardListDidChangeEmitter.event; onStart(): void { - this.notificationCenter.onAttachedBoardsDidChange( - this.notifyAttachedBoardsChanged.bind(this) - ); - this.notificationCenter.onPlatformDidInstall( - this.notifyPlatformInstalled.bind(this) + this.notificationCenter.onDetectedPortsDidChange(({ detectedPorts }) => + this.refreshBoardList({ detectedPorts }) ); - this.notificationCenter.onPlatformDidUninstall( - this.notifyPlatformUninstalled.bind(this) + this.notificationCenter.onPlatformDidInstall((event) => + this.maybeUpdateSelectedBoard(event) ); + this.appStateService + .reachedState('ready') + .then(async () => { + const [detectedPorts, storedState] = await Promise.all([ + this.boardsService.getDetectedPorts(), + this.restoreState(), + ]); + const { selectedBoard, selectedPort, boardListHistory } = storedState; + const options: RefreshBoardListParams = { + boardListHistory, + detectedPorts, + }; + // If either the port or the board is set, restore it. Otherwise, do not restore nothing. + // It might override the inherited boards config from the other window on File > New Sketch + if (selectedBoard || selectedPort) { + options.boardsConfig = { selectedBoard, selectedPort }; + } + this.refreshBoardList(options); + this._ready.resolve(); + }) + .finally(() => this._ready.resolve()); + } - this.appStateService.reachedState('ready').then(async () => { - const [state] = await Promise.all([ - this.boardsService.getState(), - this.loadState(), - ]); - const { boards: attachedBoards, ports: availablePorts } = - AvailablePorts.split(state); - this._attachedBoards = attachedBoards; - const oldState = this._availablePorts.slice(); - this._availablePorts = availablePorts; - this.onAvailablePortsChangedEmitter.fire({ - newState: this._availablePorts.slice(), - oldState, - }); - - await this.reconcileAvailableBoards(); - - this.tryReconnect(); - this._reconciled.resolve(); - }); + onStop(): void { + this.boardListDumper?.dispose(); } registerCommands(registry: CommandRegistry): void { registry.registerCommand(USE_INHERITED_CONFIG, { - execute: (inheritedConfig: BoardsConfig.Config) => - this.inheritedConfig.resolve(inheritedConfig), + execute: ( + boardsConfig: BoardsConfig, + boardListHistory: BoardListHistory + ) => { + if (boardListHistory) { + this._boardListHistory = boardListHistory; + } + this.update({ boardsConfig }, 'inherit'); + }, + }); + if (this.boardListDumper) { + registry.registerCommand(DUMP_BOARD_LIST, { + execute: () => this.boardListDumper?.dump(this._boardList), + }); + } + registry.registerCommand(CLEAR_BOARD_LIST_HISTORY, { + execute: () => { + this.refreshBoardList({ boardListHistory: {} }); + this.setData(boardListHistoryStorageKey, undefined); + }, + }); + registry.registerCommand(CLEAR_BOARDS_CONFIG, { + execute: () => { + this.refreshBoardList({ boardsConfig: emptyBoardsConfig() }); + Promise.all([ + this.setData(selectedPortStorageKey, undefined), + this.setData(selectedBoardStorageKey, undefined), + ]); + }, }); } - get reconciled(): Promise { - return this._reconciled.promise; + tasks(): StartupTask[] { + return [ + { + command: USE_INHERITED_CONFIG.id, + args: [this._boardsConfig, this._boardListHistory], + }, + ]; } - snapshotBoardDiscoveryOnUpload(): void { - this.lastBoardsConfigOnUpload = this._boardsConfig; - this.lastAvailablePortsOnUpload = this._availablePorts; + private refreshBoardList(params?: RefreshBoardListParams): void { + if (params?.detectedPorts) { + this._detectedPorts = params.detectedPorts; + } + if (params?.boardsConfig) { + this._boardsConfig = params.boardsConfig; + } + if (params?.boardListHistory) { + this._boardListHistory = params.boardListHistory; + } + const boardList = createBoardList( + this._detectedPorts, + this._boardsConfig, + this._boardListHistory + ); + this._boardList = this.createBoardListUI(boardList); + this.boardListDidChangeEmitter.fire(this._boardList); } - clearBoardDiscoverySnapshot(): void { - this.lastBoardsConfigOnUpload = undefined; - this.lastAvailablePortsOnUpload = undefined; + private createBoardListUI(boardList: BoardList): BoardListUI { + return Object.assign(boardList, { + select: this.onBoardsConfigSelect.bind(this), + edit: this.onBoardsConfigEdit.bind(this), + }); } - attemptPostUploadAutoSelect(): void { - setTimeout(() => { - if (this.lastBoardsConfigOnUpload && this.lastAvailablePortsOnUpload) { - this.attemptAutoSelect({ - ports: this._availablePorts, - boards: this._availableBoards, - }); - } - }, 2000); // 2 second delay same as IDE 1.8 + private onBoardsConfigSelect(params: SelectBoardsConfigActionParams): void { + this.updateConfig({ ...params, reason: 'toolbar' }); } - private attemptAutoSelect( - newState: AttachedBoardsChangeEvent['newState'] - ): void { - this.deriveBoardConfigToAutoSelect(newState); - this.tryReconnect(); + private async onBoardsConfigEdit( + params?: EditBoardsConfigActionParams + ): Promise { + const boardsConfig = await this.commandService.executeCommand< + BoardsConfig | undefined + >('arduino-open-boards-dialog', params); + if (boardsConfig) { + this.update({ boardsConfig }, 'dialog'); + } } - private deriveBoardConfigToAutoSelect( - newState: AttachedBoardsChangeEvent['newState'] + private update( + params: RefreshBoardListParams, + reason?: UpdateBoardsConfigReason ): void { - if (!this.lastBoardsConfigOnUpload || !this.lastAvailablePortsOnUpload) { - this.boardConfigToAutoSelect = undefined; + const { boardsConfig } = params; + if (!boardsConfig) { return; } + const { selectedBoard, selectedPort } = boardsConfig; + if (selectedBoard && selectedPort) { + this.updateConfig({ + selectedBoard, + selectedPort, + reason, + }); + } else if (selectedBoard) { + this.updateConfig(selectedBoard); + } else if (selectedPort) { + this.updateConfig(selectedPort); + } + } - const oldPorts = this.lastAvailablePortsOnUpload; - const { ports: newPorts, boards: newBoards } = newState; - - const appearedPorts = - oldPorts.length > 0 - ? newPorts.filter((newPort: Port) => - oldPorts.every((oldPort: Port) => !Port.sameAs(newPort, oldPort)) - ) - : newPorts; - - for (const port of appearedPorts) { - const boardOnAppearedPort = newBoards.find((board: Board) => - Port.sameAs(board.port, port) - ); - - const lastBoardsConfigOnUpload = this.lastBoardsConfigOnUpload; - - if (boardOnAppearedPort && lastBoardsConfigOnUpload.selectedBoard) { - const boardIsSameHardware = Board.hardwareIdEquals( - boardOnAppearedPort, - lastBoardsConfigOnUpload.selectedBoard - ); - - const boardIsSameFqbn = Board.sameAs( - boardOnAppearedPort, - lastBoardsConfigOnUpload.selectedBoard - ); - - if (!boardIsSameHardware && !boardIsSameFqbn) continue; - - let boardToAutoSelect = boardOnAppearedPort; - if (boardIsSameHardware && !boardIsSameFqbn) { - const { name, fqbn } = lastBoardsConfigOnUpload.selectedBoard; - - boardToAutoSelect = { - ...boardToAutoSelect, - name: - boardToAutoSelect.name === Unknown || !boardToAutoSelect.name - ? name - : boardToAutoSelect.name, - fqbn: boardToAutoSelect.fqbn || fqbn, - }; - } - - this.clearBoardDiscoverySnapshot(); - - this.boardConfigToAutoSelect = { - selectedBoard: boardToAutoSelect, - selectedPort: port, + updateConfig(params: UpdateBoardsConfigParams): boolean { + const previousSelectedBoard = this._boardsConfig.selectedBoard; + const previousSelectedPort = this._boardsConfig.selectedPort; + const boardToSelect = this.getBoardToSelect(params); + const portToSelect = this.getPortToSelect(params); + const reason = this.getUpdateReason(params); + + const boardDidChange = + boardToSelect !== 'ignore-board' && + !boardIdentifierEquals(boardToSelect, previousSelectedBoard); + const portDidChange = + portToSelect !== 'ignore-port' && + !portIdentifierEquals(portToSelect, previousSelectedPort); + const boardDidChangeEvent = boardDidChange + ? { selectedBoard: boardToSelect, previousSelectedBoard } + : undefined; + const portDidChangeEvent = portDidChange + ? { selectedPort: portToSelect, previousSelectedPort } + : undefined; + + let event: BoardsConfigChangeEvent | undefined = boardDidChangeEvent; + if (portDidChangeEvent) { + if (event) { + event = { + ...event, + ...portDidChangeEvent, }; - return; + } else { + event = portDidChangeEvent; } } - } + if (!event) { + return false; + } - protected notifyAttachedBoardsChanged( - event: AttachedBoardsChangeEvent - ): void { - if (!AttachedBoardsChangeEvent.isEmpty(event)) { - this.logger.info('Attached boards and available ports changed:'); - this.logger.info(AttachedBoardsChangeEvent.toString(event)); - this.logger.info('------------------------------------------'); + this.maybeUpdateBoardListHistory({ portToSelect, boardToSelect }); + this.maybeUpdateBoardsData({ boardToSelect, reason }); + + if (isBoardIdentifierChangeEvent(event)) { + this._boardsConfig.selectedBoard = event.selectedBoard; + } + if (isPortIdentifierChangeEvent(event)) { + this._boardsConfig.selectedPort = event.selectedPort; } - this._attachedBoards = event.newState.boards; - const oldState = this._availablePorts.slice(); - this._availablePorts = event.newState.ports; - this.onAvailablePortsChangedEmitter.fire({ - newState: this._availablePorts.slice(), - oldState, - }); - this.reconcileAvailableBoards().then(() => { - const { uploadInProgress } = event; - // avoid attempting "auto-selection" while an - // upload is in progress - if (!uploadInProgress) { - this.attemptAutoSelect(event.newState); - } - }); + this.boardsConfigDidChangeEmitter.fire(event); + this.refreshBoardList(); + this.saveState(); + return true; } - protected notifyPlatformInstalled(event: { item: BoardsPackage }): void { - this.logger.info('Boards package installed: ', JSON.stringify(event)); - const { selectedBoard } = this.boardsConfig; - const { installedVersion, id } = event.item; - if (selectedBoard) { - const installedBoard = event.item.boards.find( - ({ name }) => name === selectedBoard.name - ); - if ( - installedBoard && - (!selectedBoard.fqbn || selectedBoard.fqbn === installedBoard.fqbn) - ) { - this.logger.info( - `Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]` - ); - this.boardsConfig = { - ...this.boardsConfig, - selectedBoard: installedBoard, - }; - return; - } - // The board name can change after install. - // This logic handles it "gracefully" by unselecting the board, so that we can avoid no FQBN is set error. - // https://github.com/arduino/arduino-cli/issues/620 - // https://github.com/arduino/arduino-pro-ide/issues/374 - if ( - BoardWithPackage.is(selectedBoard) && - selectedBoard.packageId === event.item.id && - !installedBoard - ) { - const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes'); - this.messageService - .warn( - nls.localize( - 'arduino/board/couldNotFindPreviouslySelected', - "Could not find previously selected board '{0}' in installed platform '{1}'. Please manually reselect the board you want to use. Do you want to reselect it now?", - selectedBoard.name, - event.item.name - ), - nls.localize('arduino/board/reselectLater', 'Reselect later'), - yes - ) - .then(async (answer) => { - if (answer === yes) { - this.commandService.executeCommand( - 'arduino-open-boards-dialog', - selectedBoard.name - ); - } - }); - this.boardsConfig = {}; - return; - } - // Trigger a board re-set. See: https://github.com/arduino/arduino-cli/issues/954 - // E.g: install `adafruit:avr`, then select `adafruit:avr:adafruit32u4` board, and finally install the required `arduino:avr` - this.boardsConfig = this.boardsConfig; + private getBoardToSelect(params: UpdateBoardsConfigParams): BoardToSelect { + if (isPortIdentifier(params)) { + return 'ignore-board'; } + if (params === 'unset-board') { + return undefined; + } + return isBoardIdentifier(params) ? params : params.selectedBoard; } - protected notifyPlatformUninstalled(event: { item: BoardsPackage }): void { - this.logger.info('Boards package uninstalled: ', JSON.stringify(event)); - const { selectedBoard } = this.boardsConfig; - if (selectedBoard && selectedBoard.fqbn) { - const uninstalledBoard = event.item.boards.find( - ({ name }) => name === selectedBoard.name - ); - if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) { - // We should not unset the FQBN, if the selected board is an attached, recognized board. - // Attach Uno and install AVR, select Uno. Uninstall the AVR core while Uno is selected. We do not want to discard the FQBN of the Uno board. - // Dev note: We cannot assume the `selectedBoard` is a type of `AvailableBoard`. - // When the user selects an `AvailableBoard` it works, but between app start/stops, - // it is just a FQBN, so we need to find the `selected` board among the `AvailableBoards` - const selectedAvailableBoard = AvailableBoard.is(selectedBoard) - ? selectedBoard - : this._availableBoards.find( - (availableBoard) => - Board.hardwareIdEquals(availableBoard, selectedBoard) || - Board.sameAs(availableBoard, selectedBoard) - ); - if ( - selectedAvailableBoard && - selectedAvailableBoard.selected && - selectedAvailableBoard.state === AvailableBoard.State.recognized - ) { - return; - } - this.logger.info( - `Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.` - ); - const selectedBoardWithoutFqbn = { - name: selectedBoard.name, - // No FQBN - }; - this.boardsConfig = { - ...this.boardsConfig, - selectedBoard: selectedBoardWithoutFqbn, - }; - } + private getPortToSelect( + params: UpdateBoardsConfigParams + ): Exclude { + if (isBoardIdentifier(params) || params === 'unset-board') { + return 'ignore-port'; } + return isPortIdentifier(params) ? params : params.selectedPort; } - protected tryReconnect(): boolean { - if (this.latestValidBoardsConfig && !this.canUploadTo(this.boardsConfig)) { - // ** Reconnect to a board unplugged from, and plugged back into the same port - for (const board of this.availableBoards.filter( - ({ state }) => state !== AvailableBoard.State.incomplete - )) { - if ( - Board.hardwareIdEquals( - this.latestValidBoardsConfig.selectedBoard, - board - ) - ) { - const { name, fqbn } = this.latestValidBoardsConfig.selectedBoard; - this.boardsConfig = { - selectedBoard: { - name: board.name === Unknown || !board.name ? name : board.name, - fqbn: board.fqbn || fqbn, - port: board.port, - }, - selectedPort: board.port, - }; - return true; - } - - if ( - this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn && - this.latestValidBoardsConfig.selectedBoard.name === board.name && - Port.sameAs(this.latestValidBoardsConfig.selectedPort, board.port) - ) { - this.boardsConfig = this.latestValidBoardsConfig; - return true; - } - } - // ** - - // ** Reconnect to a board whose port changed due to an upload - if (!this.boardConfigToAutoSelect) return false; - - this.boardsConfig = this.boardConfigToAutoSelect; - this.boardConfigToAutoSelect = undefined; - return true; - // ** + private getUpdateReason( + params: UpdateBoardsConfigParams + ): UpdateBoardsConfigReason | undefined { + if ( + isBoardIdentifier(params) || + isPortIdentifier(params) || + params === 'unset-board' + ) { + return undefined; } - return false; + return params.reason; } - set boardsConfig(config: BoardsConfig.Config) { - this.setBoardsConfig(config); - this.saveState().finally(() => - this.reconcileAvailableBoards().finally(() => - this.onBoardsConfigChangedEmitter.fire(this._boardsConfig) - ) - ); + get ready(): Promise { + return this._ready.promise; } - get boardsConfig(): BoardsConfig.Config { + get boardsConfig(): BoardsConfig { return this._boardsConfig; } - protected setBoardsConfig(config: BoardsConfig.Config): void { - this.logger.debug('Board config changed: ', JSON.stringify(config)); - this._boardsConfig = config; - this.latestBoardsConfig = this._boardsConfig; - if (this.canUploadTo(this._boardsConfig)) { - this.latestValidBoardsConfig = this._boardsConfig; - } + get boardList(): BoardListUI { + return this._boardList; + } + + get detectedPorts(): DetectedPorts { + return this._detectedPorts; } async searchBoards({ query, - cores, }: { query?: string; cores?: string[]; @@ -459,317 +462,162 @@ export class BoardsServiceProvider return await this.boardsService.getBoardUserFields({ fqbn, protocol }); } - /** - * `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`. - */ - canVerify( - config: BoardsConfig.Config | undefined = this.boardsConfig, - options: { silent: boolean } = { silent: true } - ): config is BoardsConfig.Config & { selectedBoard: Board } { - if (!config) { - return false; - } - - if (!config.selectedBoard) { - if (!options.silent) { - this.messageService.warn( - nls.localize('arduino/board/noneSelected', 'No boards selected.'), - { - timeout: 3000, - } - ); - } - return false; - } - - return true; - } - - /** - * `true` if `canVerify`, the board has an FQBN and the `config.selectedPort` is also set, hence can upload to board. Otherwise, `false`. - */ - canUploadTo( - config: BoardsConfig.Config | undefined = this.boardsConfig, - options: { silent: boolean } = { silent: true } - ): config is RecursiveRequired { - if (!this.canVerify(config, options)) { - return false; - } - - const { name } = config.selectedBoard; - if (!config.selectedPort) { - if (!options.silent) { - this.messageService.warn( - nls.localize( - 'arduino/board/noPortsSelected', - "No ports selected for board: '{0}'.", - name - ), - { - timeout: 3000, - } + private async maybeUpdateSelectedBoard(platformDidInstallEvent: { + item: BoardsPackage; + }): Promise { + const { selectedBoard } = this._boardsConfig; + if ( + selectedBoard && + !selectedBoard.fqbn && + BoardWithPackage.is(selectedBoard) + ) { + const selectedBoardPlatformId = serializePlatformIdentifier( + selectedBoard.packageId + ); + if (selectedBoardPlatformId === platformDidInstallEvent.item.id) { + const installedSelectedBoard = platformDidInstallEvent.item.boards.find( + (board) => board.name === selectedBoard.name ); - } - return false; - } - - if (!config.selectedBoard.fqbn) { - if (!options.silent) { - this.messageService.warn( - nls.localize( - 'arduino/board/noFQBN', - 'The FQBN is not available for the selected board "{0}". Do you have the corresponding core installed?', - name - ), - { timeout: 3000 } + // if the board can be found by its name after the install event select it. otherwise unselect it + // historical hint: https://github.com/arduino/arduino-ide/blob/144df893d0dafec64a26565cf912a98f32572da9/arduino-ide-extension/src/browser/boards/boards-service-provider.ts#L289-L320 + this.updateConfig( + installedSelectedBoard ? installedSelectedBoard : 'unset-board' ); + if (!installedSelectedBoard) { + const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes'); + const answer = await this.messageService.warn( + nls.localize( + 'arduino/board/couldNotFindPreviouslySelected', + "Could not find previously selected board '{0}' in installed platform '{1}'. Please manually reselect the board you want to use. Do you want to reselect it now?", + selectedBoard.name, + platformDidInstallEvent.item.name + ), + nls.localize('arduino/board/reselectLater', 'Reselect later'), + yes + ); + if (answer === yes) { + this.onBoardsConfigEdit({ + query: selectedBoard.name, + portToSelect: this._boardsConfig.selectedPort, + }); + } + } } - return false; } - - return true; - } - - get availableBoards(): AvailableBoard[] { - return this._availableBoards; } - /** - * @deprecated Do not use this API, it will be removed. This is a hack to be able to set the missing port `properties` before an upload. - * - * See: https://github.com/arduino/arduino-ide/pull/1335#issuecomment-1224355236. - */ - // TODO: remove this API and fix the selected board config store/restore correctly. - get availablePorts(): Port[] { - return this._availablePorts.slice(); - } - - async waitUntilAvailable( - what: Board & { port: Port }, - timeout?: number - ): Promise { - const find = (needle: Board & { port: Port }, haystack: AvailableBoard[]) => - haystack.find( - (board) => - Board.equals(needle, board) && Port.sameAs(needle.port, board.port) - ); - const timeoutTask = - !!timeout && timeout > 0 - ? new Promise((_, reject) => - setTimeout( - () => reject(new Error(`Timeout after ${timeout} ms.`)), - timeout - ) - ) - : new Promise(() => { - /* never */ - }); - const waitUntilTask = new Promise((resolve) => { - let candidate = find(what, this.availableBoards); - if (candidate) { - resolve(); - return; - } - const disposable = this.onAvailableBoardsChanged((availableBoards) => { - candidate = find(what, availableBoards); - if (candidate) { - disposable.dispose(); - resolve(); - } - }); - }); - return await Promise.race([waitUntilTask, timeoutTask]); - } - - protected async reconcileAvailableBoards(): Promise { - const availablePorts = this._availablePorts; - // Unset the port on the user's config, if it is not available anymore. - if ( - this.boardsConfig.selectedPort && - !availablePorts.some((port) => - Port.sameAs(port, this.boardsConfig.selectedPort) - ) - ) { - this.setBoardsConfig({ - selectedBoard: this.boardsConfig.selectedBoard, - selectedPort: undefined, - }); - this.onBoardsConfigChangedEmitter.fire(this._boardsConfig); - } - const boardsConfig = this.boardsConfig; - const currentAvailableBoards = this._availableBoards; - const availableBoards: AvailableBoard[] = []; - const attachedBoards = this._attachedBoards.filter(({ port }) => !!port); - const availableBoardPorts = availablePorts.filter( - Port.visiblePorts(attachedBoards) - ); - - for (const boardPort of availableBoardPorts) { - const board = attachedBoards.find(({ port }) => - Port.sameAs(boardPort, port) - ); - // "board" will always be falsey for - // port that was originally mapped - // to unknown board and then selected - // manually by user - - const lastSelectedBoard = await this.getLastSelectedBoardOnPort( - boardPort + private maybeUpdateBoardListHistory( + params: UpdateBoardListHistoryParams + ): BoardListHistoryUpdateResult { + const { portToSelect, boardToSelect } = params; + const selectedPort = isPortIdentifier(portToSelect) + ? portToSelect + : portToSelect === 'ignore-port' + ? this._boardsConfig.selectedPort + : undefined; + const selectedBoard = isBoardIdentifier(boardToSelect) + ? boardToSelect + : boardToSelect === 'ignore-board' + ? this._boardsConfig.selectedBoard + : undefined; + if (selectedBoard && selectedPort) { + const match = this.boardList.items.find( + (item) => + portIdentifierEquals(item.port, selectedPort) && + item.board && + boardIdentifierEquals(item.board, selectedBoard) ); - - let availableBoard = {} as AvailableBoard; - if (board) { - availableBoard = { - ...board, - state: AvailableBoard.State.recognized, - selected: BoardsConfig.Config.sameAs(boardsConfig, board), - port: boardPort, - }; - } else if (lastSelectedBoard) { - // If the selected board is not recognized because it is a 3rd party board: https://github.com/arduino/arduino-cli/issues/623 - // We still want to show it without the red X in the boards toolbar: https://github.com/arduino/arduino-pro-ide/issues/198#issuecomment-599355836 - availableBoard = { - ...lastSelectedBoard, - state: AvailableBoard.State.guessed, - selected: - BoardsConfig.Config.sameAs(boardsConfig, lastSelectedBoard) && - Port.sameAs(boardPort, boardsConfig.selectedPort), // to avoid double selection - port: boardPort, - }; + const portKey = Port.keyOf(selectedPort); + if (match) { + // When board `B` is detected on port `P` and saving `B` on `P`, remove the entry instead! + delete this._boardListHistory[portKey]; } else { - availableBoard = { - name: Unknown, - port: boardPort, - state: AvailableBoard.State.incomplete, - }; + this._boardListHistory[portKey] = selectedBoard; } - availableBoards.push(availableBoard); + if (match) { + return { [portKey]: undefined }; + } + return { [portKey]: selectedBoard }; } + return undefined; + } + private maybeUpdateBoardsData(params: UpdateBoardsDataParams): void { + const { boardToSelect, reason } = params; if ( - boardsConfig.selectedBoard && - availableBoards.every(({ selected }) => !selected) + boardToSelect && + boardToSelect !== 'ignore-board' && + boardToSelect.fqbn && + (reason === 'toolbar' || reason === 'inherit') ) { - let port = boardsConfig.selectedPort; - // If the selected board has the same port of an unknown board - // that is already in availableBoards we might get a duplicate port. - // So we remove the one already in the array and add the selected one. - const found = availableBoards.findIndex( - (board) => board.port?.address === boardsConfig.selectedPort?.address - ); - if (found >= 0) { - // get the "Unknown board port" that we will substitute, - // then we can include it in the "availableBoard object" - // pushed below; to ensure addressLabel is included - port = availableBoards[found].port; - availableBoards.splice(found, 1); + const [, , , ...rest] = boardToSelect.fqbn.split(':'); + if (rest.length) { + // https://github.com/arduino/arduino-ide/pull/2113 + // TODO: save update data store if reason is toolbar and the FQBN has options } - availableBoards.push({ - ...boardsConfig.selectedBoard, - port, - selected: true, - state: AvailableBoard.State.incomplete, - }); - } - - availableBoards.sort(AvailableBoard.compare); - - let hasChanged = availableBoards.length !== currentAvailableBoards.length; - for (let i = 0; !hasChanged && i < availableBoards.length; i++) { - const [left, right] = [availableBoards[i], currentAvailableBoards[i]]; - hasChanged = - left.fqbn !== right.fqbn || - !!AvailableBoard.compare(left, right) || - left.selected !== right.selected; - } - if (hasChanged) { - this._availableBoards = availableBoards; - this.onAvailableBoardsChangedEmitter.fire(this._availableBoards); } } - protected async getLastSelectedBoardOnPort( - port: Port - ): Promise { - const key = this.getLastSelectedBoardOnPortKey(port); - return this.getData(key); - } - - protected async saveState(): Promise { - // We save the port with the selected board name/FQBN, to be able to guess a better board name. - // Required when the attached board belongs to a 3rd party boards package, and neither the name, nor - // the FQBN can be retrieved with a `board list` command. - // https://github.com/arduino/arduino-cli/issues/623 + private async saveState(): Promise { const { selectedBoard, selectedPort } = this.boardsConfig; - if (selectedBoard && selectedPort) { - const key = this.getLastSelectedBoardOnPortKey(selectedPort); - await this.setData(key, selectedBoard); - } await Promise.all([ - this.setData('latest-valid-boards-config', this.latestValidBoardsConfig), - this.setData('latest-boards-config', this.latestBoardsConfig), + this.setData( + selectedBoardStorageKey, + selectedBoard ? JSON.stringify(selectedBoard) : undefined + ), + this.setData( + selectedPortStorageKey, + selectedPort ? JSON.stringify(selectedPort) : undefined + ), + this.setData( + boardListHistoryStorageKey, + JSON.stringify(this._boardListHistory) + ), ]); } - protected getLastSelectedBoardOnPortKey(port: Port | string): string { - // TODO: we lose the port's `protocol` info (`serial`, `network`, etc.) here if the `port` is a `string`. - return `last-selected-board-on-port:${ - typeof port === 'string' ? port : port.address - }`; + private async restoreState(): Promise< + Readonly & { boardListHistory: BoardListHistory | undefined } + > { + const [maybeSelectedBoard, maybeSelectedPort, maybeBoardHistory] = + await Promise.all([ + this.getData(selectedBoardStorageKey), + this.getData(selectedPortStorageKey), + this.getData(boardListHistoryStorageKey), + ]); + const selectedBoard = this.tryParse(maybeSelectedBoard, isBoardIdentifier); + const selectedPort = this.tryParse(maybeSelectedPort, isPortIdentifier); + const boardListHistory = this.tryParse( + maybeBoardHistory, + isBoardListHistory + ); + return { selectedBoard, selectedPort, boardListHistory }; } - protected async loadState(): Promise { - const storedLatestValidBoardsConfig = await this.getData< - RecursiveRequired - >('latest-valid-boards-config'); - if (storedLatestValidBoardsConfig) { - this.latestValidBoardsConfig = storedLatestValidBoardsConfig; - if (this.canUploadTo(this.latestValidBoardsConfig)) { - this.boardsConfig = this.latestValidBoardsConfig; - } - } else { - // If we could not restore the latest valid config, try to restore something, the board at least. - let storedLatestBoardsConfig = await this.getData< - BoardsConfig.Config | undefined - >('latest-boards-config'); - // Try to get from the startup task. Wait for it, then timeout. Maybe it never arrives. - if (!storedLatestBoardsConfig) { - storedLatestBoardsConfig = await Promise.race([ - this.inheritedConfig.promise, - new Promise((resolve) => - setTimeout(() => resolve(undefined), 2_000) - ), - ]); - } - if (storedLatestBoardsConfig) { - this.latestBoardsConfig = storedLatestBoardsConfig; - this.boardsConfig = this.latestBoardsConfig; + private tryParse( + raw: string | undefined, + typeGuard: (object: unknown) => object is T + ): T | undefined { + if (!raw) { + return undefined; + } + try { + const object = JSON.parse(raw); + if (typeGuard(object)) { + return object; } + } catch { + this.logger.error(`Failed to parse raw: '${raw}'`); } + return undefined; } private setData(key: string, value: T): Promise { - return this.commandService.executeCommand( - StorageWrapper.Commands.SET_DATA.id, - key, - value - ); + return this.storageService.setData(key, value); } private getData(key: string): Promise { - return this.commandService.executeCommand( - StorageWrapper.Commands.GET_DATA.id, - key - ); - } - - tasks(): StartupTask[] { - return [ - { - command: USE_INHERITED_CONFIG.id, - args: [this.boardsConfig], - }, - ]; + return this.storageService.getData(key); } } @@ -787,77 +635,26 @@ const USE_INHERITED_CONFIG: Command = { id: 'arduino-use-inherited-boards-config', }; -/** - * Representation of a ready-to-use board, either the user has configured it or was automatically recognized by the CLI. - * An available board was not necessarily recognized by the CLI (e.g.: it is a 3rd party board) or correctly configured but ready for `verify`. - * If it has the selected board and a associated port, it can be used for `upload`. We render an available board for the user - * when it has the `port` set. - */ -export interface AvailableBoard extends Board { - readonly state: AvailableBoard.State; - readonly selected?: boolean; - readonly port?: Port; -} - -export namespace AvailableBoard { - export enum State { - /** - * Retrieved from the CLI via the `board list` command. - */ - 'recognized', - /** - * Guessed the name/FQBN of the board from the available board ports (3rd party). - */ - 'guessed', - /** - * We do not know anything about this board, probably a 3rd party. The user has not selected a board for this port yet. - */ - 'incomplete', - } - - export function is(board: any): board is AvailableBoard { - return Board.is(board) && 'state' in board; - } +const DUMP_BOARD_LIST: Command = { + id: 'arduino-dump-board-list', + label: nls.localize('arduino/developer/dumpBoardList', 'Dump the Board List'), + category: 'Developer (Arduino)', +}; - export function hasPort( - board: AvailableBoard - ): board is AvailableBoard & { port: Port } { - return !!board.port; - } +const CLEAR_BOARD_LIST_HISTORY: Command = { + id: 'arduino-clear-board-list-history', + label: nls.localize( + 'arduino/developer/clearBoardList', + 'Clear the Board List History' + ), + category: 'Developer (Arduino)', +}; - // Available boards must be sorted in this order: - // 1. Serial with recognized boards - // 2. Serial with guessed boards - // 3. Serial with incomplete boards - // 4. Network with recognized boards - // 5. Other protocols with recognized boards - export const compare = (left: AvailableBoard, right: AvailableBoard) => { - if (left.port?.protocol === 'serial' && right.port?.protocol !== 'serial') { - return -1; - } else if ( - left.port?.protocol !== 'serial' && - right.port?.protocol === 'serial' - ) { - return 1; - } else if ( - left.port?.protocol === 'network' && - right.port?.protocol !== 'network' - ) { - return -1; - } else if ( - left.port?.protocol !== 'network' && - right.port?.protocol === 'network' - ) { - return 1; - } else if (left.port?.protocol === right.port?.protocol) { - // We show all ports, including those that have guessed - // or unrecognized boards, so we must sort those too. - if (left.state < right.state) { - return -1; - } else if (left.state > right.state) { - return 1; - } - } - return naturalCompare(left.port?.address!, right.port?.address!); - }; -} +const CLEAR_BOARDS_CONFIG: Command = { + id: 'arduino-clear-boards-config', + label: nls.localize( + 'arduino/developer/clearBoardsConfig', + 'Clear the Board and Port Selection' + ), + category: 'Developer (Arduino)', +}; diff --git a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx index 25d66e361..e2311fb1d 100644 --- a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx @@ -1,16 +1,21 @@ -import React from '@theia/core/shared/react'; -import * as ReactDOM from '@theia/core/shared/react-dom'; +import { TabBarToolbar } from '@theia/core/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar'; +import { codicon } from '@theia/core/lib/browser/widgets/widget'; import { CommandRegistry } from '@theia/core/lib/common/command'; -import { DisposableCollection } from '@theia/core/lib/common/disposable'; -import { Port } from '../../common/protocol'; -import { OpenBoardsConfig } from '../contributions/open-boards-config'; import { + Disposable, + DisposableCollection, +} from '@theia/core/lib/common/disposable'; +import { nls } from '@theia/core/lib/common/nls'; +import React from '@theia/core/shared/react'; +import ReactDOM from '@theia/core/shared/react-dom'; +import classNames from 'classnames'; +import { boardIdentifierLabel, Port } from '../../common/protocol'; +import { BoardListItemUI } from '../../common/protocol/board-list'; +import { assertUnreachable } from '../../common/utils'; +import type { + BoardListUI, BoardsServiceProvider, - AvailableBoard, } from './boards-service-provider'; -import { nls } from '@theia/core/lib/common'; -import classNames from 'classnames'; -import { BoardsConfig } from './boards-config'; export interface BoardsDropDownListCoords { readonly top: number; @@ -22,18 +27,18 @@ export interface BoardsDropDownListCoords { export namespace BoardsDropDown { export interface Props { readonly coords: BoardsDropDownListCoords | 'hidden'; - readonly items: Array void; port: Port }>; + readonly boardList: BoardListUI; readonly openBoardsConfig: () => void; + readonly hide: () => void; } } -export class BoardsDropDown extends React.Component { - protected dropdownElement: HTMLElement; +export class BoardListDropDown extends React.Component { + private dropdownElement: HTMLElement; private listRef: React.RefObject; constructor(props: BoardsDropDown.Props) { super(props); - this.listRef = React.createRef(); let list = document.getElementById('boards-dropdown-container'); if (!list) { @@ -51,11 +56,14 @@ export class BoardsDropDown extends React.Component { } override render(): React.ReactNode { - return ReactDOM.createPortal(this.renderNode(), this.dropdownElement); + return ReactDOM.createPortal( + this.renderBoardListItems(), + this.dropdownElement + ); } - protected renderNode(): React.ReactNode { - const { coords, items } = this.props; + private renderBoardListItems(): React.ReactNode { + const { coords, boardList } = this.props; if (coords === 'hidden') { return ''; } @@ -74,14 +82,12 @@ export class BoardsDropDown extends React.Component { tabIndex={0} >
- {items - .map(({ name, port, selected, onClick }) => ({ - boardLabel: name, - port, - selected, - onClick, - })) - .map(this.renderItem)} + {boardList.items.map((item, index) => + this.renderBoardListItem({ + item, + selected: index === boardList.selectedIndex, + }) + )}
{ ); } - protected renderItem({ - boardLabel, - port, + private readonly onDefaultAction = (item: BoardListItemUI): unknown => { + const { boardList, hide } = this.props; + const { type, params } = item.defaultAction; + hide(); + switch (type) { + case 'select-boards-config': { + return boardList.select(params); + } + case 'edit-boards-config': { + return boardList.edit(params); + } + default: + return assertUnreachable(type); + } + }; + + private renderBoardListItem({ + item, selected, - onClick, }: { - boardLabel: string; - port: Port; - selected?: boolean; - onClick: () => void; + item: BoardListItemUI; + selected: boolean; }): React.ReactNode { - const protocolIcon = iconNameFromProtocol(port.protocol); + const { boardLabel, portLabel, portProtocol, tooltip } = item.labels; + const port = item.port; const onKeyUp = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { - onClick(); + this.onDefaultAction(item); } }; - return (
this.onDefaultAction(item)} onKeyUp={onKeyUp} tabIndex={0} > @@ -127,21 +145,81 @@ export class BoardsDropDown extends React.Component { className={classNames( 'arduino-boards-dropdown-item--protocol', 'fa', - protocolIcon + iconNameFromProtocol(portProtocol) )} /> -
-
- {boardLabel} +
+
+
+ {boardLabel} +
- {port.addressLabel} + {portLabel}
- {selected ?
: ''} + {this.renderActions(item)} +
+ ); + } + + private renderActions(item: BoardListItemUI): React.ReactNode { + const { boardList, hide } = this.props; + const { revert, edit } = item.otherActions; + if (!edit && !revert) { + return undefined; + } + const handleOnClick = ( + event: React.MouseEvent, + callback: () => void + ) => { + event.preventDefault(); + event.stopPropagation(); + hide(); + callback(); + }; + return ( +
+ {edit && ( +
+ { +
+ handleOnClick(event, () => boardList.edit(edit.params)) + } + /> + } +
+ )} + {revert && ( +
+ { +
+ handleOnClick(event, () => boardList.select(revert.params)) + } + /> + } +
+ )}
); } @@ -153,26 +231,27 @@ export class BoardsToolBarItem extends React.Component< > { static TOOLBAR_ID: 'boards-toolbar'; - protected readonly toDispose: DisposableCollection = - new DisposableCollection(); + private readonly toDispose: DisposableCollection; constructor(props: BoardsToolBarItem.Props) { super(props); - - const { availableBoards } = props.boardsServiceProvider; + const { boardList } = props.boardsServiceProvider; this.state = { - availableBoards, + boardList, coords: 'hidden', }; - - document.addEventListener('click', () => { - this.setState({ coords: 'hidden' }); - }); + const listener = () => this.setState({ coords: 'hidden' }); + document.addEventListener('click', listener); + this.toDispose = new DisposableCollection( + Disposable.create(() => document.removeEventListener('click', listener)) + ); } override componentDidMount(): void { - this.props.boardsServiceProvider.onAvailableBoardsChanged( - (availableBoards) => this.setState({ availableBoards }) + this.toDispose.push( + this.props.boardsServiceProvider.onBoardListDidChange((boardList) => + this.setState({ boardList }) + ) ); } @@ -180,7 +259,7 @@ export class BoardsToolBarItem extends React.Component< this.toDispose.dispose(); } - protected readonly show = (event: React.MouseEvent): void => { + private readonly show = (event: React.MouseEvent): void => { const { currentTarget: element } = event; if (element instanceof HTMLElement) { if (this.state.coords === 'hidden') { @@ -201,31 +280,26 @@ export class BoardsToolBarItem extends React.Component< event.nativeEvent.stopImmediatePropagation(); }; - override render(): React.ReactNode { - const { coords, availableBoards } = this.state; - const { selectedBoard, selectedPort } = - this.props.boardsServiceProvider.boardsConfig; - - const boardLabel = - selectedBoard?.name || - nls.localize('arduino/board/selectBoard', 'Select Board'); - const selectedPortLabel = portLabel(selectedPort?.address); + private readonly hide = () => { + this.setState({ coords: 'hidden' }); + }; - const isConnected = Boolean(selectedBoard && selectedPort); - const protocolIcon = isConnected - ? iconNameFromProtocol(selectedPort?.protocol || '') + override render(): React.ReactNode { + const { coords, boardList } = this.state; + const { boardLabel, selected, portProtocol, tooltip } = boardList.labels; + const protocolIcon = portProtocol + ? iconNameFromProtocol(portProtocol) : null; const protocolIconClassNames = classNames( 'arduino-boards-toolbar-item--protocol', 'fa', protocolIcon ); - return (
{protocolIcon &&
} @@ -234,57 +308,22 @@ export class BoardsToolBarItem extends React.Component< 'arduino-boards-toolbar-item--label', 'noWrapInfo', 'noselect', - { 'arduino-boards-toolbar-item--label-connected': isConnected } + { 'arduino-boards-toolbar-item--label-connected': selected } )} > {boardLabel}
- ({ - ...board, - onClick: () => { - if (!board.fqbn) { - const previousBoardConfig = - this.props.boardsServiceProvider.boardsConfig; - this.props.boardsServiceProvider.boardsConfig = { - selectedPort: board.port, - }; - this.openDialog(previousBoardConfig); - } else { - this.props.boardsServiceProvider.boardsConfig = { - selectedBoard: board, - selectedPort: board.port, - }; - } - this.setState({ coords: 'hidden' }); - }, - }))} - openBoardsConfig={this.openDialog} - > + boardList={boardList} + openBoardsConfig={() => boardList.edit({ query: '' })} + hide={this.hide} + /> ); } - - protected openDialog = async ( - previousBoardConfig?: BoardsConfig.Config - ): Promise => { - const selectedBoardConfig = - await this.props.commands.executeCommand( - OpenBoardsConfig.Commands.OPEN_DIALOG.id - ); - if ( - previousBoardConfig && - (!selectedBoardConfig?.selectedPort || - !selectedBoardConfig?.selectedBoard) - ) { - this.props.boardsServiceProvider.boardsConfig = previousBoardConfig; - } - }; } export namespace BoardsToolBarItem { export interface Props { @@ -293,7 +332,7 @@ export namespace BoardsToolBarItem { } export interface State { - availableBoards: AvailableBoard[]; + boardList: BoardListUI; coords: BoardsDropDownListCoords | 'hidden'; } } @@ -304,19 +343,10 @@ function iconNameFromProtocol(protocol: string): string { return 'fa-arduino-technology-usb'; case 'network': return 'fa-arduino-technology-connection'; - /* - Bluetooth ports are not listed yet from the CLI; - Not sure about the naming ('bluetooth'); make sure it's correct before uncommenting the following lines - */ - // case 'bluetooth': - // return 'fa-arduino-technology-bluetooth'; + // it is fine to assign dedicated icons to the protocols used by the official boards, + // but other than that it is best to avoid implementing any special handling + // for specific protocols in the IDE codebase. default: return 'fa-arduino-technology-3dimensionscube'; } } - -function portLabel(portName?: string): string { - return portName - ? nls.localize('arduino/board/portLabel', 'Port: {0}', portName) - : nls.localize('arduino/board/disconnected', 'Disconnected'); -} diff --git a/arduino-ide-extension/src/browser/contributions/board-selection.ts b/arduino-ide-extension/src/browser/contributions/board-selection.ts index 3e7558c83..ddeda87bc 100644 --- a/arduino-ide-extension/src/browser/contributions/board-selection.ts +++ b/arduino-ide-extension/src/browser/contributions/board-selection.ts @@ -1,57 +1,58 @@ -import { inject, injectable } from '@theia/core/shared/inversify'; -import { MenuModelRegistry } from '@theia/core/lib/common/menu'; import { - DisposableCollection, Disposable, + DisposableCollection, } from '@theia/core/lib/common/disposable'; -import { BoardsConfig } from '../boards/boards-config'; +import { MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry'; +import type { MenuPath } from '@theia/core/lib/common/menu/menu-types'; +import { nls } from '@theia/core/lib/common/nls'; +import { Deferred } from '@theia/core/lib/common/promise-util'; +import { inject, injectable } from '@theia/core/shared/inversify'; import { MainMenuManager } from '../../common/main-menu-manager'; +import { + BoardsService, + BoardWithPackage, + createPlatformIdentifier, + getBoardInfo, + InstalledBoardWithPackage, + platformIdentifierEquals, + Port, + serializePlatformIdentifier, +} from '../../common/protocol'; +import type { BoardList } from '../../common/protocol/board-list'; import { BoardsListWidget } from '../boards/boards-list-widget'; -import { NotificationCenter } from '../notification-center'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { ArduinoMenus, PlaceholderMenuNode, unregisterSubmenu, } from '../menu/arduino-menus'; -import { - BoardsService, - InstalledBoardWithPackage, - AvailablePorts, - Port, - getBoardInfo, -} from '../../common/protocol'; -import { SketchContribution, Command, CommandRegistry } from './contribution'; -import { nls } from '@theia/core/lib/common'; +import { NotificationCenter } from '../notification-center'; +import { Command, CommandRegistry, SketchContribution } from './contribution'; @injectable() export class BoardSelection extends SketchContribution { @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; - + private readonly commandRegistry: CommandRegistry; @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; - + private readonly mainMenuManager: MainMenuManager; @inject(MenuModelRegistry) - protected readonly menuModelRegistry: MenuModelRegistry; - + private readonly menuModelRegistry: MenuModelRegistry; @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; - + private readonly notificationCenter: NotificationCenter; @inject(BoardsService) - protected readonly boardsService: BoardsService; - + private readonly boardsService: BoardsService; @inject(BoardsServiceProvider) - protected readonly boardsServiceProvider: BoardsServiceProvider; + private readonly boardsServiceProvider: BoardsServiceProvider; - protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection(); + private readonly toDisposeBeforeMenuRebuild = new DisposableCollection(); + // do not query installed platforms on every change + private _installedBoards: Deferred | undefined; override registerCommands(registry: CommandRegistry): void { registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, { execute: async () => { const boardInfo = await getBoardInfo( - this.boardsServiceProvider.boardsConfig.selectedPort, - this.boardsService.getState() + this.boardsServiceProvider.boardList ); if (typeof boardInfo === 'string') { this.messageService.info(boardInfo); @@ -76,34 +77,35 @@ SN: ${SN} } override onStart(): void { - this.notificationCenter.onPlatformDidInstall(() => this.updateMenus()); - this.notificationCenter.onPlatformDidUninstall(() => this.updateMenus()); - this.boardsServiceProvider.onBoardsConfigChanged(() => this.updateMenus()); - this.boardsServiceProvider.onAvailableBoardsChanged(() => - this.updateMenus() - ); - this.boardsServiceProvider.onAvailablePortsChanged(() => - this.updateMenus() + this.notificationCenter.onPlatformDidInstall(() => this.updateMenus(true)); + this.notificationCenter.onPlatformDidUninstall(() => + this.updateMenus(true) ); + this.boardsServiceProvider.onBoardListDidChange(() => this.updateMenus()); } override async onReady(): Promise { this.updateMenus(); } - protected async updateMenus(): Promise { - const [installedBoards, availablePorts, config] = await Promise.all([ - this.installedBoards(), - this.boardsService.getState(), - this.boardsServiceProvider.boardsConfig, - ]); - this.rebuildMenus(installedBoards, availablePorts, config); + private async updateMenus(discardCache = false): Promise { + if (discardCache) { + this._installedBoards?.reject(); + this._installedBoards = undefined; + } + if (!this._installedBoards) { + this._installedBoards = new Deferred(); + this.installedBoards().then((installedBoards) => + this._installedBoards?.resolve(installedBoards) + ); + } + const installedBoards = await this._installedBoards.promise; + this.rebuildMenus(installedBoards, this.boardsServiceProvider.boardList); } - protected rebuildMenus( + private rebuildMenus( installedBoards: InstalledBoardWithPackage[], - availablePorts: AvailablePorts, - config: BoardsConfig.Config + boardList: BoardList ): void { this.toDisposeBeforeMenuRebuild.dispose(); @@ -112,7 +114,8 @@ SN: ${SN} ...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, '1_boards', ]; - const boardsSubmenuLabel = config.selectedBoard?.name; + const { selectedBoard, selectedPort } = boardList.boardsConfig; + const boardsSubmenuLabel = selectedBoard?.name; // Note: The submenu order starts from `100` because `Auto Format`, `Serial Monitor`, etc starts from `0` index. // The board specific items, and the rest, have order with `z`. We needed something between `0` and `z` with natural-order. this.menuModelRegistry.registerSubmenu( @@ -132,7 +135,7 @@ SN: ${SN} // Ports submenu const portsSubmenuPath = ArduinoMenus.TOOLS__PORTS_SUBMENU; - const portsSubmenuLabel = config.selectedPort?.address; + const portsSubmenuLabel = selectedPort?.address; this.menuModelRegistry.registerSubmenu( portsSubmenuPath, nls.localize( @@ -171,69 +174,116 @@ SN: ${SN} label: `${BoardsListWidget.WIDGET_LABEL}...`, }); - // Installed boards - installedBoards.forEach((board, index) => { - const { packageId, packageName, fqbn, name, manuallyInstalled } = board; + const selectedBoardPlatformId = selectedBoard + ? createPlatformIdentifier(selectedBoard) + : undefined; + + // Keys are the vendor IDs + type BoardsPerVendor = Record; + // Group boards by their platform names. The keys are the platform names as menu labels. + // If there is a platform name (menu label) collision, refine the menu label with the vendor ID. + const groupedBoards = new Map(); + for (const board of installedBoards) { + const { packageId, packageName } = board; + const { vendorId } = packageId; + let boardsPerPackageName = groupedBoards.get(packageName); + if (!boardsPerPackageName) { + boardsPerPackageName = {} as BoardsPerVendor; + groupedBoards.set(packageName, boardsPerPackageName); + } + let boardPerVendor: BoardWithPackage[] | undefined = + boardsPerPackageName[vendorId]; + if (!boardPerVendor) { + boardPerVendor = []; + boardsPerPackageName[vendorId] = boardPerVendor; + } + boardPerVendor.push(board); + } - const packageLabel = - packageName + - `${ - manuallyInstalled - ? nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)') - : '' - }`; - // Platform submenu - const platformMenuPath = [...boardsPackagesGroup, packageId]; - // Note: Registering the same submenu twice is a noop. No need to group the boards per platform. - this.menuModelRegistry.registerSubmenu(platformMenuPath, packageLabel, { - order: packageName.toLowerCase(), - }); + // Installed boards + Array.from(groupedBoards.entries()).forEach( + ([packageName, boardsPerPackage]) => { + const useVendorSuffix = Object.keys(boardsPerPackage).length > 1; + Object.entries(boardsPerPackage).forEach(([vendorId, boards]) => { + let platformMenuPath: MenuPath | undefined = undefined; + boards.forEach((board, index) => { + const { packageId, fqbn, name, manuallyInstalled } = board; + // create the platform submenu once. + // creating and registering the same submenu twice in Theia is a noop, though. + if (!platformMenuPath) { + let packageLabel = + packageName + + `${ + manuallyInstalled + ? nls.localize( + 'arduino/board/inSketchbook', + ' (in Sketchbook)' + ) + : '' + }`; + if ( + selectedBoardPlatformId && + platformIdentifierEquals(packageId, selectedBoardPlatformId) + ) { + packageLabel = `● ${packageLabel}`; + } + if (useVendorSuffix) { + packageLabel += ` (${vendorId})`; + } + // Platform submenu + platformMenuPath = [ + ...boardsPackagesGroup, + serializePlatformIdentifier(packageId), + ]; + this.menuModelRegistry.registerSubmenu( + platformMenuPath, + packageLabel, + { + order: packageName.toLowerCase(), + } + ); + } - const id = `arduino-select-board--${fqbn}`; - const command = { id }; - const handler = { - execute: () => { - if ( - fqbn !== this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn - ) { - this.boardsServiceProvider.boardsConfig = { - selectedBoard: { - name, - fqbn, - port: this.boardsServiceProvider.boardsConfig.selectedBoard - ?.port, // TODO: verify! - }, - selectedPort: - this.boardsServiceProvider.boardsConfig.selectedPort, + const id = `arduino-select-board--${fqbn}`; + const command = { id }; + const handler = { + execute: () => + this.boardsServiceProvider.updateConfig({ + name: name, + fqbn: fqbn, + }), + isToggled: () => fqbn === selectedBoard?.fqbn, }; - } - }, - isToggled: () => - fqbn === this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn, - }; - // Board menu - const menuAction = { - commandId: id, - label: name, - order: String(index).padStart(4), // pads with leading zeros for alphanumeric sort where order is 1, 2, 11, and NOT 1, 11, 2 - }; - this.commandRegistry.registerCommand(command, handler); - this.toDisposeBeforeMenuRebuild.push( - Disposable.create(() => this.commandRegistry.unregisterCommand(command)) - ); - this.menuModelRegistry.registerMenuAction(platformMenuPath, menuAction); - // Note: we do not dispose the menu actions individually. Calling `unregisterSubmenu` on the parent will wipe the children menu nodes recursively. - }); + // Board menu + const menuAction = { + commandId: id, + label: name, + order: String(index).padStart(4), // pads with leading zeros for alphanumeric sort where order is 1, 2, 11, and NOT 1, 11, 2 + }; + this.commandRegistry.registerCommand(command, handler); + this.toDisposeBeforeMenuRebuild.push( + Disposable.create(() => + this.commandRegistry.unregisterCommand(command) + ) + ); + this.menuModelRegistry.registerMenuAction( + platformMenuPath, + menuAction + ); + // Note: we do not dispose the menu actions individually. Calling `unregisterSubmenu` on the parent will wipe the children menu nodes recursively. + }); + }); + } + ); - // Installed ports + // Detected ports const registerPorts = ( protocol: string, - protocolOrder: number, - ports: AvailablePorts + ports: ReturnType, + protocolOrder: number ) => { - const portIDs = Object.keys(ports); - if (!portIDs.length) { + if (!ports.length) { return; } @@ -258,46 +308,26 @@ SN: ${SN} ) ); - // First we show addresses with recognized boards connected, - // then all the rest. - const sortedIDs = Object.keys(ports).sort( - (left: string, right: string): number => { - const [, leftBoards] = ports[left]; - const [, rightBoards] = ports[right]; - return rightBoards.length - leftBoards.length; - } - ); - - for (let i = 0; i < sortedIDs.length; i++) { - const portID = sortedIDs[i]; - const [port, boards] = ports[portID]; + for (let i = 0; i < ports.length; i++) { + const { port, boards } = ports[i]; + const portKey = Port.keyOf(port); let label = `${port.addressLabel}`; - if (boards.length) { + if (boards?.length) { const boardsList = boards.map((board) => board.name).join(', '); label = `${label} (${boardsList})`; } - const id = `arduino-select-port--${portID}`; + const id = `arduino-select-port--${portKey}`; const command = { id }; const handler = { execute: () => { - if ( - !Port.sameAs( - port, - this.boardsServiceProvider.boardsConfig.selectedPort - ) - ) { - this.boardsServiceProvider.boardsConfig = { - selectedBoard: - this.boardsServiceProvider.boardsConfig.selectedBoard, - selectedPort: port, - }; - } + this.boardsServiceProvider.updateConfig({ + protocol: port.protocol, + address: port.address, + }); + }, + isToggled: () => { + return i === ports.matchingIndex; }, - isToggled: () => - Port.sameAs( - port, - this.boardsServiceProvider.boardsConfig.selectedPort - ), }; const menuAction = { commandId: id, @@ -314,22 +344,12 @@ SN: ${SN} } }; - const grouped = AvailablePorts.groupByProtocol(availablePorts); + const groupedPorts = boardList.portsGroupedByProtocol(); let protocolOrder = 100; - // We first show serial and network ports, then all the rest - ['serial', 'network'].forEach((protocol) => { - const ports = grouped.get(protocol); - if (ports) { - registerPorts(protocol, protocolOrder, ports); - grouped.delete(protocol); - protocolOrder = protocolOrder + 100; - } - }); - grouped.forEach((ports, protocol) => { - registerPorts(protocol, protocolOrder, ports); - protocolOrder = protocolOrder + 100; + Object.entries(groupedPorts).forEach(([protocol, ports]) => { + registerPorts(protocol, ports, protocolOrder); + protocolOrder += 100; }); - this.mainMenuManager.update(); } diff --git a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts b/arduino-ide-extension/src/browser/contributions/boards-data-menu-updater.ts similarity index 73% rename from arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts rename to arduino-ide-extension/src/browser/contributions/boards-data-menu-updater.ts index f323621d3..d9fe0ae7e 100644 --- a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts +++ b/arduino-ide-extension/src/browser/contributions/boards-data-menu-updater.ts @@ -1,67 +1,66 @@ -import PQueue from 'p-queue'; -import { inject, injectable } from '@theia/core/shared/inversify'; -import { CommandRegistry } from '@theia/core/lib/common/command'; -import { MenuModelRegistry } from '@theia/core/lib/common/menu'; import { Disposable, DisposableCollection, } from '@theia/core/lib/common/disposable'; -import { BoardsServiceProvider } from './boards-service-provider'; -import { Board, ConfigOption, Programmer } from '../../common/protocol'; -import { FrontendApplicationContribution } from '@theia/core/lib/browser'; -import { BoardsDataStore } from './boards-data-store'; -import { MainMenuManager } from '../../common/main-menu-manager'; +import { nls } from '@theia/core/lib/common/nls'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import PQueue from 'p-queue'; +import { + BoardIdentifier, + ConfigOption, + isBoardIdentifierChangeEvent, + Programmer, +} from '../../common/protocol'; +import { BoardsDataStore } from '../boards/boards-data-store'; +import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus'; -import { nls } from '@theia/core/lib/common'; -import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { + CommandRegistry, + Contribution, + MenuModelRegistry, +} from './contribution'; @injectable() -export class BoardsDataMenuUpdater implements FrontendApplicationContribution { +export class BoardsDataMenuUpdater extends Contribution { @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; - + private readonly commandRegistry: CommandRegistry; @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; - - @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; - + private readonly menuRegistry: MenuModelRegistry; @inject(BoardsDataStore) - protected readonly boardsDataStore: BoardsDataStore; - + private readonly boardsDataStore: BoardsDataStore; @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; - - @inject(FrontendApplicationStateService) - private readonly appStateService: FrontendApplicationStateService; + private readonly boardsServiceProvider: BoardsServiceProvider; - protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); - protected readonly toDisposeOnBoardChange = new DisposableCollection(); + private readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); + private readonly toDisposeOnBoardChange = new DisposableCollection(); - async onStart(): Promise { - this.appStateService - .reachedState('ready') - .then(() => - this.updateMenuActions( - this.boardsServiceClient.boardsConfig.selectedBoard - ) - ); + override onStart(): void { this.boardsDataStore.onChanged(() => this.updateMenuActions( - this.boardsServiceClient.boardsConfig.selectedBoard + this.boardsServiceProvider.boardsConfig.selectedBoard ) ); - this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => - this.updateMenuActions(selectedBoard) + this.boardsServiceProvider.onBoardsConfigDidChange((event) => { + if (isBoardIdentifierChangeEvent(event)) { + this.updateMenuActions(event.selectedBoard); + } + }); + } + + override onReady(): void { + this.boardsServiceProvider.ready.then(() => + this.updateMenuActions( + this.boardsServiceProvider.boardsConfig.selectedBoard + ) ); } - protected async updateMenuActions( - selectedBoard: Board | undefined + private async updateMenuActions( + selectedBoard: BoardIdentifier | undefined ): Promise { return this.queue.add(async () => { this.toDisposeOnBoardChange.dispose(); - this.mainMenuManager.update(); + this.menuManager.update(); if (selectedBoard) { const { fqbn } = selectedBoard; if (fqbn) { @@ -172,7 +171,7 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution { ]); } } - this.mainMenuManager.update(); + this.menuManager.update(); } } }); diff --git a/arduino-ide-extension/src/browser/contributions/debug.ts b/arduino-ide-extension/src/browser/contributions/debug.ts index f43f00426..ee07d7aa5 100644 --- a/arduino-ide-extension/src/browser/contributions/debug.ts +++ b/arduino-ide-extension/src/browser/contributions/debug.ts @@ -5,8 +5,10 @@ import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { NotificationCenter } from '../notification-center'; import { Board, + BoardIdentifier, BoardsService, ExecutableService, + isBoardIdentifierChangeEvent, Sketch, } from '../../common/protocol'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; @@ -88,9 +90,11 @@ export class Debug extends SketchContribution { : Debug.Commands.START_DEBUGGING.label }`) ); - this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => - this.refreshState(selectedBoard) - ); + this.boardsServiceProvider.onBoardsConfigDidChange((event) => { + if (isBoardIdentifierChangeEvent(event)) { + this.refreshState(event.selectedBoard); + } + }); this.notificationCenter.onPlatformDidInstall(() => this.refreshState()); this.notificationCenter.onPlatformDidUninstall(() => this.refreshState()); } @@ -169,7 +173,7 @@ export class Debug extends SketchContribution { } private async startDebug( - board: Board | undefined = this.boardsServiceProvider.boardsConfig + board: BoardIdentifier | undefined = this.boardsServiceProvider.boardsConfig .selectedBoard ): Promise { if (!board) { diff --git a/arduino-ide-extension/src/browser/contributions/examples.ts b/arduino-ide-extension/src/browser/contributions/examples.ts index 16fc6a380..3796b7535 100644 --- a/arduino-ide-extension/src/browser/contributions/examples.ts +++ b/arduino-ide-extension/src/browser/contributions/examples.ts @@ -28,6 +28,8 @@ import { CoreService, SketchesService, Sketch, + isBoardIdentifierChangeEvent, + BoardIdentifier, } from '../../common/protocol'; import { nls } from '@theia/core/lib/common/nls'; import { unregisterSubmenu } from '../menu/arduino-menus'; @@ -108,7 +110,7 @@ export abstract class Examples extends SketchContribution { protected readonly coreService: CoreService; @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; + protected readonly boardsServiceProvider: BoardsServiceProvider; @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; @@ -117,12 +119,14 @@ export abstract class Examples extends SketchContribution { protected override init(): void { super.init(); - this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => - this.handleBoardChanged(selectedBoard) - ); + this.boardsServiceProvider.onBoardsConfigDidChange((event) => { + if (isBoardIdentifierChangeEvent(event)) { + this.handleBoardChanged(event.selectedBoard); + } + }); this.notificationCenter.onDidReinitialize(() => this.update({ - board: this.boardsServiceClient.boardsConfig.selectedBoard, + board: this.boardsServiceProvider.boardsConfig.selectedBoard, // No force refresh. The core client was already refreshed. }) ); @@ -134,7 +138,7 @@ export abstract class Examples extends SketchContribution { } protected abstract update(options?: { - board?: Board | undefined; + board?: BoardIdentifier | undefined; forceRefresh?: boolean; }): void; @@ -225,7 +229,7 @@ export abstract class Examples extends SketchContribution { protected createHandler(uri: string): CommandHandler { const forceUpdate = () => this.update({ - board: this.boardsServiceClient.boardsConfig.selectedBoard, + board: this.boardsServiceProvider.boardsConfig.selectedBoard, forceRefresh: true, }); return { @@ -306,7 +310,7 @@ export class LibraryExamples extends Examples { protected override async update( options: { board?: Board; forceRefresh?: boolean } = { - board: this.boardsServiceClient.boardsConfig.selectedBoard, + board: this.boardsServiceProvider.boardsConfig.selectedBoard, } ): Promise { const { board, forceRefresh } = options; diff --git a/arduino-ide-extension/src/browser/contributions/include-library.ts b/arduino-ide-extension/src/browser/contributions/include-library.ts index cb6479f18..5d77e9ec3 100644 --- a/arduino-ide-extension/src/browser/contributions/include-library.ts +++ b/arduino-ide-extension/src/browser/contributions/include-library.ts @@ -37,7 +37,7 @@ export class IncludeLibrary extends SketchContribution { protected readonly notificationCenter: NotificationCenter; @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; + protected readonly boardsServiceProvider: BoardsServiceProvider; @inject(LibraryService) protected readonly libraryService: LibraryService; @@ -46,7 +46,7 @@ export class IncludeLibrary extends SketchContribution { protected readonly toDispose = new DisposableCollection(); override onStart(): void { - this.boardsServiceClient.onBoardsConfigChanged(() => + this.boardsServiceProvider.onBoardsConfigDidChange(() => this.updateMenuActions() ); this.notificationCenter.onLibraryDidInstall(() => this.updateMenuActions()); @@ -98,7 +98,7 @@ export class IncludeLibrary extends SketchContribution { this.toDispose.dispose(); this.mainMenuManager.update(); const libraries: LibraryPackage[] = []; - const fqbn = this.boardsServiceClient.boardsConfig.selectedBoard?.fqbn; + const fqbn = this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn; // Show all libraries, when no board is selected. // Otherwise, show libraries only for the selected board. libraries.push(...(await this.libraryService.list({ fqbn }))); diff --git a/arduino-ide-extension/src/browser/contributions/ino-language.ts b/arduino-ide-extension/src/browser/contributions/ino-language.ts index 2577d5a73..096c27ed8 100644 --- a/arduino-ide-extension/src/browser/contributions/ino-language.ts +++ b/arduino-ide-extension/src/browser/contributions/ino-language.ts @@ -7,12 +7,13 @@ import { Mutex } from 'async-mutex'; import { ArduinoDaemon, assertSanitizedFqbn, + BoardIdentifier, BoardsService, ExecutableService, + isBoardIdentifierChangeEvent, sanitizeFqbn, } from '../../common/protocol'; import { CurrentSketch } from '../sketches-service-client-impl'; -import { BoardsConfig } from '../boards/boards-config'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { HostedPluginEvents } from '../hosted-plugin-events'; import { NotificationCenter } from '../notification-center'; @@ -48,7 +49,7 @@ export class InoLanguage extends SketchContribution { override onReady(): void { const start = ( - { selectedBoard }: BoardsConfig.Config, + selectedBoard: BoardIdentifier | undefined, forceStart = false ) => { if (selectedBoard) { @@ -59,12 +60,16 @@ export class InoLanguage extends SketchContribution { } }; const forceRestart = () => { - start(this.boardsServiceProvider.boardsConfig, true); + start(this.boardsServiceProvider.boardsConfig.selectedBoard, true); }; this.toDispose.pushAll([ - this.boardsServiceProvider.onBoardsConfigChanged(start), + this.boardsServiceProvider.onBoardsConfigDidChange((event) => { + if (isBoardIdentifierChangeEvent(event)) { + start(event.selectedBoard); + } + }), this.hostedPluginEvents.onPluginsDidStart(() => - start(this.boardsServiceProvider.boardsConfig) + start(this.boardsServiceProvider.boardsConfig.selectedBoard) ), this.hostedPluginEvents.onPluginsWillUnload( () => (this.languageServerFqbn = undefined) @@ -101,12 +106,14 @@ export class InoLanguage extends SketchContribution { matchingFqbn && boardsConfig.selectedBoard?.fqbn === matchingFqbn ) { - start(boardsConfig); + start(boardsConfig.selectedBoard); } } }), ]); - start(this.boardsServiceProvider.boardsConfig); + this.boardsServiceProvider.ready.then(() => + start(this.boardsServiceProvider.boardsConfig.selectedBoard) + ); } onStop(): void { diff --git a/arduino-ide-extension/src/browser/contributions/open-boards-config.ts b/arduino-ide-extension/src/browser/contributions/open-boards-config.ts index 8feffc14f..443c3e5e4 100644 --- a/arduino-ide-extension/src/browser/contributions/open-boards-config.ts +++ b/arduino-ide-extension/src/browser/contributions/open-boards-config.ts @@ -1,25 +1,18 @@ -import { CommandRegistry } from '@theia/core'; +import type { Command, CommandRegistry } from '@theia/core/lib/common/command'; import { inject, injectable } from '@theia/core/shared/inversify'; +import type { EditBoardsConfigActionParams } from '../../common/protocol/board-list'; import { BoardsConfigDialog } from '../boards/boards-config-dialog'; -import { BoardsServiceProvider } from '../boards/boards-service-provider'; -import { Contribution, Command } from './contribution'; +import { Contribution } from './contribution'; @injectable() export class OpenBoardsConfig extends Contribution { - @inject(BoardsServiceProvider) - private readonly boardsServiceProvider: BoardsServiceProvider; - @inject(BoardsConfigDialog) private readonly boardsConfigDialog: BoardsConfigDialog; override registerCommands(registry: CommandRegistry): void { registry.registerCommand(OpenBoardsConfig.Commands.OPEN_DIALOG, { - execute: async (query?: string | undefined) => { - const boardsConfig = await this.boardsConfigDialog.open(query); - if (boardsConfig) { - return (this.boardsServiceProvider.boardsConfig = boardsConfig); - } - }, + execute: async (params?: EditBoardsConfigActionParams) => + this.boardsConfigDialog.open(params), }); } } diff --git a/arduino-ide-extension/src/browser/contributions/selected-board.ts b/arduino-ide-extension/src/browser/contributions/selected-board.ts index bf8a84ae8..8c1ad74d6 100644 --- a/arduino-ide-extension/src/browser/contributions/selected-board.ts +++ b/arduino-ide-extension/src/browser/contributions/selected-board.ts @@ -4,7 +4,10 @@ import { } from '@theia/core/lib/browser/status-bar/status-bar'; import { nls } from '@theia/core/lib/common/nls'; import { inject, injectable } from '@theia/core/shared/inversify'; -import { BoardsConfig } from '../boards/boards-config'; +import type { + BoardList, + BoardListItem, +} from '../../common/protocol/board-list'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { Contribution } from './contribution'; @@ -12,21 +15,21 @@ import { Contribution } from './contribution'; export class SelectedBoard extends Contribution { @inject(StatusBar) private readonly statusBar: StatusBar; - @inject(BoardsServiceProvider) private readonly boardsServiceProvider: BoardsServiceProvider; override onStart(): void { - this.boardsServiceProvider.onBoardsConfigChanged((config) => - this.update(config) + this.boardsServiceProvider.onBoardListDidChange(() => + this.update(this.boardsServiceProvider.boardList) ); } override onReady(): void { - this.update(this.boardsServiceProvider.boardsConfig); + this.update(this.boardsServiceProvider.boardList); } - private update({ selectedBoard, selectedPort }: BoardsConfig.Config): void { + private update(boardList: BoardList): void { + const { selectedBoard, selectedPort } = boardList.boardsConfig; this.statusBar.setElement('arduino-selected-board', { alignment: StatusBarAlignment.RIGHT, text: selectedBoard @@ -38,17 +41,30 @@ export class SelectedBoard extends Contribution { className: 'arduino-selected-board', }); if (selectedBoard) { + const notConnectedLabel = nls.localize( + 'arduino/common/notConnected', + '[not connected]' + ); + let portLabel = notConnectedLabel; + if (selectedPort) { + portLabel = nls.localize( + 'arduino/common/selectedOn', + 'on {0}', + selectedPort.address + ); + const selectedItem: BoardListItem | undefined = + boardList.items[boardList.selectedIndex]; + if (!selectedItem) { + portLabel += ` ${notConnectedLabel}`; // append ` [not connected]` when the port is selected but it's not detected by the CLI + } + } this.statusBar.setElement('arduino-selected-port', { alignment: StatusBarAlignment.RIGHT, - text: selectedPort - ? nls.localize( - 'arduino/common/selectedOn', - 'on {0}', - selectedPort.address - ) - : nls.localize('arduino/common/notConnected', '[not connected]'), + text: portLabel, className: 'arduino-selected-port', }); + } else { + this.statusBar.removeElement('arduino-selected-port'); } } } diff --git a/arduino-ide-extension/src/browser/contributions/update-arduino-state.ts b/arduino-ide-extension/src/browser/contributions/update-arduino-state.ts index a227a51a0..35b4c2ab7 100644 --- a/arduino-ide-extension/src/browser/contributions/update-arduino-state.ts +++ b/arduino-ide-extension/src/browser/contributions/update-arduino-state.ts @@ -6,15 +6,16 @@ import type { ArduinoState } from 'vscode-arduino-api'; import { BoardsService, CompileSummary, - Port, isCompileSummary, + BoardsConfig, + PortIdentifier, + resolveDetectedPort, } from '../../common/protocol'; import { toApiBoardDetails, toApiCompileSummary, toApiPort, } from '../../common/protocol/arduino-context-mapper'; -import type { BoardsConfig } from '../boards/boards-config'; import { BoardsDataStore } from '../boards/boards-data-store'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { CurrentSketch } from '../sketches-service-client-impl'; @@ -44,8 +45,8 @@ export class UpdateArduinoState extends SketchContribution { override onStart(): void { this.toDispose.pushAll([ - this.boardsServiceProvider.onBoardsConfigChanged((config) => - this.updateBoardsConfig(config) + this.boardsServiceProvider.onBoardsConfigDidChange(() => + this.updateBoardsConfig(this.boardsServiceProvider.boardsConfig) ), this.sketchServiceClient.onCurrentSketchDidChange((sketch) => this.updateSketchPath(sketch) @@ -75,9 +76,7 @@ export class UpdateArduinoState extends SketchContribution { } override onReady(): void { - this.boardsServiceProvider.reconciled.then(() => { - this.updateBoardsConfig(this.boardsServiceProvider.boardsConfig); - }); + this.updateBoardsConfig(this.boardsServiceProvider.boardsConfig); // TODO: verify! this.updateSketchPath(this.sketchServiceClient.tryGetCurrentSketch()); this.updateUserDirPath(this.configService.tryGetSketchDirUri()); this.updateDataDirPath(this.configService.tryGetDataDirUri()); @@ -106,9 +105,7 @@ export class UpdateArduinoState extends SketchContribution { }); } - private async updateBoardsConfig( - boardsConfig: BoardsConfig.Config - ): Promise { + private async updateBoardsConfig(boardsConfig: BoardsConfig): Promise { const fqbn = boardsConfig.selectedBoard?.fqbn; const port = boardsConfig.selectedPort; await this.updateFqbn(fqbn); @@ -146,8 +143,11 @@ export class UpdateArduinoState extends SketchContribution { }); } - private async updatePort(port: Port | undefined): Promise { - const apiPort = port && toApiPort(port); + 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 }); } @@ -171,9 +171,6 @@ export class UpdateArduinoState extends SketchContribution { params: UpdateStateParams ): Promise { await this.hostedPluginSupport.didStart; - return this.commandService.executeCommand( - 'arduinoAPI.updateState', - params - ); + return this.commandService.executeCommand('arduinoAPI.updateState', params); } } diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 034ea87d3..75c591dba 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -1,30 +1,30 @@ -import { inject, injectable } from '@theia/core/shared/inversify'; import { Emitter } from '@theia/core/lib/common/event'; -import { CoreService, Port, sanitizeFqbn } from '../../common/protocol'; +import { nls } from '@theia/core/lib/common/nls'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { CoreService, sanitizeFqbn } from '../../common/protocol'; import { ArduinoMenus } from '../menu/arduino-menus'; +import { CurrentSketch } from '../sketches-service-client-impl'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { Command, CommandRegistry, - MenuModelRegistry, + CoreServiceContribution, KeybindingRegistry, + MenuModelRegistry, TabBarToolbarRegistry, - CoreServiceContribution, } from './contribution'; -import { deepClone, nls } from '@theia/core/lib/common'; -import { CurrentSketch } from '../sketches-service-client-impl'; -import type { VerifySketchParams } from './verify-sketch'; import { UserFields } from './user-fields'; +import type { VerifySketchParams } from './verify-sketch'; @injectable() export class UploadSketch extends CoreServiceContribution { + @inject(UserFields) + private readonly userFields: UserFields; + private readonly onDidChangeEmitter = new Emitter(); private readonly onDidChange = this.onDidChangeEmitter.event; private uploadInProgress = false; - @inject(UserFields) - private readonly userFields: UserFields; - override registerCommands(registry: CommandRegistry): void { registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, { execute: async () => { @@ -107,7 +107,6 @@ export class UploadSketch extends CoreServiceContribution { // uploadInProgress will be set to false whether the upload fails or not this.uploadInProgress = true; this.menuManager.update(); - this.boardsServiceProvider.snapshotBoardDiscoveryOnUpload(); this.onDidChangeEmitter.fire(); this.clearVisibleNotification(); @@ -135,12 +134,14 @@ export class UploadSketch extends CoreServiceContribution { return; } - await this.doWithProgress({ + const uploadResponse = await this.doWithProgress({ progressText: nls.localize('arduino/sketch/uploading', 'Uploading...'), task: (progressId, coreService) => coreService.upload({ ...uploadOptions, progressId }), keepOutput: true, }); + // the port update is NOOP if nothing has changed + this.boardsServiceProvider.updateConfig(uploadResponse.portAfterUpload); this.messageService.info( nls.localize('arduino/sketch/doneUploading', 'Done uploading.'), @@ -150,9 +151,10 @@ export class UploadSketch extends CoreServiceContribution { this.userFields.notifyFailedWithError(e); this.handleError(e); } finally { + // TODO: here comes the port change if happened during the upload + // https://github.com/arduino/arduino-cli/issues/2245 this.uploadInProgress = false; this.menuManager.update(); - this.boardsServiceProvider.attemptPostUploadAutoSelect(); this.onDidChangeEmitter.fire(); } } @@ -174,7 +176,7 @@ export class UploadSketch extends CoreServiceContribution { this.preferences.get('arduino.upload.verify'), this.preferences.get('arduino.upload.verbose'), ]); - const port = this.maybeUpdatePortProperties(boardsConfig.selectedPort); + const port = boardsConfig.selectedPort; return { sketch, fqbn, @@ -185,28 +187,6 @@ export class UploadSketch extends CoreServiceContribution { userFields, }; } - - /** - * This is a hack to ensure that the port object has the `properties` when uploading.(https://github.com/arduino/arduino-ide/issues/740) - * This method works around a bug when restoring a `port` persisted by an older version of IDE2. See the bug [here](https://github.com/arduino/arduino-ide/pull/1335#issuecomment-1224355236). - * - * Before the upload, this method checks the available ports and makes sure that the `properties` of an available port, and the port selected by the user have the same `properties`. - * This method does not update any state (for example, the `BoardsConfig.Config`) but uses the correct `properties` for the `upload`. - */ - private maybeUpdatePortProperties(port: Port | undefined): Port | undefined { - if (port) { - const key = Port.keyOf(port); - for (const candidate of this.boardsServiceProvider.availablePorts) { - if (key === Port.keyOf(candidate) && candidate.properties) { - return { - ...port, - properties: deepClone(candidate.properties), - }; - } - } - } - return port; - } } export namespace UploadSketch { diff --git a/arduino-ide-extension/src/browser/contributions/user-fields.ts b/arduino-ide-extension/src/browser/contributions/user-fields.ts index 62bef9748..14a4e55a8 100644 --- a/arduino-ide-extension/src/browser/contributions/user-fields.ts +++ b/arduino-ide-extension/src/browser/contributions/user-fields.ts @@ -21,7 +21,7 @@ export class UserFields extends Contribution { protected override init(): void { super.init(); - this.boardsServiceProvider.onBoardsConfigChanged(async () => { + this.boardsServiceProvider.onBoardsConfigDidChange(async () => { const userFields = await this.boardsServiceProvider.selectedBoardUserFields(); this.boardRequiresUserFields = userFields.length > 0; @@ -43,10 +43,7 @@ export class UserFields extends Contribution { if (!fqbn) { return undefined; } - const address = - boardsConfig.selectedBoard?.port?.address || - boardsConfig.selectedPort?.address || - ''; + const address = boardsConfig.selectedPort?.address || ''; return fqbn + '|' + address; } diff --git a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-component.tsx b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-component.tsx index b426b82e3..21f8f2406 100644 --- a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-component.tsx +++ b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-component.tsx @@ -1,20 +1,30 @@ +import { nls } from '@theia/core/lib/common/nls'; import React from '@theia/core/shared/react'; import Tippy from '@tippyjs/react'; -import { AvailableBoard } from '../../boards/boards-service-provider'; -import { CertificateListComponent } from './certificate-list'; -import { SelectBoardComponent } from './select-board-components'; +import { + BoardList, + isInferredBoardListItem, +} from '../../../common/protocol/board-list'; +import { + boardIdentifierEquals, + portIdentifierEquals, +} from '../../../common/protocol/boards-service'; import { CertificateAddComponent } from './certificate-add-new'; -import { nls } from '@theia/core/lib/common'; +import { CertificateListComponent } from './certificate-list'; +import { + BoardOptionValue, + SelectBoardComponent, +} from './select-board-components'; export const CertificateUploaderComponent = ({ - availableBoards, + boardList, certificates, addCertificate, updatableFqbns, uploadCertificates, openContextMenu, }: { - availableBoards: AvailableBoard[]; + boardList: BoardList; certificates: string[]; addCertificate: (cert: string) => void; updatableFqbns: string[]; @@ -33,11 +43,17 @@ export const CertificateUploaderComponent = ({ const [selectedCerts, setSelectedCerts] = React.useState([]); - const [selectedBoard, setSelectedBoard] = - React.useState(null); + const [selectedItem, setSelectedItem] = + React.useState(null); const installCertificates = async () => { - if (!selectedBoard || !selectedBoard.fqbn || !selectedBoard.port) { + if (!selectedItem) { + return; + } + const board = isInferredBoardListItem(selectedItem) + ? selectedItem.inferredBoard + : selectedItem.board; + if (!board.fqbn) { return; } @@ -45,8 +61,8 @@ export const CertificateUploaderComponent = ({ try { await uploadCertificates( - selectedBoard.fqbn, - selectedBoard.port.address, + board.fqbn, + selectedItem.port.address, selectedCerts ); setInstallFeedback('ok'); @@ -55,17 +71,29 @@ export const CertificateUploaderComponent = ({ } }; - const onBoardSelect = React.useCallback( - (board: AvailableBoard) => { - const newFqbn = (board && board.fqbn) || null; - const prevFqbn = (selectedBoard && selectedBoard.fqbn) || null; + const onItemSelect = React.useCallback( + (item: BoardOptionValue | null) => { + if (!item) { + return; + } + const board = isInferredBoardListItem(item) + ? item.inferredBoard + : item.board; + const selectedBoard = isInferredBoardListItem(selectedItem) + ? selectedItem.inferredBoard + : selectedItem?.board; + const port = item.port; + const selectedPort = selectedItem?.port; - if (newFqbn !== prevFqbn) { + if ( + !boardIdentifierEquals(board, selectedBoard) || + !portIdentifierEquals(port, selectedPort) + ) { setInstallFeedback(null); - setSelectedBoard(board); + setSelectedItem(item); } }, - [selectedBoard] + [selectedItem] ); return ( @@ -125,10 +153,10 @@ export const CertificateUploaderComponent = ({
@@ -167,7 +195,7 @@ export const CertificateUploaderComponent = ({ type="button" className="theia-button primary install-cert-btn" onClick={installCertificates} - disabled={selectedCerts.length === 0 || !selectedBoard} + disabled={selectedCerts.length === 0 || !selectedItem} > {nls.localize('arduino/certificate/upload', 'Upload')} diff --git a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx index 5dde52d91..921807d6f 100644 --- a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx @@ -1,62 +1,51 @@ -import React from '@theia/core/shared/react'; +import { DialogProps } from '@theia/core/lib/browser/dialogs'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { + PreferenceScope, + PreferenceService, +} from '@theia/core/lib/browser/preferences/preference-service'; +import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; +import { CommandRegistry } from '@theia/core/lib/common/command'; +import { nls } from '@theia/core/lib/common/nls'; +import type { Message } from '@theia/core/shared/@phosphor/messaging'; +import { Widget } from '@theia/core/shared/@phosphor/widgets'; import { inject, injectable, postConstruct, } from '@theia/core/shared/inversify'; -import { DialogProps } from '@theia/core/lib/browser/dialogs'; +import React from '@theia/core/shared/react'; +import { ArduinoFirmwareUploader } from '../../../common/protocol/arduino-firmware-uploader'; +import { createBoardList } from '../../../common/protocol/board-list'; +import { ArduinoPreferences } from '../../arduino-preferences'; +import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { AbstractDialog } from '../../theia/dialogs/dialogs'; -import { Widget } from '@theia/core/shared/@phosphor/widgets'; -import { Message } from '@theia/core/shared/@phosphor/messaging'; -import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; -import { - AvailableBoard, - BoardsServiceProvider, -} from '../../boards/boards-service-provider'; import { CertificateUploaderComponent } from './certificate-uploader-component'; -import { ArduinoPreferences } from '../../arduino-preferences'; -import { - PreferenceScope, - PreferenceService, -} from '@theia/core/lib/browser/preferences/preference-service'; -import { CommandRegistry } from '@theia/core/lib/common/command'; import { certificateList, sanifyCertString } from './utils'; -import { ArduinoFirmwareUploader } from '../../../common/protocol/arduino-firmware-uploader'; -import { nls } from '@theia/core/lib/common'; -import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @injectable() export class UploadCertificateDialogWidget extends ReactWidget { @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; - + private readonly boardsServiceProvider: BoardsServiceProvider; @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; - + private readonly arduinoPreferences: ArduinoPreferences; @inject(PreferenceService) - protected readonly preferenceService: PreferenceService; - + private readonly preferenceService: PreferenceService; @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; - + private readonly commandRegistry: CommandRegistry; @inject(ArduinoFirmwareUploader) - protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader; - + private readonly arduinoFirmwareUploader: ArduinoFirmwareUploader; @inject(FrontendApplicationStateService) private readonly appStateService: FrontendApplicationStateService; - protected certificates: string[] = []; - protected updatableFqbns: string[] = []; - protected availableBoards: AvailableBoard[] = []; + private certificates: string[] = []; + private updatableFqbns: string[] = []; + private boardList = createBoardList({}); - public busyCallback = (busy: boolean) => { + busyCallback = (busy: boolean) => { return; }; - constructor() { - super(); - } - @postConstruct() protected init(): void { this.arduinoPreferences.ready.then(() => { @@ -81,8 +70,8 @@ export class UploadCertificateDialogWidget extends ReactWidget { }) ); - this.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => { - this.availableBoards = availableBoards; + this.boardsServiceProvider.onBoardListDidChange((boardList) => { + this.boardList = boardList; this.update(); }); } @@ -126,7 +115,7 @@ export class UploadCertificateDialogWidget extends ReactWidget { protected render(): React.ReactNode { return ( { @inject(UploadCertificateDialogWidget) - protected readonly widget: UploadCertificateDialogWidget; + private readonly widget: UploadCertificateDialogWidget; private busy = false; diff --git a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/select-board-components.tsx b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/select-board-components.tsx index ca4cd91be..49aa5617d 100644 --- a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/select-board-components.tsx +++ b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/select-board-components.tsx @@ -1,37 +1,38 @@ import { nls } from '@theia/core/lib/common'; import React from '@theia/core/shared/react'; -import { AvailableBoard } from '../../boards/boards-service-provider'; +import { + BoardList, + BoardListItemWithBoard, + InferredBoardListItem, + isInferredBoardListItem, +} from '../../../common/protocol/board-list'; import { ArduinoSelect } from '../../widgets/arduino-select'; -type BoardOption = { value: string; label: string }; +export type BoardOptionValue = BoardListItemWithBoard | InferredBoardListItem; +type BoardOption = { value: BoardOptionValue | undefined; label: string }; export const SelectBoardComponent = ({ - availableBoards, + boardList, updatableFqbns, - onBoardSelect, - selectedBoard, + onItemSelect, + selectedItem, busy, }: { - availableBoards: AvailableBoard[]; + boardList: BoardList; updatableFqbns: string[]; - onBoardSelect: (board: AvailableBoard | null) => void; - selectedBoard: AvailableBoard | null; + onItemSelect: (item: BoardOptionValue | null) => void; + selectedItem: BoardOptionValue | null; busy: boolean; }): React.ReactElement => { const [selectOptions, setSelectOptions] = React.useState([]); - const [selectBoardPlaceholder, setSelectBoardPlaceholder] = - React.useState(''); + const [selectItemPlaceholder, setSelectBoardPlaceholder] = React.useState(''); const selectOption = React.useCallback( - (boardOpt: BoardOption) => { - onBoardSelect( - (boardOpt && - availableBoards.find((board) => board.fqbn === boardOpt.value)) || - null - ); + (boardOpt: BoardOption | null) => { + onItemSelect(boardOpt?.value ?? null); }, - [availableBoards, onBoardSelect] + [onItemSelect] ); React.useEffect(() => { @@ -44,26 +45,33 @@ export const SelectBoardComponent = ({ 'arduino/certificate/selectBoard', 'Select a board...' ); + const updatableBoards = boardList.boards.filter((item) => { + const fqbn = ( + isInferredBoardListItem(item) ? item.inferredBoard : item.board + ).fqbn; + return fqbn && updatableFqbns.includes(fqbn); + }); let selBoard = -1; - const updatableBoards = availableBoards.filter( - (board) => board.port && board.fqbn && updatableFqbns.includes(board.fqbn) - ); - const boardsList: BoardOption[] = updatableBoards.map((board, i) => { - if (board.selected) { + + const boardOptions: BoardOption[] = updatableBoards.map((item, i) => { + if (selectedItem === item) { selBoard = i; } + const board = isInferredBoardListItem(item) + ? item.inferredBoard + : item.board; return { label: nls.localize( 'arduino/certificate/boardAtPort', '{0} at {1}', board.name, - board.port?.address ?? '' + item.port?.address ?? '' ), - value: board.fqbn || '', + value: item, }; }); - if (boardsList.length === 0) { + if (boardOptions.length === 0) { placeholderTxt = nls.localize( 'arduino/certificate/noSupportedBoardConnected', 'No supported board connected' @@ -71,32 +79,32 @@ export const SelectBoardComponent = ({ } setSelectBoardPlaceholder(placeholderTxt); - setSelectOptions(boardsList); + setSelectOptions(boardOptions); - if (selectedBoard) { - selBoard = boardsList - .map((boardOpt) => boardOpt.value) - .indexOf(selectedBoard.fqbn || ''); + if (selectedItem) { + selBoard = updatableBoards.indexOf(selectedItem); } - selectOption(boardsList[selBoard] || null); - }, [busy, availableBoards, selectOption, updatableFqbns, selectedBoard]); - + selectOption(boardOptions[selBoard] || null); + }, [busy, boardList, selectOption, updatableFqbns, selectedItem]); return ( Promise; @@ -31,8 +39,8 @@ export const FirmwareUploaderComponent = ({ 'ok' | 'fail' | 'installing' | null >(null); - const [selectedBoard, setSelectedBoard] = - React.useState(null); + const [selectedItem, setSelectedItem] = + React.useState(null); const [availableFirmwares, setAvailableFirmwares] = React.useState< FirmwareInfo[] @@ -50,13 +58,16 @@ export const FirmwareUploaderComponent = ({ const fetchFirmwares = React.useCallback(async () => { setInstallFeedback(null); setFirmwaresFetching(true); - if (!selectedBoard) { + if (!selectedItem) { return; } // fetch the firmwares for the selected board + const board = isInferredBoardListItem(selectedItem) + ? selectedItem.inferredBoard + : selectedItem.board; const firmwaresForFqbn = await firmwareUploader.availableFirmwares( - selectedBoard.fqbn || '' + board.fqbn || '' ); setAvailableFirmwares(firmwaresForFqbn); @@ -69,7 +80,7 @@ export const FirmwareUploaderComponent = ({ if (firmwaresForFqbn.length > 0) setSelectedFirmware(firmwaresOpts[0]); setFirmwaresFetching(false); - }, [firmwareUploader, selectedBoard]); + }, [firmwareUploader, selectedItem]); const installFirmware = React.useCallback(async () => { setInstallFeedback('installing'); @@ -81,27 +92,39 @@ export const FirmwareUploaderComponent = ({ try { const installStatus = !!firmwareToFlash && - !!selectedBoard?.port && - (await flashFirmware(firmwareToFlash, selectedBoard?.port)); + !!selectedItem?.board && + (await flashFirmware(firmwareToFlash, selectedItem?.port)); setInstallFeedback((installStatus && 'ok') || 'fail'); } catch { setInstallFeedback('fail'); } - }, [firmwareUploader, selectedBoard, selectedFirmware, availableFirmwares]); + }, [selectedItem, selectedFirmware, availableFirmwares, flashFirmware]); - const onBoardSelect = React.useCallback( - (board: AvailableBoard) => { - const newFqbn = (board && board.fqbn) || null; - const prevFqbn = (selectedBoard && selectedBoard.fqbn) || null; - - if (newFqbn !== prevFqbn) { + const onItemSelect = React.useCallback( + (item: BoardListItemWithBoard | null) => { + if (!item) { + return; + } + const board = isInferredBoardListItem(item) + ? item.inferredBoard + : item.board; + const selectedBoard = isInferredBoardListItem(selectedItem) + ? selectedItem.inferredBoard + : selectedItem?.board; + const port = item.port; + const selectedPort = selectedItem?.port; + + if ( + !boardIdentifierEquals(board, selectedBoard) || + !portIdentifierEquals(port, selectedPort) + ) { setInstallFeedback(null); setAvailableFirmwares([]); - setSelectedBoard(board); + setSelectedItem(item); } }, - [selectedBoard] + [selectedItem] ); return ( @@ -115,10 +138,10 @@ export const FirmwareUploaderComponent = ({
@@ -126,7 +149,7 @@ export const FirmwareUploaderComponent = ({ type="button" className="theia-button secondary" disabled={ - selectedBoard === null || + selectedItem === null || firmwaresFetching || installFeedback === 'installing' } @@ -150,7 +173,7 @@ export const FirmwareUploaderComponent = ({ id="firmware-select" menuPosition="fixed" isDisabled={ - !selectedBoard || + !selectedItem || firmwaresFetching || installFeedback === 'installing' } diff --git a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx index c9e23b937..44745d5f2 100644 --- a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx @@ -1,24 +1,21 @@ -import React from '@theia/core/shared/react'; +import { DialogProps } from '@theia/core/lib/browser/dialogs'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import type { Message } from '@theia/core/shared/@phosphor/messaging'; import { inject, injectable, postConstruct, } from '@theia/core/shared/inversify'; -import { DialogProps } from '@theia/core/lib/browser/dialogs'; -import { ReactDialog } from '../../theia/dialogs/dialogs'; -import { Message } from '@theia/core/shared/@phosphor/messaging'; -import { - AvailableBoard, - BoardsServiceProvider, -} from '../../boards/boards-service-provider'; +import React from '@theia/core/shared/react'; import { ArduinoFirmwareUploader, FirmwareInfo, } from '../../../common/protocol/arduino-firmware-uploader'; -import { FirmwareUploaderComponent } from './firmware-uploader-component'; +import type { Port } from '../../../common/protocol/boards-service'; +import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { UploadFirmware } from '../../contributions/upload-firmware'; -import { Port } from '../../../common/protocol'; -import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { ReactDialog } from '../../theia/dialogs/dialogs'; +import { FirmwareUploaderComponent } from './firmware-uploader-component'; @injectable() export class UploadFirmwareDialogProps extends DialogProps {} @@ -26,14 +23,13 @@ export class UploadFirmwareDialogProps extends DialogProps {} @injectable() export class UploadFirmwareDialog extends ReactDialog { @inject(BoardsServiceProvider) - private readonly boardsServiceClient: BoardsServiceProvider; + private readonly boardsServiceProvider: BoardsServiceProvider; @inject(ArduinoFirmwareUploader) private readonly arduinoFirmwareUploader: ArduinoFirmwareUploader; @inject(FrontendApplicationStateService) - private readonly appStatusService: FrontendApplicationStateService; + private readonly appStateService: FrontendApplicationStateService; private updatableFqbns: string[] = []; - private availableBoards: AvailableBoard[] = []; private isOpen = new Object(); private busy = false; @@ -49,16 +45,12 @@ export class UploadFirmwareDialog extends ReactDialog { @postConstruct() protected init(): void { - this.appStatusService.reachedState('ready').then(async () => { + this.appStateService.reachedState('ready').then(async () => { const fqbns = await this.arduinoFirmwareUploader.updatableBoards(); this.updatableFqbns = fqbns; this.update(); }); - - this.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => { - this.availableBoards = availableBoards; - this.update(); - }); + this.boardsServiceProvider.onBoardListDidChange(() => this.update()); } get value(): void { @@ -70,7 +62,7 @@ export class UploadFirmwareDialog extends ReactDialog {
{ - await this.boardsServiceProvider.reconciled; - this.lastConnectedBoard = { - selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard, - selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort, - }; - - if (!this.onBoardsConfigChanged) { - this.onBoardsConfigChanged = - this.boardsServiceProvider.onBoardsConfigChanged( - async ({ selectedBoard, selectedPort }) => { - if ( - typeof selectedBoard === 'undefined' || - typeof selectedPort === 'undefined' - ) + const { boardList } = this.boardsServiceProvider; + this.lastConnectedBoard = boardList.items[boardList.selectedIndex]; + if (!this.onBoardListDidChange) { + this.onBoardListDidChange = + this.boardsServiceProvider.onBoardListDidChange( + async (newBoardList) => { + const currentConnectedBoard = + newBoardList.items[newBoardList.selectedIndex]; + if (!currentConnectedBoard) { return; + } - // a board is plugged and it's different from the old connected board if ( - selectedBoard?.fqbn !== - this.lastConnectedBoard?.selectedBoard?.fqbn || - Port.keyOf(selectedPort) !== - (this.lastConnectedBoard.selectedPort - ? Port.keyOf(this.lastConnectedBoard.selectedPort) - : undefined) + !this.lastConnectedBoard || + boardListItemEquals( + currentConnectedBoard, + this.lastConnectedBoard + ) ) { - this.lastConnectedBoard = { - selectedBoard: selectedBoard, - selectedPort: selectedPort, - }; - this.onMonitorShouldResetEmitter.fire(); - } else { // a board is plugged and it's the same as prev, rerun "this.startMonitor" to // recreate the listener callback this.startMonitor(); + } else { + // a board is plugged and it's different from the old connected board + this.lastConnectedBoard = currentConnectedBoard; + this.onMonitorShouldResetEmitter.fire(); } } ); } - const { selectedBoard, selectedPort } = - this.boardsServiceProvider.boardsConfig; - if (!selectedBoard || !selectedBoard.fqbn || !selectedPort) return; + if (!this.lastConnectedBoard) { + return; + } + + const board = getInferredBoardOrBoard(this.lastConnectedBoard); + if (!board) { + return; + } try { this.clearVisibleNotification(); - await this.server().startMonitor(selectedBoard, selectedPort, settings); + await this.server().startMonitor( + board, + this.lastConnectedBoard.port, + settings + ); } catch (err) { const message = ApplicationError.is(err) ? err.message : String(err); this.previousNotificationId = this.notificationId(message); @@ -186,7 +189,10 @@ export class MonitorManagerProxyClientImpl } } - getCurrentSettings(board: Board, port: Port): Promise { + getCurrentSettings( + board: BoardIdentifier, + port: PortIdentifier + ): Promise { return this.server().getCurrentSettings(board, port); } diff --git a/arduino-ide-extension/src/browser/notification-center.ts b/arduino-ide-extension/src/browser/notification-center.ts index 96c938d25..e7d5c6676 100644 --- a/arduino-ide-extension/src/browser/notification-center.ts +++ b/arduino-ide-extension/src/browser/notification-center.ts @@ -14,13 +14,13 @@ import { NotificationServiceClient, NotificationServiceServer, } from '../common/protocol/notification-service'; -import { - AttachedBoardsChangeEvent, +import type { BoardsPackage, LibraryPackage, ConfigState, Sketch, ProgressMessage, + DetectedPorts, } from '../common/protocol'; import { FrontendApplicationStateService, @@ -61,8 +61,9 @@ export class NotificationCenter private readonly libraryDidUninstallEmitter = new Emitter<{ item: LibraryPackage; }>(); - private readonly attachedBoardsDidChangeEmitter = - new Emitter(); + private readonly detectedPortsDidChangeEmitter = new Emitter<{ + detectedPorts: DetectedPorts; + }>(); private readonly recentSketchesChangedEmitter = new Emitter<{ sketches: Sketch[]; }>(); @@ -82,7 +83,7 @@ export class NotificationCenter this.platformDidUninstallEmitter, this.libraryDidInstallEmitter, this.libraryDidUninstallEmitter, - this.attachedBoardsDidChangeEmitter + this.detectedPortsDidChangeEmitter ); readonly onDidReinitialize = this.didReinitializeEmitter.event; @@ -97,8 +98,7 @@ export class NotificationCenter readonly onPlatformDidUninstall = this.platformDidUninstallEmitter.event; readonly onLibraryDidInstall = this.libraryDidInstallEmitter.event; readonly onLibraryDidUninstall = this.libraryDidUninstallEmitter.event; - readonly onAttachedBoardsDidChange = - this.attachedBoardsDidChangeEmitter.event; + readonly onDetectedPortsDidChange = this.detectedPortsDidChangeEmitter.event; readonly onRecentSketchesDidChange = this.recentSketchesChangedEmitter.event; readonly onAppStateDidChange = this.onAppStateDidChangeEmitter.event; @@ -166,8 +166,8 @@ export class NotificationCenter this.libraryDidUninstallEmitter.fire(event); } - notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void { - this.attachedBoardsDidChangeEmitter.fire(event); + notifyDetectedPortsDidChange(event: { detectedPorts: DetectedPorts }): void { + this.detectedPortsDidChangeEmitter.fire(event); } notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void { diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index 7b390772f..f5c394603 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -173,7 +173,6 @@ export class MonitorWidget extends ReactWidget { private async startMonitor(): Promise { await this.appStateService.reachedState('ready'); - await this.boardsServiceProvider.reconciled; await this.syncSettings(); await this.monitorManagerProxy.startMonitor(); } diff --git a/arduino-ide-extension/src/browser/style/boards-config-dialog.css b/arduino-ide-extension/src/browser/style/boards-config-dialog.css index 90938ca70..ad18afc7f 100644 --- a/arduino-ide-extension/src/browser/style/boards-config-dialog.css +++ b/arduino-ide-extension/src/browser/style/boards-config-dialog.css @@ -172,20 +172,19 @@ div#select-board-dialog .selectBoardContainer .list .item.selected i { width: 210px; } -.arduino-boards-toolbar-item--protocol, +.arduino-boards-toolbar-item--protocol, .arduino-boards-dropdown-item--protocol { align-items: center; display: flex; font-size: 16px; } -.arduino-boards-toolbar-item--protocol , +.arduino-boards-toolbar-item--protocol, .arduino-boards-dropdown-item--protocol { color: var(--theia-arduino-toolbar-dropdown-label); } -.arduino-boards-toolbar-item-container - .arduino-boards-toolbar-item { +.arduino-boards-toolbar-item-container .arduino-boards-toolbar-item { display: flex; align-items: baseline; width: 100%; @@ -196,7 +195,10 @@ div#select-board-dialog .selectBoardContainer .list .item.selected i { } .arduino-boards-toolbar-item--label-connected { + font-family: 'Open Sans Bold'; + font-style: normal; font-weight: 700; + font-size: 14px; } .arduino-boards-toolbar-item-container .caret { @@ -208,6 +210,10 @@ div#select-board-dialog .selectBoardContainer .list .item.selected i { margin: -1px; z-index: 1; border: 1px solid var(--theia-arduino-toolbar-dropdown-border); + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + font-size: 12px; } .arduino-boards-dropdown-list:focus { @@ -230,20 +236,47 @@ div#select-board-dialog .selectBoardContainer .list .item.selected i { cursor: default; display: flex; font-size: var(--theia-ui-font-size1); - gap: 10px; justify-content: space-between; padding: 10px; } +.arduino-boards-dropdown-item--board-header { + display: flex; + align-items: center; +} + .arduino-boards-dropdown-item--label { overflow: hidden; flex: 1; } +/* Redefine default codicon size https://github.com/microsoft/vscode/commit/38cd0a377b7abef34fb07fe770fc633e68819ba6 */ +.arduino-boards-dropdown-item .codicon[class*='codicon-'] { + font-size: 14px; +} + +.arduino-boards-dropdown-item .p-TabBar-toolbar { + padding: 0px; + margin: 0px; + flex-direction: column; +} + +.arduino-boards-dropdown-item .p-TabBar-toolbar .item { + margin: 0px; +} + +.arduino-boards-dropdown-item .p-TabBar-toolbar .item .action-label { + padding: 0px; +} + .arduino-boards-dropdown-item--board-label { font-size: 14px; } +.arduino-boards-dropdown-item .arduino-boards-dropdown-item--protocol { + margin-right: 10px; +} + .arduino-boards-dropdown-item--port-label { font-size: 12px; } @@ -267,10 +300,6 @@ div#select-board-dialog .selectBoardContainer .list .item.selected i { color: var(--theia-arduino-toolbar-dropdown-iconSelected); } -.arduino-boards-dropdown-item .fa-check { - align-self: center; -} - .arduino-board-dropdown-footer { color: var(--theia-secondaryButton-foreground); border-top: 1px solid var(--theia-dropdown-border); diff --git a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.tsx b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.tsx index cc1ab7dd2..c354bfd77 100644 --- a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.tsx +++ b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.tsx @@ -3,14 +3,9 @@ import { DialogProps, } from '@theia/core/lib/browser/dialogs'; import { ReactDialog as TheiaReactDialog } from '@theia/core/lib/browser/dialogs/react-dialog'; -import { codiconArray, Message } from '@theia/core/lib/browser/widgets/widget'; -import { - Disposable, - DisposableCollection, -} from '@theia/core/lib/common/disposable'; +import { codiconArray } from '@theia/core/lib/browser/widgets/widget'; +import type { Message } from '@theia/core/shared/@phosphor/messaging'; import { inject, injectable } from '@theia/core/shared/inversify'; -import React from '@theia/core/shared/react'; -import { createRoot } from '@theia/core/shared/react-dom/client'; @injectable() export abstract class AbstractDialog extends TheiaAbstractDialog { @@ -18,7 +13,6 @@ export abstract class AbstractDialog extends TheiaAbstractDialog { @inject(DialogProps) protected override readonly props: DialogProps ) { super(props); - this.closeCrossNode.classList.remove(...codiconArray('close')); this.closeCrossNode.classList.add('fa', 'fa-close'); } @@ -26,38 +20,26 @@ export abstract class AbstractDialog extends TheiaAbstractDialog { @injectable() export abstract class ReactDialog extends TheiaReactDialog { - protected override onUpdateRequest(msg: Message): void { - // This is tricky to bypass the default Theia code. - // Otherwise, there is a warning when opening the dialog for the second time. - // You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it. - const disposables = new DisposableCollection(); - if (!this.isMounted) { - // toggle the `isMounted` logic for the time being of the super call so that the `createRoot` does not run - this.isMounted = true; - disposables.push(Disposable.create(() => (this.isMounted = false))); - } + private _isOnCloseRequestInProgress = false; - // Always unset the `contentNodeRoot` so there is no double update when calling super. - const restoreContentNodeRoot = this.contentNodeRoot; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (this.contentNodeRoot as any) = undefined; - disposables.push( - Disposable.create(() => (this.contentNodeRoot = restoreContentNodeRoot)) - ); + override dispose(): void { + // There is a bug in Theia, and the React component's `componentWillUnmount` will not be called, as the Theia widget is already disposed when closing and reopening a dialog. + // Widget lifecycle issue in Theia: https://github.com/eclipse-theia/theia/issues/12093 + // Bogus react widget lifecycle management PR: https://github.com/eclipse-theia/theia/pull/11687 + // Do not call super. Do not let the Phosphor widget to be disposed on dialog close. + if (this._isOnCloseRequestInProgress) { + // Do not let the widget dispose on close. + return; + } + super.dispose(); + } + protected override onCloseRequest(message: Message): void { + this._isOnCloseRequestInProgress = true; try { - super.onUpdateRequest(msg); + super.onCloseRequest(message); } finally { - disposables.dispose(); - } - - // Use the patched rendering. - if (!this.isMounted) { - this.contentNodeRoot = createRoot(this.contentNode); - // Resetting the prop is missing from the Theia code. - // https://github.com/eclipse-theia/theia/blob/v1.31.1/packages/core/src/browser/dialogs/react-dialog.tsx#L41-L47 - this.isMounted = true; + this._isOnCloseRequestInProgress = false; } - this.contentNodeRoot?.render(<>{this.render()}); } } diff --git a/arduino-ide-extension/src/common/nls.ts b/arduino-ide-extension/src/common/nls.ts index 29ab5604f..06c8baee7 100644 --- a/arduino-ide-extension/src/common/nls.ts +++ b/arduino-ide-extension/src/common/nls.ts @@ -1,5 +1,6 @@ import { nls } from '@theia/core/lib/common/nls'; +// TODO: rename constants: `Unknown` should be `unknownLabel`, change `Later` to `laterLabel`, etc. export const Unknown = nls.localize('arduino/common/unknown', 'Unknown'); export const Later = nls.localize('arduino/common/later', 'Later'); export const Updatable = nls.localize('arduino/common/updateable', 'Updatable'); diff --git a/arduino-ide-extension/src/common/protocol/board-list.ts b/arduino-ide-extension/src/common/protocol/board-list.ts new file mode 100644 index 000000000..2340e3224 --- /dev/null +++ b/arduino-ide-extension/src/common/protocol/board-list.ts @@ -0,0 +1,694 @@ +import type { Mutable } from '@theia/core/lib/common/types'; +import type { Defined } from '../types'; +import { naturalCompare } from '../utils'; +import { + BoardIdentifier, + boardIdentifierComparator, + boardIdentifierEquals, + BoardsConfig, + DetectedPort, + DetectedPorts, + emptyBoardsConfig, + findMatchingPortIndex, + isBoardIdentifier, + isDefinedBoardsConfig, + Port, + PortIdentifier, + portIdentifierEquals, + portProtocolComparator, + selectBoard, + unconfirmedBoard, + unknownBoard, + notConnected, + boardIdentifierLabel, +} from './boards-service'; + +/** + * Representation of a detected port with an optional board. + */ +export interface BoardListItem { + readonly port: Port; + readonly board?: BoardIdentifier; +} + +/** + * Representation of a detected port with multiple discovered boards on the same port. For example Arduino Nano ESP32 from `esp32:esp32:nano_nora` and `arduino:esp32:nano_nora`. + * If multiple boards are detected, but the board names are the same, the `board` will be the `first` element of the `boards` array. + * If multiple boards are detected, but the board names are not identical, the `board` will be missing. + */ +export interface MultiBoardsBoardListItem extends BoardListItem { + readonly boards: readonly BoardIdentifier[]; +} + +function findUniqueBoardName( + item: MultiBoardsBoardListItem +): string | undefined { + const distinctNames = new Set(item.boards.map(({ name }) => name)); + if (distinctNames.size === 1) { + const name = Array.from(distinctNames.keys()).shift(); + if (name) { + return name; + } + } + return undefined; +} + +export function isMultiBoardsBoardListItem( + arg: unknown +): arg is MultiBoardsBoardListItem { + return ( + isBoardListItem(arg) && + (arg).boards !== undefined && + Array.isArray((arg).boards) && + Boolean((arg).boards.length) && + (arg).boards.every(isBoardIdentifier) + ); +} + +/** + * Base inferred board list item type. + * The the type of the inferred board can be: + * - manually specified board for a detected port where no boards were discovered, + * - the board has been overridden for detected port discovered board pair. + */ +export type InferredBoardListItem = + | ManuallySelectedBoardListItem + | BoardOverriddenBoardListItem; + +/** + * No boards have been discovered for a detected port, it has been manually selected by the user. + */ +export interface ManuallySelectedBoardListItem extends BoardListItem { + readonly inferredBoard: BoardIdentifier; + readonly type: 'manually-selected'; +} + +/** + * One or more boards have been discovered for a detected port, but the board has been overridden by a manual action. + */ +export interface BoardOverriddenBoardListItem extends BoardListItem { + readonly inferredBoard: BoardIdentifier; + readonly board: BoardIdentifier; + readonly type: 'board-overridden'; +} + +export function isBoardListItem(arg: unknown): arg is BoardListItem { + return ( + Boolean(arg) && + typeof arg === 'object' && + (arg).port !== undefined && + Port.is((arg).port) && + ((arg).board === undefined || + ((arg).board !== undefined && + isBoardIdentifier((arg).board))) + ); +} + +export function boardListItemEquals( + left: BoardListItem, + right: BoardListItem +): boolean { + if (portIdentifierEquals(left.port, right.port)) { + const leftBoard = getBoardOrInferredBoard(left); + const rightBoard = getBoardOrInferredBoard(right); + if (boardIdentifierEquals(leftBoard, rightBoard)) { + const leftInferredBoard = getInferredBoardOrBoard(left); + const rightInferredBoard = getInferredBoardOrBoard(right); + return boardIdentifierEquals(leftInferredBoard, rightInferredBoard); + } + } + return false; +} + +export interface BoardListItemWithBoard extends BoardListItem { + readonly board: BoardIdentifier; +} + +function getBoardOrInferredBoard( + item: BoardListItem +): BoardIdentifier | undefined { + let board: BoardIdentifier | undefined = undefined; + board = item.board; + if (!board && isInferredBoardListItem(item)) { + board = item.inferredBoard; + } + return board; +} + +export function getInferredBoardOrBoard( + item: BoardListItem +): BoardIdentifier | undefined { + if (isInferredBoardListItem(item)) { + return item.inferredBoard; + } + return item.board; +} + +export function isInferredBoardListItem( + arg: unknown +): arg is InferredBoardListItem { + return ( + isBoardListItem(arg) && + (arg).type !== undefined && + isInferenceType((arg).type) && + (arg).inferredBoard !== undefined && + isBoardIdentifier((arg).inferredBoard) + ); +} + +/** + * Stores historical info about boards manually specified for detected boards. The key are generated with `Port#keyOf`. + */ +export type BoardListHistory = Readonly>; + +export function isBoardListHistory(arg: unknown): arg is BoardListHistory { + return ( + Boolean(arg) && + typeof arg === 'object' && + Object.entries(arg).every(([, value]) => isBoardIdentifier(value)) + ); +} + +const inferenceTypeLiterals = [ + /** + * The user has manually selected the board (FQBN) for the detected port of a 3rd party board (no matching boards were detected by the CLI for the port) + */ + 'manually-selected', + /** + * The user has manually edited the detected FQBN of a recognized board from a detected port (there are matching boards for a detected port, but the user decided to use another FQBN) + */ + 'board-overridden', +] as const; +type InferenceType = (typeof inferenceTypeLiterals)[number]; +function isInferenceType(arg: unknown): arg is InferenceType { + return ( + typeof arg === 'string' && + inferenceTypeLiterals.includes(arg) + ); +} + +/** + * Compare precedence: + * 1. `BoardListItem#port#protocol`: `'serial'`, `'network'`, then natural compare of the `protocol` string. + * 1. `BoardListItem`s with a `board` comes before items without a `board`. + * 1. `BoardListItem#board`: + * 1. Items with `'arduino'` vendor ID in the `fqbn` come before other vendors. + * 1. Natural compare of the `name`. + * 1. If the `BoardListItem`s do not have a `board` property: + * 1. Ambiguous boards come before no boards. + * 1. `BoardListItem#port#address` natural compare is the fallback. + */ +function boardListItemComparator( + left: BoardListItem, + right: BoardListItem +): number { + // sort by port protocol + let result = portProtocolComparator(left.port, right.port); + if (result) { + return result; + } + + // compare by board + result = boardIdentifierComparator( + getBoardOrInferredBoard(left), + getBoardOrInferredBoard(right) + ); + if (result) { + return result; + } + + // detected ports with multiple discovered boards come before any other unknown items + if (isMultiBoardsBoardListItem(left) && !isMultiBoardsBoardListItem(right)) { + return -1; + } + if (!isMultiBoardsBoardListItem(left) && !isMultiBoardsBoardListItem(right)) { + return 1; + } + // ambiguous boards with a unique board name comes first than other ambiguous ones + if (isMultiBoardsBoardListItem(left) && isMultiBoardsBoardListItem(right)) { + const leftUniqueName = findUniqueBoardName(left); + const rightUniqueName = findUniqueBoardName(right); + if (leftUniqueName && !rightUniqueName) { + return -1; + } + if (!leftUniqueName && rightUniqueName) { + return 1; + } + if (leftUniqueName && rightUniqueName) { + return naturalCompare(leftUniqueName, rightUniqueName); + } + } + + // fallback compare based on the address + return naturalCompare(left.port.address, right.port.address); +} + +/** + * What is shown in the UI for the entire board list. + */ +export interface BoardListLabels { + readonly boardLabel: string; + readonly portProtocol: string | undefined; + readonly tooltip: string; + /** + * The client's board+port selection matches with one of the board list items. + */ + readonly selected: boolean; +} + +function createBoardListLabels( + boardsConfig: BoardsConfig, + allPorts: readonly DetectedPort[], + selectedItem: BoardListItem | undefined +): BoardListLabels { + const { selectedBoard, selectedPort } = boardsConfig; + const boardLabel = selectedBoard?.name || selectBoard; + let tooltip = ''; + if (!selectedBoard && !selectedPort) { + tooltip = selectBoard; + } else { + if (selectedBoard) { + tooltip += boardIdentifierLabel(selectedBoard); + } + if (selectedPort) { + if (tooltip) { + tooltip += '\n'; + } + tooltip += selectedPort.address; + const index = findMatchingPortIndex(selectedPort, allPorts); + if (index < 0) { + tooltip += ` ${notConnected}`; + } + } + } + return { + boardLabel, + portProtocol: selectedBoard ? selectedPort?.protocol : undefined, + tooltip, + selected: Boolean(selectedItem), + }; +} + +/** + * What is show in the UI for a particular board with all its refinements, fallbacks, and tooltips. + */ +export interface BoardListItemLabels { + readonly boardLabel: string; + readonly boardLabelWithFqbn: string; + readonly portLabel: string; + readonly portProtocol: string; + readonly tooltip: string; +} + +export interface BoardListItemUI extends BoardListItem { + readonly labels: BoardListItemLabels; + readonly defaultAction: BoardListItemAction; + readonly otherActions: Readonly<{ + edit?: EditBoardsConfigAction; + revert?: SelectBoardsConfigAction; + }>; +} + +function createBoardListItemLabels(item: BoardListItem): BoardListItemLabels { + const { port } = item; + const portLabel = port.address; + const portProtocol = port.protocol; + let board = item.board; // use default board label if any + if (isInferredBoardListItem(item)) { + board = item.inferredBoard; // inferred board overrides any discovered boards + } + // if the board is still missing, maybe it's ambiguous + if (!board && isMultiBoardsBoardListItem(item)) { + const name = + // get a unique board name + findUniqueBoardName(item) ?? + // or fall back to something else than unknown board + unconfirmedBoard; + board = { name, fqbn: undefined }; + } + const boardLabel = board?.name ?? unknownBoard; + let boardLabelWithFqbn = boardLabel; + if (board?.fqbn) { + boardLabelWithFqbn += ` (${board.fqbn})`; + } + return { + boardLabel, + boardLabelWithFqbn, + portLabel, + portProtocol, + tooltip: `${boardLabelWithFqbn}\n${portLabel}`, + }; +} + +/** + * A list of boards discovered by the Arduino CLI. With the `board list --watch` gRPC equivalent command, + * the CLI provides a `1..*` mapping between a port and the matching boards list. This type inverts the mapping + * and makes a `1..1` association between a board identifier and the port it belongs to. + */ +export interface BoardList { + readonly labels: BoardListLabels; + /** + * All detected ports with zero to many boards and optional inferred information based on historical selection/usage. + */ + readonly items: readonly BoardListItemUI[]; + /** + * A snapshot of the board and port configuration this board list has been initialized with. + */ + readonly boardsConfig: Readonly; + + /** + * Index of the board+port item that is currently "selected". A board list item is selected, if matches the board+port combination of `boardsConfig`. + */ + readonly selectedIndex: number; + + /** + * Contains all boards recognized from the detected port, and an optional unrecognized one that is derived from the detected port and the `initParam#selectedBoard`. + */ + readonly boards: readonly (BoardListItemWithBoard | InferredBoardListItem)[]; + + /** + * If `predicate` is not defined, no ports are filtered. + */ + ports( + predicate?: (detectedPort: DetectedPort) => boolean + ): readonly DetectedPort[] & Readonly<{ matchingIndex: number }>; + + /** + * Sugar for `#ports` with additional grouping based on the port `protocol`. + */ + portsGroupedByProtocol(): Readonly< + Record<'serial' | 'network' | string, ReturnType> + >; + + /** + * For dumping the current state of board list for debugging purposes. + */ + toString(): string; +} + +export type SelectBoardsConfigActionParams = Readonly>; +export interface SelectBoardsConfigAction { + readonly type: 'select-boards-config'; + readonly params: SelectBoardsConfigActionParams; +} +export interface EditBoardsConfigActionParams { + readonly portToSelect?: PortIdentifier; + readonly boardToSelect?: BoardIdentifier; + readonly query?: string; + readonly searchSet?: readonly BoardIdentifier[]; +} +export interface EditBoardsConfigAction { + readonly type: 'edit-boards-config'; + readonly params: EditBoardsConfigActionParams; +} +export type BoardListItemAction = + | SelectBoardsConfigAction + | EditBoardsConfigAction; + +export function createBoardList( + detectedPorts: DetectedPorts, + boardsConfig: Readonly = emptyBoardsConfig(), + boardListHistory: BoardListHistory = {} +): BoardList { + const items: BoardListItemUI[] = []; + for (const detectedPort of Object.values(detectedPorts)) { + const item = createBoardListItemUI(detectedPort, boardListHistory); + items.push(item); + } + items.sort(boardListItemComparator); + const selectedIndex = findSelectedIndex(boardsConfig, items); + const boards = collectBoards(items); + const allPorts = collectPorts(items, detectedPorts); + const labels = createBoardListLabels( + boardsConfig, + allPorts, + items[selectedIndex] + ); + return { + labels, + items, + boardsConfig, + boards, + selectedIndex, + ports(predicate?: (detectedPort: DetectedPort) => boolean) { + return filterPorts(allPorts, boardsConfig.selectedPort, predicate); + }, + portsGroupedByProtocol() { + const _allPorts = filterPorts(allPorts, boardsConfig.selectedPort); + return portsGroupedByProtocol(_allPorts); + }, + toString() { + return JSON.stringify( + { + labels, + detectedPorts, + boardsConfig, + items, + selectedIndex, + boardListHistory, + }, + null, + 2 + ); + }, + }; +} + +function portsGroupedByProtocol( + allPorts: ReturnType +): ReturnType { + const result: Record = {}; + for (const detectedPort of allPorts) { + const protocol = detectedPort.port.protocol; + if (!result[protocol]) { + result[protocol] = Object.assign([], { + matchingIndex: -1, + }); + } + const portsOnProtocol = result[protocol]; + portsOnProtocol.push(detectedPort); + } + const matchItem = allPorts[allPorts.matchingIndex]; + // the cached match index is per all ports. Here, IDE2 needs to adjust the match index per grouped protocol + if (matchItem) { + const matchProtocol = matchItem.port.protocol; + const matchPorts = result[matchProtocol]; + matchPorts.matchingIndex = matchPorts.indexOf(matchItem); + } + return result; +} + +function filterPorts( + allPorts: readonly DetectedPort[], + selectedPort: PortIdentifier | undefined, + predicate: (detectedPort: DetectedPort) => boolean = () => true +): ReturnType { + const ports = allPorts.filter(predicate); + const matchingIndex = findMatchingPortIndex(selectedPort, ports); + return Object.assign(ports, { matchingIndex }); +} + +function collectPorts( + items: readonly BoardListItem[], + detectedPorts: DetectedPorts +): DetectedPort[] { + const allPorts: DetectedPort[] = []; + // to keep the order or the detected ports + const visitedPortKeys = new Set(); + for (let i = 0; i < items.length; i++) { + const { port } = items[i]; + const portKey = Port.keyOf(port); + if (!visitedPortKeys.has(portKey)) { + visitedPortKeys.add(portKey); + const detectedPort = detectedPorts[portKey]; + if (detectedPort) { + allPorts.push(detectedPort); + } + } + } + return allPorts; +} + +function collectBoards( + items: readonly BoardListItem[] +): readonly (BoardListItemWithBoard | InferredBoardListItem)[] { + const boards: (BoardListItemWithBoard | InferredBoardListItem)[] = []; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (isInferredBoardListItem(item)) { + boards.push(item); + } else if (item.board?.fqbn) { + boards.push(>item); + } + } + return boards; +} + +function findSelectedIndex( + boardsConfig: BoardsConfig, + items: readonly BoardListItem[] +): number { + if (!isDefinedBoardsConfig(boardsConfig)) { + return -1; + } + const length = items.length; + const { selectedPort, selectedBoard } = boardsConfig; + const portKey = Port.keyOf(selectedPort); + // find the exact match of the board and port combination + for (let index = 0; index < length; index++) { + const item = items[index]; + const { board, port } = item; + if (!board) { + continue; + } + if ( + Port.keyOf(port) === portKey && + boardIdentifierEquals(board, selectedBoard) + ) { + return index; + } + } + // find match from inferred board + for (let index = 0; index < length; index++) { + const item = items[index]; + if (!isInferredBoardListItem(item)) { + continue; + } + const { inferredBoard, port } = item; + if ( + Port.keyOf(port) === portKey && + boardIdentifierEquals(inferredBoard, boardsConfig.selectedBoard) + ) { + return index; + } + } + return -1; +} + +function createBoardListItemUI( + detectedPort: DetectedPort, + boardListHistory: BoardListHistory +): BoardListItemUI { + const item = createBoardListItem(detectedPort, boardListHistory); + const labels = createBoardListItemLabels(item); + const defaultAction = createDefaultAction(item); + const otherActions = createOtherActions(item); + return Object.assign(item, { labels, defaultAction, otherActions }); +} + +function createBoardListItem( + detectedPort: DetectedPort, + boardListHistory: BoardListHistory +): BoardListItem { + const { port, boards } = detectedPort; + // boards with arduino vendor should come first + boards?.sort(boardIdentifierComparator); + const portKey = Port.keyOf(port); + const inferredBoard = boardListHistory[portKey]; + if (!boards?.length) { + let unknownItem: BoardListItem | InferredBoardListItem = { port }; + // Infer unrecognized boards from the history + if (inferredBoard) { + unknownItem = { + ...unknownItem, + inferredBoard, + type: 'manually-selected', + }; + } + return unknownItem; + } else if (boards.length === 1) { + const board = boards[0]; + let detectedItem: BoardListItemWithBoard | InferredBoardListItem = { + port, + board, + }; + if ( + inferredBoard && + // ignore the inferred item if it's the same as the discovered board + !boardIdentifierEquals(board, inferredBoard) + ) { + detectedItem = { + ...detectedItem, + inferredBoard, + type: 'board-overridden', + }; + } + return detectedItem; + } else { + let ambiguousItem: MultiBoardsBoardListItem | InferredBoardListItem = { + port, + boards, + }; + if (inferredBoard) { + ambiguousItem = { + ...ambiguousItem, + inferredBoard, + type: 'manually-selected', + }; + } + return ambiguousItem; + } +} + +function createDefaultAction(item: BoardListItem): BoardListItemAction { + if (isInferredBoardListItem(item)) { + return createSelectAction({ + selectedBoard: item.inferredBoard, + selectedPort: item.port, + }); + } + if (item.board) { + return createSelectAction({ + selectedBoard: item.board, + selectedPort: item.port, + }); + } + return createEditAction(item); +} + +function createOtherActions( + item: BoardListItem +): BoardListItemUI['otherActions'] { + if (isInferredBoardListItem(item)) { + const edit = createEditAction(item); + if (item.type === 'board-overridden') { + const revert = createSelectAction({ + selectedBoard: item.board, + selectedPort: item.port, + }); + return { edit, revert }; + } + return { edit }; + } + return {}; +} + +function createSelectAction( + params: SelectBoardsConfigActionParams +): SelectBoardsConfigAction { + return { + type: 'select-boards-config', + params, + }; +} + +function createEditAction(item: BoardListItem): EditBoardsConfigAction { + const params: Mutable = { + portToSelect: item.port, + }; + if (isMultiBoardsBoardListItem(item)) { + const uniqueBoardName = findUniqueBoardName(item); + params.query = uniqueBoardName ?? ''; + params.searchSet = item.boards; + } else if (isInferredBoardListItem(item)) { + params.query = item.inferredBoard.name; + } else if (item.board) { + params.query = item.board.name; + } else { + params.query = ''; + } + return { + type: 'edit-boards-config', + params, + }; +} diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index 7e2f77555..c485e6704 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -1,8 +1,6 @@ -import { naturalCompare } from './../utils'; -import { Searchable } from './searchable'; -import { Installable } from './installable'; -import { ArduinoComponent } from './arduino-component'; import { nls } from '@theia/core/lib/common/nls'; +import type { MaybePromise } from '@theia/core/lib/common/types'; +import type URI from '@theia/core/lib/common/uri'; import { All, Contributed, @@ -10,131 +8,46 @@ import { Type as TypeLabel, Updatable, } from '../nls'; -import URI from '@theia/core/lib/common/uri'; -import { MaybePromise } from '@theia/core/lib/common/types'; - -export type AvailablePorts = Record]>; -export namespace AvailablePorts { - export function groupByProtocol( - availablePorts: AvailablePorts - ): Map { - const grouped = new Map(); - for (const portID of Object.keys(availablePorts)) { - const [port, boards] = availablePorts[portID]; - let ports = grouped.get(port.protocol); - if (!ports) { - ports = {} as AvailablePorts; - } - ports[portID] = [port, boards]; - grouped.set(port.protocol, ports); - } - return grouped; - } - export function split( - state: AvailablePorts - ): Readonly<{ boards: Board[]; ports: Port[] }> { - const availablePorts: Port[] = []; - const attachedBoards: Board[] = []; - for (const key of Object.keys(state)) { - const [port, boards] = state[key]; - availablePorts.push(port); - attachedBoards.push(...boards); - } - return { - boards: attachedBoards, - ports: availablePorts, - }; - } -} +import type { Defined } from '../types'; +import { naturalCompare } from './../utils'; +import type { ArduinoComponent } from './arduino-component'; +import type { BoardList } from './board-list'; +import { Installable } from './installable'; +import { Searchable } from './searchable'; -export interface AttachedBoardsChangeEvent { - readonly oldState: Readonly<{ boards: Board[]; ports: Port[] }>; - readonly newState: Readonly<{ boards: Board[]; ports: Port[] }>; - readonly uploadInProgress: boolean; +export interface DetectedPort { + readonly port: Port; + readonly boards?: Pick[]; } -export namespace AttachedBoardsChangeEvent { - export function isEmpty(event: AttachedBoardsChangeEvent): boolean { - const { detached, attached } = diff(event); - return ( - !!detached.boards.length && - !!detached.ports.length && - !!attached.boards.length && - !!attached.ports.length - ); - } - export function toString(event: AttachedBoardsChangeEvent): string { - const rows: string[] = []; - if (!isEmpty(event)) { - const { attached, detached } = diff(event); - const visitedAttachedPorts: Port[] = []; - const visitedDetachedPorts: Port[] = []; - for (const board of attached.boards) { - const port = board.port ? ` on ${Port.toString(board.port)}` : ''; - rows.push(` - Attached board: ${Board.toString(board)}${port}`); - if (board.port) { - visitedAttachedPorts.push(board.port); - } - } - for (const board of detached.boards) { - const port = board.port ? ` from ${Port.toString(board.port)}` : ''; - rows.push(` - Detached board: ${Board.toString(board)}${port}`); - if (board.port) { - visitedDetachedPorts.push(board.port); - } - } - for (const port of attached.ports) { - if (!visitedAttachedPorts.find((p) => Port.sameAs(port, p))) { - rows.push(` - New port is available on ${Port.toString(port)}`); - } - } - for (const port of detached.ports) { - if (!visitedDetachedPorts.find((p) => Port.sameAs(port, p))) { - rows.push(` - Port is no longer available on ${Port.toString(port)}`); - } - } - } - return rows.length ? rows.join('\n') : 'No changes.'; +export function findMatchingPortIndex( + toFind: PortIdentifier | undefined, + ports: readonly DetectedPort[] | readonly Port[] +): number { + if (!toFind) { + return -1; } + const toFindPortKey = Port.keyOf(toFind); + return ports.findIndex((port) => Port.keyOf(port) === toFindPortKey); +} - export function diff(event: AttachedBoardsChangeEvent): Readonly<{ - attached: { - boards: Board[]; - ports: Port[]; - }; - detached: { - boards: Board[]; - ports: Port[]; - }; - }> { - // In `lefts` AND not in `rights`. - const diff = ( - lefts: T[], - rights: T[], - sameAs: (left: T, right: T) => boolean - ) => { - return lefts.filter( - (left) => rights.findIndex((right) => sameAs(left, right)) === -1 - ); - }; - const { boards: newBoards } = event.newState; - const { boards: oldBoards } = event.oldState; - const { ports: newPorts } = event.newState; - const { ports: oldPorts } = event.oldState; - const boardSameAs = (left: Board, right: Board) => - Board.sameAs(left, right); - const portSameAs = (left: Port, right: Port) => Port.sameAs(left, right); - return { - detached: { - boards: diff(oldBoards, newBoards, boardSameAs), - ports: diff(oldPorts, newPorts, portSameAs), - }, - attached: { - boards: diff(newBoards, oldBoards, boardSameAs), - ports: diff(newPorts, oldPorts, portSameAs), - }, - }; +/** + * The closest representation what the Arduino CLI detects with the `board list --watch` gRPC equivalent. + * The keys are unique identifiers generated from the port object (via `Port#keyOf`). + * The values are the detected ports with all their optional `properties` and matching board list. + */ +export type DetectedPorts = Readonly>; + +export function resolveDetectedPort( + port: PortIdentifier, + detectedPorts: DetectedPorts +): Port | undefined { + const portKey = Port.keyOf(port); + const detectedPort = detectedPorts[portKey]; + if (detectedPort) { + return detectedPort.port; } + return undefined; } export const BoardsServicePath = '/services/boards-service'; @@ -152,14 +65,17 @@ export interface BoardsService */ skipPostInstall?: boolean; }): Promise; - getState(): Promise; + getDetectedPorts(): Promise; getBoardDetails(options: { fqbn: string }): Promise; - getBoardPackage(options: { id: string }): Promise; + getBoardPackage(options: { + id: string /* TODO: change to PlatformIdentifier type? */; + }): Promise; getContainerBoardPackage(options: { fqbn: string; }): Promise; searchBoards({ query }: { query?: string }): Promise; getInstalledBoards(): Promise; + getInstalledPlatforms(): Promise; getBoardUserFields(options: { fqbn: string; protocol: string; @@ -180,7 +96,7 @@ export namespace BoardSearch { 'Partner', 'Arduino@Heart', ] as const; - export type Type = typeof TypeLiterals[number]; + export type Type = (typeof TypeLiterals)[number]; export namespace Type { export function is(arg: unknown): arg is Type { return typeof arg === 'string' && TypeLiterals.includes(arg as Type); @@ -252,9 +168,9 @@ export namespace Port { export namespace Properties { export function create( properties: [string, string][] | undefined - ): Properties { - if (!properties) { - return {}; + ): Properties | undefined { + if (!properties || !properties.length) { + return undefined; } return properties.reduce((acc, curr) => { const [key, value] = curr; @@ -282,10 +198,13 @@ export namespace Port { } /** - * Key is the combination of address and protocol formatted like `'${address}|${protocol}'` used to uniquely identify a port. + * Key is the combination of address and protocol formatted like `'arduino+${protocol}://${address}'` used to uniquely identify a port. */ - export function keyOf({ address, protocol }: Port): string { - return `${address}|${protocol}`; + export function keyOf(port: PortIdentifier | Port | DetectedPort): string { + if (isPortIdentifier(port)) { + return `arduino+${port.protocol}://${port.address}`; + } + return keyOf(port.port); } export function toString({ addressLabel, protocolLabel }: Port): string { @@ -297,16 +216,8 @@ export namespace Port { // 1. Serial // 2. Network // 3. Other protocols - if (left.protocol === 'serial' && right.protocol !== 'serial') { - return -1; - } else if (left.protocol !== 'serial' && right.protocol === 'serial') { - return 1; - } else if (left.protocol === 'network' && right.protocol !== 'network') { - return -1; - } else if (left.protocol !== 'network' && right.protocol === 'network') { - return 1; - } - return naturalCompare(left.address!, right.address!); + const priorityResult = portProtocolComparator(left, right); + return priorityResult || naturalCompare(left.address, right.address); } export function sameAs( @@ -324,35 +235,28 @@ export namespace Port { /** * All ports with `'serial'` or `'network'` `protocol`, or any other port `protocol` that has at least one recognized board connected to. */ - export function visiblePorts( - boardsHaystack: ReadonlyArray - ): (port: Port) => boolean { - return (port: Port) => { - if (port.protocol === 'serial' || port.protocol === 'network') { - // Allow all `serial` and `network` boards. - // IDE2 must support better label for unrecognized `network` boards: https://github.com/arduino/arduino-ide/issues/1331 - return true; - } - // All other ports with different protocol are - // only shown if there is a recognized board - // connected - for (const board of boardsHaystack) { - if (board.port?.address === port.address) { - return true; - } - } - return false; - }; + export function isVisiblePort(detectedPort: DetectedPort): boolean { + const protocol = detectedPort.port.protocol; + if (protocol === 'serial' || protocol === 'network') { + // Allow all `serial` and `network` boards. + // IDE2 must support better label for unrecognized `network` boards: https://github.com/arduino/arduino-ide/issues/1331 + return true; + } + // All other ports with different protocol are + // only shown if there is a recognized board + // connected + return Boolean(detectedPort?.boards?.length); } export namespace Protocols { + // IDE2 does not want to handle any other port protocols in a special way export const KnownProtocolLiterals = ['serial', 'network'] as const; - export type KnownProtocol = typeof KnownProtocolLiterals[number]; + export type KnownProtocol = (typeof KnownProtocolLiterals)[number]; export namespace KnownProtocol { export function is(protocol: unknown): protocol is KnownProtocol { return ( typeof protocol === 'string' && - KnownProtocolLiterals.indexOf(protocol as KnownProtocol) >= 0 + KnownProtocolLiterals.includes(protocol as KnownProtocol) ); } } @@ -377,29 +281,12 @@ export namespace BoardsPackage { export function equals(left: BoardsPackage, right: BoardsPackage): boolean { return left.id === right.id; } - - export function contains( - selectedBoard: Board, - { id, boards }: BoardsPackage - ): boolean { - if (boards.some((board) => Board.sameAs(board, selectedBoard))) { - return true; - } - if (selectedBoard.fqbn) { - const [platform, architecture] = selectedBoard.fqbn.split(':'); - if (platform && architecture) { - return `${platform}:${architecture}` === id; - } - } - return false; - } } -export interface Board { - readonly name: string; - readonly fqbn?: string; - readonly port?: Port; -} +/** + * @deprecated user `BoardIdentifier` instead. + */ +export type Board = BoardIdentifier; export interface BoardUserField { readonly toolId: string; @@ -411,14 +298,19 @@ export interface BoardUserField { export interface BoardWithPackage extends Board { readonly packageName: string; - readonly packageId: string; + readonly packageId: PlatformIdentifier; readonly manuallyInstalled: boolean; } export namespace BoardWithPackage { - export function is( - board: Board & Partial<{ packageName: string; packageId: string }> - ): board is BoardWithPackage { - return !!board.packageId && !!board.packageName; + export function is(arg: unknown): arg is BoardWithPackage { + return ( + isBoardIdentifier(arg) && + (arg).packageName !== undefined && + typeof (arg).packageName === 'string' && + isPlatformIdentifier((arg).packageId) && + (arg).manuallyInstalled !== undefined && + typeof (arg).manuallyInstalled === 'boolean' + ); } } @@ -456,18 +348,6 @@ export interface ConfigOption { readonly values: ConfigValue[]; } export namespace ConfigOption { - export function is(arg: any): arg is ConfigOption { - return ( - !!arg && - 'option' in arg && - 'label' in arg && - 'values' in arg && - typeof arg['option'] === 'string' && - typeof arg['label'] === 'string' && - Array.isArray(arg['values']) - ); - } - /** * Appends the configuration options to the `fqbn` argument. * Throws an error if the `fqbn` does not have the `segment(':'segment)*` format. @@ -555,24 +435,15 @@ export namespace Board { return left.name === right.name && left.fqbn === right.fqbn; } - export function hardwareIdEquals(left: Board, right: Board): boolean { - if (left.port && right.port) { - const { hardwareId: leftHardwareId } = left.port; - const { hardwareId: rightHardwareId } = right.port; - - if (leftHardwareId && rightHardwareId) { - return leftHardwareId === rightHardwareId; - } - } - - return false; - } - - export function sameAs(left: Board, right: string | Board): boolean { + export function sameAs( + left: BoardIdentifier, + right: string | BoardIdentifier + ): boolean { // How to associate a selected board with one of the available cores: https://typefox.slack.com/archives/CJJHJCJSJ/p1571142327059200 // 1. How to use the FQBN if any and infer the package ID from it: https://typefox.slack.com/archives/CJJHJCJSJ/p1571147549069100 // 2. How to trim the `/Genuino` from the name: https://arduino.slack.com/archives/CJJHJCJSJ/p1571146951066800?thread_ts=1571142327.059200&cid=CJJHJCJSJ - const other = typeof right === 'string' ? { name: right } : right; + const other: BoardIdentifier = + typeof right === 'string' ? { name: right, fqbn: undefined } : right; if (left.fqbn && other.fqbn) { return left.fqbn === other.fqbn; } @@ -594,7 +465,7 @@ export namespace Board { } export function toString( - board: Board, + board: BoardIdentifier, options: { useFqbn: boolean } = { useFqbn: true } ): string { const fqbn = @@ -607,14 +478,15 @@ export namespace Board { selected: boolean; missing: boolean; packageName: string; - packageId: string; + packageId: PlatformIdentifier; details?: string; manuallyInstalled: boolean; }>; export function decorateBoards( - selectedBoard: Board | undefined, + selectedBoard: BoardIdentifier | BoardWithPackage | undefined, boards: Array ): Array { + let foundSelected = false; // Board names are not unique. We show the corresponding core name as a detail. // https://github.com/arduino/arduino-cli/pull/294#issuecomment-513764948 const distinctBoardNames = new Map(); @@ -622,21 +494,42 @@ export namespace Board { const counter = distinctBoardNames.get(name) || 0; distinctBoardNames.set(name, counter + 1); } - - // Due to the non-unique board names, we have to check the package name as well. - const selected = (board: BoardWithPackage) => { - if (!!selectedBoard) { - if (Board.equals(board, selectedBoard)) { - if ('packageName' in selectedBoard) { - return board.packageName === (selectedBoard as any).packageName; - } - if ('packageId' in selectedBoard) { - return board.packageId === (selectedBoard as any).packageId; + const selectedBoardPackageId = selectedBoard + ? createPlatformIdentifier(selectedBoard) + : undefined; + const selectedBoardFqbn = selectedBoard?.fqbn; + // Due to the non-unique board names, IDE2 has to check the package name when boards are not installed and the FQBN is absent. + const isSelected = (board: BoardWithPackage) => { + if (!selectedBoard) { + return false; + } + if (foundSelected) { + return false; + } + let selected = false; + if (board.fqbn && selectedBoardFqbn) { + if (boardIdentifierEquals(board, selectedBoard)) { + selected = true; + } + } + if (!selected) { + if (board.name === selectedBoard.name) { + if (selectedBoardPackageId) { + const boardPackageId = createPlatformIdentifier(board); + if (boardPackageId) { + if ( + platformIdentifierEquals(boardPackageId, selectedBoardPackageId) + ) { + selected = true; + } + } } - return true; } } - return false; + if (selected) { + foundSelected = true; + } + return selected; }; return boards.map((board) => ({ ...board, @@ -644,7 +537,7 @@ export namespace Board { (distinctBoardNames.get(board.name) || 0) > 1 ? ` - ${board.packageName}` : undefined, - selected: selected(board), + selected: isSelected(board), missing: !installed(board), })); } @@ -674,11 +567,267 @@ export function sanitizeFqbn(fqbn: string | undefined): string | undefined { return `${vendor}:${arch}:${id}`; } -export interface BoardConfig { - selectedBoard?: Board; - selectedPort?: Port; +export type PlatformIdentifier = Readonly<{ vendorId: string; arch: string }>; +export function createPlatformIdentifier( + board: BoardWithPackage +): PlatformIdentifier; +export function createPlatformIdentifier( + board: BoardIdentifier +): PlatformIdentifier | undefined; +export function createPlatformIdentifier( + fqbn: string +): PlatformIdentifier | undefined; +export function createPlatformIdentifier( + arg: BoardIdentifier | BoardWithPackage | string +): PlatformIdentifier | undefined { + if (BoardWithPackage.is(arg)) { + return arg.packageId; + } + const toSplit = typeof arg === 'string' ? arg : arg.fqbn; + if (toSplit) { + const [vendorId, arch] = toSplit.split(':'); + if (vendorId && arch) { + return { vendorId, arch }; + } + } + return undefined; +} + +export function isPlatformIdentifier(arg: unknown): arg is PlatformIdentifier { + return ( + Boolean(arg) && + typeof arg === 'object' && + (arg).vendorId !== undefined && + typeof (arg).vendorId === 'string' && + (arg).arch !== undefined && + typeof (arg).arch === 'string' + ); +} + +export function serializePlatformIdentifier({ + vendorId, + arch, +}: PlatformIdentifier): string { + return `${vendorId}:${arch}`; +} + +export function platformIdentifierEquals( + left: PlatformIdentifier, + right: PlatformIdentifier +) { + return left.vendorId === right.vendorId && left.arch === right.arch; +} + +/** + * Bare minimum information to identify port. + */ +export type PortIdentifier = Readonly>; + +export function portIdentifierEquals( + left: PortIdentifier | undefined, + right: PortIdentifier | undefined +): boolean { + if (!left) { + return !right; + } + if (!right) { + return !left; + } + return left.protocol === right.protocol && left.address === right.address; } +export function isPortIdentifier(arg: unknown): arg is PortIdentifier { + return ( + Boolean(arg) && + typeof arg === 'object' && + (arg).protocol !== undefined && + typeof (arg).protocol === 'string' && + (arg).address !== undefined && + typeof (arg).address === 'string' + ); +} + +// the smaller the number, the higher the priority +const portProtocolPriorities: Record = { + serial: 0, + network: 1, +} as const; + +/** + * See `boardListItemComparator`. + */ +export function portProtocolComparator( + left: PortIdentifier, + right: PortIdentifier +): number { + const leftPriority = + portProtocolPriorities[left.protocol] ?? Number.MAX_SAFE_INTEGER; + const rightPriority = + portProtocolPriorities[right.protocol] ?? Number.MAX_SAFE_INTEGER; + return leftPriority - rightPriority; +} + +/** + * Lightweight information to identify a board.\ + * \ + * Note: the `name` property of the board identifier must never participate in the board's identification. + * Hence, it should only be used as the final fallback for the UI when the board's platform is not installed and only the board's name is available. + */ +export interface BoardIdentifier { + /** + * The name of the board. It's only purpose is to provide a fallback for the UI. Preferably do not use this property for any sophisticated logic. When + */ + readonly name: string; + /** + * The FQBN might contain boards config options if selected from the discovered ports (see [arduino/arduino-ide#1588](https://github.com/arduino/arduino-ide/issues/1588)). + */ + // TODO: decide whether to persist the boards config if any + readonly fqbn: string | undefined; +} + +export function isBoardIdentifier(arg: unknown): arg is BoardIdentifier { + return ( + Boolean(arg) && + typeof arg === 'object' && + (arg).name !== undefined && + typeof (arg).name === 'string' && + ((arg).fqbn === undefined || + ((arg).fqbn !== undefined && + typeof (arg).fqbn === 'string')) + ); +} + +/** + * @param options if `looseFqbn` is `true`, FQBN config options are ignored. Hence, `{ name: 'x', fqbn: 'a:b:c:o1=v1 }` equals `{ name: 'y', fqbn: 'a:b:c' }`. It's `true` by default. + */ +export function boardIdentifierEquals( + left: BoardIdentifier | undefined, + right: BoardIdentifier | undefined, + options: { looseFqbn: boolean } = { looseFqbn: true } +): boolean { + if (!left) { + return !right; + } + if (!right) { + return !left; + } + if ((left.fqbn && !right.fqbn) || (!left.fqbn && right.fqbn)) { + // This can be very tricky when comparing boards + // the CLI's board search returns with falsy FQBN when the platform is not installed + // the CLI's board list returns with the full FQBN (for detected boards) even if the platform is not installed + // when there are multiple boards with the same name (Arduino Nano RP2040) from different platforms (Mbed Nano OS vs. the deprecated global Mbed OS) + // maybe add some 3rd party platform overhead (https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json) + // and it will get very tricky when comparing a board which has a FQBN and which does not. + return false; // TODO: This a strict now. Maybe compare name in the future. + } + if (left.fqbn && right.fqbn) { + const leftFqbn = options.looseFqbn ? sanitizeFqbn(left.fqbn) : left.fqbn; + const rightFqbn = options.looseFqbn ? sanitizeFqbn(right.fqbn) : right.fqbn; + return leftFqbn === rightFqbn; + } + // No more Genuino hack. + // https://github.com/arduino/arduino-ide/blob/f6a43254f5c416a2e4fa888875358336b42dd4d5/arduino-ide-extension/src/common/protocol/boards-service.ts#L572-L581 + return left.name === right.name; +} + +/** + * See `boardListItemComparator`. + */ +export function boardIdentifierComparator( + left: BoardIdentifier | undefined, + right: BoardIdentifier | undefined +): number { + if (!left) { + return right ? 1 : 0; + } + if (!right) { + return left ? -1 : 0; + } + let leftVendor: string | undefined = undefined; + let rightVendor: string | undefined = undefined; + if (left.fqbn) { + const [vendor] = left.fqbn.split(':'); + leftVendor = vendor; + } + if (right.fqbn) { + const [vendor] = right.fqbn.split(':'); + rightVendor = vendor; + } + if (leftVendor === 'arduino' && rightVendor !== 'arduino') { + return -1; + } + if (leftVendor !== 'arduino' && rightVendor === 'arduino') { + return 1; + } + return naturalCompare(left.name, right.name); +} + +export function boardIdentifierLabel( + board: BoardIdentifier, + showFqbn = true +): string { + const { name, fqbn } = board; + let label = name; + if (fqbn && showFqbn) { + label += ` (${fqbn})`; + } + return label; +} + +export interface BoardsConfig { + selectedBoard: BoardIdentifier | undefined; + selectedPort: PortIdentifier | undefined; +} + +/** + * Creates a new board config object with `undefined` properties. + */ +export function emptyBoardsConfig(): BoardsConfig { + return { + selectedBoard: undefined, + selectedPort: undefined, + }; +} + +export function isDefinedBoardsConfig( + boardsConfig: BoardsConfig | undefined +): boardsConfig is Defined { + if (!boardsConfig) { + return false; + } + return ( + boardsConfig.selectedBoard !== undefined && + boardsConfig.selectedPort !== undefined + ); +} + +export interface BoardIdentifierChangeEvent { + readonly previousSelectedBoard: BoardIdentifier | undefined; + readonly selectedBoard: BoardIdentifier | undefined; +} + +export function isBoardIdentifierChangeEvent( + event: BoardsConfigChangeEvent +): event is BoardIdentifierChangeEvent { + return 'previousSelectedBoard' in event && 'selectedBoard' in event; +} + +export interface PortIdentifierChangeEvent { + readonly previousSelectedPort: PortIdentifier | undefined; + readonly selectedPort: PortIdentifier | undefined; +} + +export function isPortIdentifierChangeEvent( + event: BoardsConfigChangeEvent +): event is PortIdentifierChangeEvent { + return 'previousSelectedPort' in event && 'selectedPort' in event; +} + +export type BoardsConfigChangeEvent = + | BoardIdentifierChangeEvent + | PortIdentifierChangeEvent + | (BoardIdentifierChangeEvent & PortIdentifierChangeEvent); + export interface BoardInfo { /** * Board name. Could be `'Unknown board`'. @@ -714,50 +863,57 @@ export const unknownBoard = nls.localize( 'arduino/board/unknownBoard', 'Unknown board' ); +export const unconfirmedBoard = nls.localize( + 'arduino/board/unconfirmedBoard', + 'Unconfirmed board' +); +export const selectBoard = nls.localize( + 'arduino/board/selectBoard', + 'Select Board' +); +export const notConnected = nls.localize( + 'arduino/common/notConnected', + '[not connected]' +); /** * The returned promise resolves to a `BoardInfo` if available to show in the UI or an info message explaining why showing the board info is not possible. */ export async function getBoardInfo( - selectedPort: Port | undefined, - availablePorts: MaybePromise + boardListProvider: MaybePromise ): Promise { - if (!selectedPort) { + const boardList = await boardListProvider; + const ports = boardList.ports(); + const detectedPort = ports[ports.matchingIndex]; + if (!detectedPort) { return selectPortForInfo; } + const { port: selectedPort, boards } = detectedPort; // IDE2 must show the board info based on the selected port. // https://github.com/arduino/arduino-ide/issues/1489 // IDE 1.x supports only serial port protocol if (selectedPort.protocol !== 'serial') { return nonSerialPort; } - const selectedPortKey = Port.keyOf(selectedPort); - const state = await availablePorts; - const boardListOnSelectedPort = Object.entries(state).filter( - ([portKey, [port]]) => - portKey === selectedPortKey && isNonNativeSerial(port) - ); - - if (!boardListOnSelectedPort.length) { + if (!isNonNativeSerial(selectedPort)) { return noNativeSerialPort; } - const [, [port, boards]] = boardListOnSelectedPort[0]; - if (boardListOnSelectedPort.length > 1 || boards.length > 1) { + if (boards && boards.length > 1) { console.warn( `Detected more than one available boards on the selected port : ${JSON.stringify( - selectedPort + detectedPort )}. Detected boards were: ${JSON.stringify( - boardListOnSelectedPort - )}. Using the first one: ${JSON.stringify([port, boards])}` + boards + )}. Using the first one: ${JSON.stringify(boards[0])}` ); } - const board = boards[0]; + const board = boards ? boards[0] : undefined; const BN = board?.name ?? unknownBoard; - const VID = readProperty('vid', port); - const PID = readProperty('pid', port); - const SN = readProperty('serialNumber', port); + const VID = readProperty('vid', selectedPort); + const PID = readProperty('pid', selectedPort); + const SN = readProperty('serialNumber', selectedPort); return { VID, PID, SN, BN }; } diff --git a/arduino-ide-extension/src/common/protocol/core-service.ts b/arduino-ide-extension/src/common/protocol/core-service.ts index 2a683370d..9104df01e 100644 --- a/arduino-ide-extension/src/common/protocol/core-service.ts +++ b/arduino-ide-extension/src/common/protocol/core-service.ts @@ -1,15 +1,15 @@ -import { nls } from '@theia/core/lib/common/nls'; import { ApplicationError } from '@theia/core/lib/common/application-error'; +import { nls } from '@theia/core/lib/common/nls'; import type { Location, - Range, Position, + Range, } from '@theia/core/shared/vscode-languageserver-protocol'; -import type { BoardUserField, Port, Installable } from '../../common/protocol/'; -import type { Programmer } from './boards-service'; -import type { Sketch } from './sketches-service'; -import { IndexUpdateSummary } from './notification-service'; import type { CompileSummary as ApiCompileSummary } from 'vscode-arduino-api'; +import type { BoardUserField, Installable } from '../../common/protocol/'; +import { isPortIdentifier, PortIdentifier, Programmer } from './boards-service'; +import type { IndexUpdateSummary } from './notification-service'; +import type { Sketch } from './sketches-service'; export const CompilerWarningLiterals = [ 'None', @@ -148,11 +148,22 @@ export function isCompileSummary(arg: unknown): arg is CompileSummary { ); } +export interface UploadResponse { + readonly portAfterUpload: PortIdentifier; +} +export function isUploadResponse(arg: unknown): arg is UploadResponse { + return ( + Boolean(arg) && + typeof arg === 'object' && + isPortIdentifier((arg).portAfterUpload) + ); +} + export const CoreServicePath = '/services/core-service'; export const CoreService = Symbol('CoreService'); export interface CoreService { compile(options: CoreService.Options.Compile): Promise; - upload(options: CoreService.Options.Upload): Promise; + upload(options: CoreService.Options.Upload): Promise; burnBootloader(options: CoreService.Options.Bootloader): Promise; /** * Refreshes the underling core gRPC client for the Arduino CLI. @@ -198,7 +209,7 @@ export namespace CoreService { readonly sketch: Sketch; } export interface BoardBased { - readonly port?: Port; + readonly port?: PortIdentifier; readonly programmer?: Programmer | undefined; /** * For the _Verify after upload_ setting. diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 5eb793f5b..92fb7e4a6 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -1,5 +1,8 @@ -import { ApplicationError, Event, JsonRpcServer, nls } from '@theia/core'; -import { Board, Port } from './boards-service'; +import { ApplicationError } from '@theia/core/lib/common/application-error'; +import type { Event } from '@theia/core/lib/common/event'; +import type { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; +import { nls } from '@theia/core/lib/common/nls'; +import type { BoardIdentifier, PortIdentifier } from './boards-service'; export type PluggableMonitorSettings = Record; export interface MonitorSettings { @@ -15,17 +18,20 @@ export const MonitorManagerProxy = Symbol('MonitorManagerProxy'); export interface MonitorManagerProxy extends JsonRpcServer { startMonitor( - board: Board, - port: Port, + board: BoardIdentifier, + port: PortIdentifier, settings?: PluggableMonitorSettings ): Promise; changeMonitorSettings( - board: Board, - port: Port, + board: BoardIdentifier, + port: PortIdentifier, settings: MonitorSettings ): Promise; - stopMonitor(board: Board, port: Port): Promise; - getCurrentSettings(board: Board, port: Port): Promise; + stopMonitor(board: BoardIdentifier, port: PortIdentifier): Promise; + getCurrentSettings( + board: BoardIdentifier, + port: PortIdentifier + ): Promise; } export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); @@ -38,7 +44,10 @@ export interface MonitorManagerProxyClient { getWebSocketPort(): number | undefined; isWSConnected(): Promise; startMonitor(settings?: PluggableMonitorSettings): Promise; - getCurrentSettings(board: Board, port: Port): Promise; + getCurrentSettings( + board: BoardIdentifier, + port: PortIdentifier + ): Promise; send(message: string): void; changeSettings(settings: MonitorSettings): void; } @@ -95,7 +104,7 @@ export const MissingConfigurationError = declareMonitorError( ); export function createConnectionFailedError( - port: Port, + port: PortIdentifier, details?: string ): ApplicationError { const { protocol, address } = port; @@ -120,7 +129,7 @@ export function createConnectionFailedError( return ConnectionFailedError(message, { protocol, address }); } export function createNotConnectedError( - port: Port + port: PortIdentifier ): ApplicationError { const { protocol, address } = port; return NotConnectedError( @@ -134,7 +143,7 @@ export function createNotConnectedError( ); } export function createAlreadyConnectedError( - port: Port + port: PortIdentifier ): ApplicationError { const { protocol, address } = port; return AlreadyConnectedError( @@ -148,7 +157,7 @@ export function createAlreadyConnectedError( ); } export function createMissingConfigurationError( - port: Port + port: PortIdentifier ): ApplicationError { const { protocol, address } = port; return MissingConfigurationError( diff --git a/arduino-ide-extension/src/common/protocol/notification-service.ts b/arduino-ide-extension/src/common/protocol/notification-service.ts index eba8f798e..9ad5c202d 100644 --- a/arduino-ide-extension/src/common/protocol/notification-service.ts +++ b/arduino-ide-extension/src/common/protocol/notification-service.ts @@ -1,11 +1,11 @@ import type { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; import type { - AttachedBoardsChangeEvent, BoardsPackage, ConfigState, + DetectedPorts, + IndexType, ProgressMessage, Sketch, - IndexType, } from '../protocol'; import type { LibraryPackage } from './library-service'; @@ -68,7 +68,9 @@ export interface NotificationServiceClient { notifyLibraryDidUninstall(event: { item: LibraryPackage }): void; // Boards discovery - notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void; + notifyDetectedPortsDidChange(event: { detectedPorts: DetectedPorts }): void; + + // Sketches notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void; } diff --git a/arduino-ide-extension/src/common/types.ts b/arduino-ide-extension/src/common/types.ts index 421a27534..c73987650 100644 --- a/arduino-ide-extension/src/common/types.ts +++ b/arduino-ide-extension/src/common/types.ts @@ -1,3 +1,7 @@ export type RecursiveRequired = { [P in keyof T]-?: RecursiveRequired; }; + +export type Defined = { + [P in keyof T]: NonNullable; +}; diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts index 76ee458be..9cb5f74e4 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts @@ -13,6 +13,7 @@ import { promises as fs, rm, rmSync } from 'node:fs'; import type { MaybePromise, Mutable } from '@theia/core/lib/common/types'; import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token'; import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props'; +import { environment } from '@theia/application-package/lib/environment'; import { ElectronMainApplication as TheiaElectronMainApplication, ElectronMainExecutionParams, @@ -702,6 +703,12 @@ class InterruptWorkspaceRestoreError extends Error { async function updateFrontendApplicationConfigFromPackageJson( config: FrontendApplicationConfig ): Promise { + if (environment.electron.isDevMode()) { + console.debug( + 'Skipping frontend application configuration customizations. Running in dev mode.' + ); + return config; + } try { const modulePath = __filename; // must go from `./lib/backend/electron-main.js` to `./package.json` when the app is webpacked. diff --git a/arduino-ide-extension/src/node/arduino-firmware-uploader-impl.ts b/arduino-ide-extension/src/node/arduino-firmware-uploader-impl.ts index f7f22d1d2..d1c1d0cb5 100644 --- a/arduino-ide-extension/src/node/arduino-firmware-uploader-impl.ts +++ b/arduino-ide-extension/src/node/arduino-firmware-uploader-impl.ts @@ -66,7 +66,7 @@ export class ArduinoFirmwareUploaderImpl implements ArduinoFirmwareUploader { ]); return output; } finally { - await this.monitorManager.notifyUploadFinished(board.fqbn, port); + await this.monitorManager.notifyUploadFinished(board.fqbn, port, port); // here the before and after ports are assumed to be always the same } } diff --git a/arduino-ide-extension/src/node/board-discovery.ts b/arduino-ide-extension/src/node/board-discovery.ts index 8699b7232..e698abf6e 100644 --- a/arduino-ide-extension/src/node/board-discovery.ts +++ b/arduino-ide-extension/src/node/board-discovery.ts @@ -1,18 +1,22 @@ -import { ClientDuplexStream } from '@grpc/grpc-js'; -import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import type { ClientDuplexStream } from '@grpc/grpc-js'; +import { + Disposable, + DisposableCollection, +} from '@theia/core/lib/common/disposable'; import { Emitter, Event } from '@theia/core/lib/common/event'; import { ILogger } from '@theia/core/lib/common/logger'; import { deepClone } from '@theia/core/lib/common/objects'; import { Deferred } from '@theia/core/lib/common/promise-util'; -import { BackendApplicationContribution } from '@theia/core/lib/node'; +import type { Mutable } from '@theia/core/lib/common/types'; +import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; import { inject, injectable, named } from '@theia/core/shared/inversify'; -import { Disposable } from '@theia/core/lib/common/disposable'; +import { isDeepStrictEqual } from 'util'; import { v4 } from 'uuid'; import { Unknown } from '../common/nls'; import { - AttachedBoardsChangeEvent, - AvailablePorts, Board, + DetectedPort, + DetectedPorts, NotificationServiceServer, Port, } from '../common/protocol'; @@ -22,7 +26,7 @@ import { DetectedPort as RpcDetectedPort, } from './cli-protocol/cc/arduino/cli/commands/v1/board_pb'; import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; -import { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb'; +import type { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb'; import { CoreClientAware } from './core-client-provider'; import { ServiceError } from './service-error'; @@ -57,23 +61,9 @@ export class BoardDiscovery private readonly onStreamDidCancelEmitter = new Emitter(); // when the watcher is canceled by the IDE2 private readonly toDisposeOnStopWatch = new DisposableCollection(); - private uploadInProgress = false; - - /** - * Keys are the `address` of the ports. - * - * The `protocol` is ignored because the board detach event does not carry the protocol information, - * just the address. - * ```json - * { - * "type": "remove", - * "address": "/dev/cu.usbmodem14101" - * } - * ``` - */ - private _availablePorts: AvailablePorts = {}; - get availablePorts(): AvailablePorts { - return this._availablePorts; + private _detectedPorts: DetectedPorts = {}; + get detectedPorts(): DetectedPorts { + return this._detectedPorts; } onStart(): void { @@ -120,10 +110,6 @@ export class BoardDiscovery }); } - setUploadInProgress(uploadInProgress: boolean): void { - this.uploadInProgress = uploadInProgress; - } - private createTimeout( after: number, onTimeout: (error: Error) => void @@ -202,18 +188,6 @@ export class BoardDiscovery return wrapper; } - private toJson(arg: BoardListWatchRequest | BoardListWatchResponse): string { - let object: Record | undefined = undefined; - if (arg instanceof BoardListWatchRequest) { - object = BoardListWatchRequest.toObject(false, arg); - } else if (arg instanceof BoardListWatchResponse) { - object = BoardListWatchResponse.toObject(false, arg); - } else { - throw new Error(`Unhandled object type: ${arg}`); - } - return JSON.stringify(object); - } - async start(): Promise { this.logger.info('start'); if (this.stopping) { @@ -240,9 +214,8 @@ export class BoardDiscovery this.logger.info('start resolved watching'); } - // XXX: make this `protected` and override for tests if IDE2 wants to mock events from the CLI. - private onBoardListWatchResponse(resp: BoardListWatchResponse): void { - this.logger.info(this.toJson(resp)); + protected onBoardListWatchResponse(resp: BoardListWatchResponse): void { + this.logger.info(JSON.stringify(resp.toObject(false))); const eventType = EventType.parse(resp.getEventType()); if (eventType === EventType.Quit) { @@ -251,69 +224,161 @@ export class BoardDiscovery return; } - const detectedPort = resp.getPort(); - if (detectedPort) { - const { port, boards } = this.fromRpc(detectedPort); - if (!port) { - if (!!boards.length) { - console.warn( - `Could not detect the port, but unexpectedly received discovered boards. This is most likely a bug! Response was: ${this.toJson( - resp - )}` - ); - } - return; - } - const oldState = deepClone(this._availablePorts); - const newState = deepClone(this._availablePorts); - const key = Port.keyOf(port); - - if (eventType === EventType.Add) { - if (newState[key]) { - const [, knownBoards] = newState[key]; - this.logger.warn( - `Port '${Port.toString( - port - )}' was already available. Known boards before override: ${JSON.stringify( - knownBoards - )}` - ); - } - newState[key] = [port, boards]; - } else if (eventType === EventType.Remove) { - if (!newState[key]) { - this.logger.warn( - `Port '${Port.toString(port)}' was not available. Skipping` - ); - return; - } - delete newState[key]; + const rpcDetectedPort = resp.getPort(); + if (rpcDetectedPort) { + const detectedPort = this.fromRpc(rpcDetectedPort); + if (detectedPort) { + this.fireSoon({ detectedPort, eventType }); + } else { + this.logger.warn( + `Could not extract the detected port from ${rpcDetectedPort.toObject( + false + )}` + ); } - - const event: AttachedBoardsChangeEvent = { - oldState: { - ...AvailablePorts.split(oldState), - }, - newState: { - ...AvailablePorts.split(newState), - }, - uploadInProgress: this.uploadInProgress, - }; - - this._availablePorts = newState; - this.notificationService.notifyAttachedBoardsDidChange(event); + } else if (resp.getError()) { + this.logger.error( + `Could not extract any detected 'port' from the board list watch response. An 'error' has occurred: ${resp.getError()}` + ); } } - private fromRpc(detectedPort: RpcDetectedPort): DetectedPort { + private fromRpc(detectedPort: RpcDetectedPort): DetectedPort | undefined { const rpcPort = detectedPort.getPort(); - const port = rpcPort && this.fromRpcPort(rpcPort); + if (!rpcPort) { + return undefined; + } + const port = createApiPort(rpcPort); + // if (port.address === '/dev/cu.Bluetooth-Incoming-Port') { + // return { + // port: { + // address: 'COM41', + // addressLabel: 'COM41', + // protocol: 'serial', + // protocolLabel: 'Serial Port (USB)', + // properties: { + // pid: '0x1001', + // serialNumber: '', + // vid: '0x303A', + // }, + // }, + // boards: [ + // { + // name: 'Adafruit QT Py ESP32-C3', + // fqbn: 'esp32:esp32:adafruit_qtpy_esp32c3', + // }, + // { + // name: 'AirM2M_CORE_ESP32C3', + // fqbn: 'esp32:esp32:AirM2M_CORE_ESP32C3', + // }, + // { + // name: 'Crabik Slot ESP32-S3', + // fqbn: 'esp32:esp32:crabik_slot_esp32_s3', + // }, + // { + // name: 'DFRobot Beetle ESP32-C3', + // fqbn: 'esp32:esp32:dfrobot_beetle_esp32c3', + // }, + // { + // name: 'DFRobot Firebeetle 2 ESP32-S3', + // fqbn: 'esp32:esp32:dfrobot_firebeetle2_esp32s3', + // }, + // { + // name: 'DFRobot Romeo ESP32-S3', + // fqbn: 'esp32:esp32:dfrobot_romeo_esp32s3', + // }, + // { + // name: 'ESP32C3 Dev Module', + // fqbn: 'esp32:esp32:esp32c3', + // }, + // { + // name: 'ESP32S3 Dev Module', + // fqbn: 'esp32:esp32:esp32s3', + // }, + // { + // name: 'ESP32-S3-Box', + // fqbn: 'esp32:esp32:esp32s3box', + // }, + // { + // name: 'ESP32S3 CAM LCD', + // fqbn: 'esp32:esp32:esp32s3camlcd', + // }, + // { + // name: 'ESP32-S3-USB-OTG', + // fqbn: 'esp32:esp32:esp32s3usbotg', + // }, + // { + // name: 'Heltec WiFi Kit 32(V3)', + // fqbn: 'esp32:esp32:heltec_wifi_kit_32_V3', + // }, + // { + // name: 'Heltec WiFi LoRa 32(V3) / Wireless shell(V3) / Wireless stick lite (V3)', + // fqbn: 'esp32:esp32:heltec_wifi_lora_32_V3', + // }, + // { + // name: 'LilyGo T-Display-S3', + // fqbn: 'esp32:esp32:lilygo_t_display_s3', + // }, + // { + // name: 'LOLIN C3 Mini', + // fqbn: 'esp32:esp32:lolin_c3_mini', + // }, + // { + // name: 'LOLIN S3', + // fqbn: 'esp32:esp32:lolin_s3', + // }, + // { + // name: 'M5Stack-ATOMS3', + // fqbn: 'esp32:esp32:m5stack-atoms3', + // }, + // { + // name: 'M5Stack-CoreS3', + // fqbn: 'esp32:esp32:m5stack-cores3', + // }, + // { + // name: 'Nebula S3', + // fqbn: 'esp32:esp32:nebulas3', + // }, + // { + // name: 'u-blox NORA-W10 series (ESP32-S3)', + // fqbn: 'esp32:esp32:nora_w10', + // }, + // { + // name: 'RedPill(+) ESP32-S3', + // fqbn: 'esp32:esp32:redpill_esp32s3', + // }, + // { + // name: 'STAMP-S3', + // fqbn: 'esp32:esp32:stamp-s3', + // }, + // { + // name: 'TAMC Termod S3', + // fqbn: 'esp32:esp32:tamc_termod_s3', + // }, + // { + // name: 'VALTRACK_V4_MFW_ESP32_C3', + // fqbn: 'esp32:esp32:VALTRACK_V4_MFW_ESP32_C3', + // }, + // { + // name: 'VALTRACK_V4_VTS_ESP32_C3', + // fqbn: 'esp32:esp32:VALTRACK_V4_VTS_ESP32_C3', + // }, + // { + // name: 'WiFiduinoV2', + // fqbn: 'esp32:esp32:wifiduino32c3', + // }, + // { + // name: 'WiFiduino32S3', + // fqbn: 'esp32:esp32:wifiduino32s3', + // }, + // ], + // }; + // } const boards = detectedPort.getMatchingBoardsList().map( (board) => ({ - fqbn: board.getFqbn(), + fqbn: board.getFqbn() || undefined, // prefer undefined fqbn over empty string name: board.getName() || Unknown, - port, } as Board) ); return { @@ -322,15 +387,55 @@ export class BoardDiscovery }; } - private fromRpcPort(rpcPort: RpcPort): Port { - return { - address: rpcPort.getAddress(), - addressLabel: rpcPort.getLabel(), - protocol: rpcPort.getProtocol(), - protocolLabel: rpcPort.getProtocolLabel(), - properties: Port.Properties.create(rpcPort.getPropertiesMap().toObject()), - hardwareId: rpcPort.getHardwareId(), - }; + private fireSoonHandle: NodeJS.Timeout | undefined; + private readonly bufferedEvents: DetectedPortChangeEvent[] = []; + private fireSoon(event: DetectedPortChangeEvent): void { + this.bufferedEvents.push(event); + clearTimeout(this.fireSoonHandle); + this.fireSoonHandle = setTimeout(() => { + const current = deepClone(this.detectedPorts); + const newState = this.calculateNewState(this.bufferedEvents, current); + if (!isDeepStrictEqual(current, newState)) { + this._detectedPorts = newState; + this.notificationService.notifyDetectedPortsDidChange({ + detectedPorts: this._detectedPorts, + }); + } + this.bufferedEvents.length = 0; + }, 100); + } + + private calculateNewState( + events: DetectedPortChangeEvent[], + prevState: Mutable + ): DetectedPorts { + const newState = deepClone(prevState); + for (const { detectedPort, eventType } of events) { + const { port, boards } = detectedPort; + const key = Port.keyOf(port); + if (eventType === EventType.Add) { + const alreadyDetectedPort = newState[key]; + if (alreadyDetectedPort) { + console.warn( + `Detected a new port that has been already discovered. The old value will be overridden. Old value: ${JSON.stringify( + alreadyDetectedPort + )}, new value: ${JSON.stringify(detectedPort)}` + ); + } + newState[key] = { port, boards }; + } else if (eventType === EventType.Remove) { + const alreadyDetectedPort = newState[key]; + if (!alreadyDetectedPort) { + console.warn( + `Detected a port removal but it has not been discovered. This is most likely a bug! Detected port was: ${JSON.stringify( + detectedPort + )}` + ); + } + delete newState[key]; + } + } + return newState; } } @@ -357,7 +462,18 @@ namespace EventType { } } -interface DetectedPort { - port: Port | undefined; - boards: Board[]; +interface DetectedPortChangeEvent { + readonly detectedPort: DetectedPort; + readonly eventType: EventType.Add | EventType.Remove; +} + +export function createApiPort(rpcPort: RpcPort): Port { + return { + address: rpcPort.getAddress(), + addressLabel: rpcPort.getLabel(), + protocol: rpcPort.getProtocol(), + protocolLabel: rpcPort.getProtocolLabel(), + properties: Port.Properties.create(rpcPort.getPropertiesMap().toObject()), + hardwareId: rpcPort.getHardwareId() || undefined, // prefer undefined over empty string + }; } diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index a2db13193..80f1446e0 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -12,13 +12,15 @@ import { Programmer, ResponseService, NotificationServiceServer, - AvailablePorts, + DetectedPorts, BoardWithPackage, BoardUserField, BoardSearch, sortComponents, SortGroup, platformInstallFailed, + createPlatformIdentifier, + platformIdentifierEquals, } from '../common/protocol'; import { PlatformInstallRequest, @@ -65,8 +67,8 @@ export class BoardsServiceImpl @inject(BoardDiscovery) protected readonly boardDiscovery: BoardDiscovery; - async getState(): Promise { - return this.boardDiscovery.availablePorts; + async getDetectedPorts(): Promise { + return this.boardDiscovery.detectedPorts; } async getBoardDetails(options: { @@ -165,7 +167,7 @@ export class BoardsServiceImpl debuggingSupported, VID, PID, - buildProperties + buildProperties, }; } @@ -212,6 +214,28 @@ export class BoardsServiceImpl return this.handleListBoards(client.boardListAll.bind(client), req); } + async getInstalledPlatforms(): Promise { + const { instance, client } = await this.coreClient; + return new Promise((resolve, reject) => { + client.platformList( + new PlatformListRequest().setInstance(instance), + (err, response) => { + if (err) { + reject(err); + return; + } + resolve( + response + .getInstalledPlatformsList() + .map((platform, _, installedPlatforms) => + toBoardsPackage(platform, installedPlatforms) + ) + ); + } + ); + }); + } + private async handleListBoards( getBoards: ( request: BoardListAllRequest | BoardSearchRequest, @@ -232,10 +256,38 @@ export class BoardsServiceImpl for (const board of resp.getBoardsList()) { const platform = board.getPlatform(); if (platform) { + const platformId = platform.getId(); + const fqbn = board.getFqbn() || undefined; // prefer undefined over empty string + const parsedPlatformId = createPlatformIdentifier(platformId); + if (!parsedPlatformId) { + console.warn( + `Could not create platform identifier from platform ID input: ${platform.getId()}. Skipping` + ); + continue; + } + if (fqbn) { + const checkPlatformId = createPlatformIdentifier(board.getFqbn()); + if (!checkPlatformId) { + console.warn( + `Could not create platform identifier from FQBN input: ${board.getFqbn()}. Skipping` + ); + continue; + } + if ( + !platformIdentifierEquals(parsedPlatformId, checkPlatformId) + ) { + console.warn( + `Mismatching platform identifiers. Platform: ${JSON.stringify( + parsedPlatformId + )}, FQBN: ${JSON.stringify(checkPlatformId)}. Skipping` + ); + continue; + } + } boards.push({ name: board.getName(), fqbn: board.getFqbn(), - packageId: platform.getId(), + packageId: parsedPlatformId, packageName: platform.getName(), manuallyInstalled: platform.getManuallyInstalled(), }); @@ -316,38 +368,6 @@ export class BoardsServiceImpl } ); const packages = new Map(); - const toPackage = (platform: Platform) => { - let installedVersion: string | undefined; - const matchingPlatform = installedPlatforms.find( - (ip) => ip.getId() === platform.getId() - ); - if (!!matchingPlatform) { - installedVersion = matchingPlatform.getInstalled(); - } - return { - id: platform.getId(), - name: platform.getName(), - author: platform.getMaintainer(), - availableVersions: [platform.getLatest()], - description: platform - .getBoardsList() - .map((b) => b.getName()) - .join(', '), - installable: true, - types: platform.getTypeList(), - deprecated: platform.getDeprecated(), - summary: nls.localize( - 'arduino/component/boardsIncluded', - 'Boards included in this package:' - ), - installedVersion, - boards: platform - .getBoardsList() - .map((b) => { name: b.getName(), fqbn: b.getFqbn() }), - moreInfoLink: platform.getWebsite(), - }; - }; - // We must group the cores by ID, and sort platforms by, first the installed version, then version alphabetical order. // Otherwise we lose the FQBN information. const groupedById: Map = new Map(); @@ -400,7 +420,7 @@ export class BoardsServiceImpl pkg.availableVersions.push(platform.getLatest()); pkg.availableVersions.sort(Installable.Version.COMPARATOR).reverse(); } else { - packages.set(id, toPackage(platform)); + packages.set(id, toBoardsPackage(platform, installedPlatforms)); } } } @@ -572,3 +592,37 @@ function boardsPackageSortGroup(boardsPackage: BoardsPackage): SortGroup { } return types.join('-') as SortGroup; } + +function toBoardsPackage( + platform: Platform, + installedPlatforms: Platform[] +): BoardsPackage { + let installedVersion: string | undefined; + const matchingPlatform = installedPlatforms.find( + (ip) => ip.getId() === platform.getId() + ); + if (!!matchingPlatform) { + installedVersion = matchingPlatform.getInstalled(); + } + return { + id: platform.getId(), + name: platform.getName(), + author: platform.getMaintainer(), + availableVersions: [platform.getLatest()], + description: platform + .getBoardsList() + .map((b) => b.getName()) + .join(', '), + types: platform.getTypeList(), + deprecated: platform.getDeprecated(), + summary: nls.localize( + 'arduino/component/boardsIncluded', + 'Boards included in this package:' + ), + installedVersion, + boards: platform + .getBoardsList() + .map((b) => { name: b.getName(), fqbn: b.getFqbn() }), + moreInfoLink: platform.getWebsite(), + }; +} diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/board_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/board_pb.d.ts index 76e70da5f..fbf5b47b7 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/board_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/board_pb.d.ts @@ -14,14 +14,11 @@ export class BoardDetailsRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): BoardDetailsRequest; - getFqbn(): string; setFqbn(value: string): BoardDetailsRequest; - getDoNotExpandBuildProperties(): boolean; setDoNotExpandBuildProperties(value: boolean): BoardDetailsRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardDetailsRequest.AsObject; static toObject(includeInstance: boolean, msg: BoardDetailsRequest): BoardDetailsRequest.AsObject; @@ -43,66 +40,51 @@ export namespace BoardDetailsRequest { export class BoardDetailsResponse extends jspb.Message { getFqbn(): string; setFqbn(value: string): BoardDetailsResponse; - getName(): string; setName(value: string): BoardDetailsResponse; - getVersion(): string; setVersion(value: string): BoardDetailsResponse; - getPropertiesId(): string; setPropertiesId(value: string): BoardDetailsResponse; - getAlias(): string; setAlias(value: string): BoardDetailsResponse; - getOfficial(): boolean; setOfficial(value: boolean): BoardDetailsResponse; - getPinout(): string; setPinout(value: string): BoardDetailsResponse; - hasPackage(): boolean; clearPackage(): void; getPackage(): Package | undefined; setPackage(value?: Package): BoardDetailsResponse; - hasPlatform(): boolean; clearPlatform(): void; getPlatform(): BoardPlatform | undefined; setPlatform(value?: BoardPlatform): BoardDetailsResponse; - clearToolsDependenciesList(): void; getToolsDependenciesList(): Array; setToolsDependenciesList(value: Array): BoardDetailsResponse; addToolsDependencies(value?: ToolsDependencies, index?: number): ToolsDependencies; - clearConfigOptionsList(): void; getConfigOptionsList(): Array; setConfigOptionsList(value: Array): BoardDetailsResponse; addConfigOptions(value?: ConfigOption, index?: number): ConfigOption; - clearProgrammersList(): void; getProgrammersList(): Array; setProgrammersList(value: Array): BoardDetailsResponse; addProgrammers(value?: cc_arduino_cli_commands_v1_common_pb.Programmer, index?: number): cc_arduino_cli_commands_v1_common_pb.Programmer; - getDebuggingSupported(): boolean; setDebuggingSupported(value: boolean): BoardDetailsResponse; - clearIdentificationPropertiesList(): void; getIdentificationPropertiesList(): Array; setIdentificationPropertiesList(value: Array): BoardDetailsResponse; addIdentificationProperties(value?: BoardIdentificationProperties, index?: number): BoardIdentificationProperties; - clearBuildPropertiesList(): void; getBuildPropertiesList(): Array; setBuildPropertiesList(value: Array): BoardDetailsResponse; addBuildProperties(value: string, index?: number): string; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardDetailsResponse.AsObject; static toObject(includeInstance: boolean, msg: BoardDetailsResponse): BoardDetailsResponse.AsObject; @@ -138,7 +120,6 @@ export class BoardIdentificationProperties extends jspb.Message { getPropertiesMap(): jspb.Map; clearPropertiesMap(): void; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardIdentificationProperties.AsObject; static toObject(includeInstance: boolean, msg: BoardIdentificationProperties): BoardIdentificationProperties.AsObject; @@ -159,26 +140,20 @@ export namespace BoardIdentificationProperties { export class Package extends jspb.Message { getMaintainer(): string; setMaintainer(value: string): Package; - getUrl(): string; setUrl(value: string): Package; - getWebsiteUrl(): string; setWebsiteUrl(value: string): Package; - getEmail(): string; setEmail(value: string): Package; - getName(): string; setName(value: string): Package; - hasHelp(): boolean; clearHelp(): void; getHelp(): Help | undefined; setHelp(value?: Help): Package; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Package.AsObject; static toObject(includeInstance: boolean, msg: Package): Package.AsObject; @@ -204,7 +179,6 @@ export class Help extends jspb.Message { getOnline(): string; setOnline(value: string): Help; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Help.AsObject; static toObject(includeInstance: boolean, msg: Help): Help.AsObject; @@ -224,26 +198,19 @@ export namespace Help { export class BoardPlatform extends jspb.Message { getArchitecture(): string; setArchitecture(value: string): BoardPlatform; - getCategory(): string; setCategory(value: string): BoardPlatform; - getUrl(): string; setUrl(value: string): BoardPlatform; - getArchiveFilename(): string; setArchiveFilename(value: string): BoardPlatform; - getChecksum(): string; setChecksum(value: string): BoardPlatform; - getSize(): number; setSize(value: number): BoardPlatform; - getName(): string; setName(value: string): BoardPlatform; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardPlatform.AsObject; static toObject(includeInstance: boolean, msg: BoardPlatform): BoardPlatform.AsObject; @@ -269,19 +236,15 @@ export namespace BoardPlatform { export class ToolsDependencies extends jspb.Message { getPackager(): string; setPackager(value: string): ToolsDependencies; - getName(): string; setName(value: string): ToolsDependencies; - getVersion(): string; setVersion(value: string): ToolsDependencies; - clearSystemsList(): void; getSystemsList(): Array; setSystemsList(value: Array): ToolsDependencies; addSystems(value?: Systems, index?: number): Systems; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ToolsDependencies.AsObject; static toObject(includeInstance: boolean, msg: ToolsDependencies): ToolsDependencies.AsObject; @@ -304,20 +267,15 @@ export namespace ToolsDependencies { export class Systems extends jspb.Message { getChecksum(): string; setChecksum(value: string): Systems; - getHost(): string; setHost(value: string): Systems; - getArchiveFilename(): string; setArchiveFilename(value: string): Systems; - getUrl(): string; setUrl(value: string): Systems; - getSize(): number; setSize(value: number): Systems; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Systems.AsObject; static toObject(includeInstance: boolean, msg: Systems): Systems.AsObject; @@ -341,16 +299,13 @@ export namespace Systems { export class ConfigOption extends jspb.Message { getOption(): string; setOption(value: string): ConfigOption; - getOptionLabel(): string; setOptionLabel(value: string): ConfigOption; - clearValuesList(): void; getValuesList(): Array; setValuesList(value: Array): ConfigOption; addValues(value?: ConfigValue, index?: number): ConfigValue; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ConfigOption.AsObject; static toObject(includeInstance: boolean, msg: ConfigOption): ConfigOption.AsObject; @@ -372,14 +327,11 @@ export namespace ConfigOption { export class ConfigValue extends jspb.Message { getValue(): string; setValue(value: string): ConfigValue; - getValueLabel(): string; setValueLabel(value: string): ConfigValue; - getSelected(): boolean; setSelected(value: boolean): ConfigValue; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ConfigValue.AsObject; static toObject(includeInstance: boolean, msg: ConfigValue): ConfigValue.AsObject; @@ -404,14 +356,11 @@ export class BoardListRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): BoardListRequest; - getTimeout(): number; setTimeout(value: number): BoardListRequest; - getFqbn(): string; setFqbn(value: string): BoardListRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardListRequest.AsObject; static toObject(includeInstance: boolean, msg: BoardListRequest): BoardListRequest.AsObject; @@ -436,7 +385,6 @@ export class BoardListResponse extends jspb.Message { setPortsList(value: Array): BoardListResponse; addPorts(value?: DetectedPort, index?: number): DetectedPort; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardListResponse.AsObject; static toObject(includeInstance: boolean, msg: BoardListResponse): BoardListResponse.AsObject; @@ -459,13 +407,11 @@ export class DetectedPort extends jspb.Message { setMatchingBoardsList(value: Array): DetectedPort; addMatchingBoards(value?: BoardListItem, index?: number): BoardListItem; - hasPort(): boolean; clearPort(): void; getPort(): cc_arduino_cli_commands_v1_port_pb.Port | undefined; setPort(value?: cc_arduino_cli_commands_v1_port_pb.Port): DetectedPort; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DetectedPort.AsObject; static toObject(includeInstance: boolean, msg: DetectedPort): DetectedPort.AsObject; @@ -489,16 +435,13 @@ export class BoardListAllRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): BoardListAllRequest; - clearSearchArgsList(): void; getSearchArgsList(): Array; setSearchArgsList(value: Array): BoardListAllRequest; addSearchArgs(value: string, index?: number): string; - getIncludeHiddenBoards(): boolean; setIncludeHiddenBoards(value: boolean): BoardListAllRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardListAllRequest.AsObject; static toObject(includeInstance: boolean, msg: BoardListAllRequest): BoardListAllRequest.AsObject; @@ -523,7 +466,6 @@ export class BoardListAllResponse extends jspb.Message { setBoardsList(value: Array): BoardListAllResponse; addBoards(value?: BoardListItem, index?: number): BoardListItem; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardListAllResponse.AsObject; static toObject(includeInstance: boolean, msg: BoardListAllResponse): BoardListAllResponse.AsObject; @@ -546,11 +488,9 @@ export class BoardListWatchRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): BoardListWatchRequest; - getInterrupt(): boolean; setInterrupt(value: boolean): BoardListWatchRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardListWatchRequest.AsObject; static toObject(includeInstance: boolean, msg: BoardListWatchRequest): BoardListWatchRequest.AsObject; @@ -572,16 +512,13 @@ export class BoardListWatchResponse extends jspb.Message { getEventType(): string; setEventType(value: string): BoardListWatchResponse; - hasPort(): boolean; clearPort(): void; getPort(): DetectedPort | undefined; setPort(value?: DetectedPort): BoardListWatchResponse; - getError(): string; setError(value: string): BoardListWatchResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardListWatchResponse.AsObject; static toObject(includeInstance: boolean, msg: BoardListWatchResponse): BoardListWatchResponse.AsObject; @@ -603,20 +540,16 @@ export namespace BoardListWatchResponse { export class BoardListItem extends jspb.Message { getName(): string; setName(value: string): BoardListItem; - getFqbn(): string; setFqbn(value: string): BoardListItem; - getIsHidden(): boolean; setIsHidden(value: boolean): BoardListItem; - hasPlatform(): boolean; clearPlatform(): void; getPlatform(): cc_arduino_cli_commands_v1_common_pb.Platform | undefined; setPlatform(value?: cc_arduino_cli_commands_v1_common_pb.Platform): BoardListItem; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardListItem.AsObject; static toObject(includeInstance: boolean, msg: BoardListItem): BoardListItem.AsObject; @@ -642,14 +575,11 @@ export class BoardSearchRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): BoardSearchRequest; - getSearchArgs(): string; setSearchArgs(value: string): BoardSearchRequest; - getIncludeHiddenBoards(): boolean; setIncludeHiddenBoards(value: boolean): BoardSearchRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardSearchRequest.AsObject; static toObject(includeInstance: boolean, msg: BoardSearchRequest): BoardSearchRequest.AsObject; @@ -674,7 +604,6 @@ export class BoardSearchResponse extends jspb.Message { setBoardsList(value: Array): BoardSearchResponse; addBoards(value?: BoardListItem, index?: number): BoardListItem; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BoardSearchResponse.AsObject; static toObject(includeInstance: boolean, msg: BoardSearchResponse): BoardSearchResponse.AsObject; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.d.ts index b321b3c23..c5159b1cb 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.d.ts @@ -5,7 +5,6 @@ /* eslint-disable */ import * as grpc from "@grpc/grpc-js"; -import {handleClientStreamingCall} from "@grpc/grpc-js/build/src/server-call"; import * as cc_arduino_cli_commands_v1_commands_pb from "../../../../../cc/arduino/cli/commands/v1/commands_pb"; import * as google_rpc_status_pb from "../../../../../google/rpc/status_pb"; import * as cc_arduino_cli_commands_v1_common_pb from "../../../../../cc/arduino/cli/commands/v1/common_pb"; @@ -26,6 +25,7 @@ interface IArduinoCoreServiceService extends grpc.ServiceDefinition; responseDeserialize: grpc.deserialize; } +interface IArduinoCoreServiceService_ISetSketchDefaults extends grpc.MethodDefinition { + path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/SetSketchDefaults"; + requestStream: false; + responseStream: false; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} interface IArduinoCoreServiceService_IBoardDetails extends grpc.MethodDefinition { path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/BoardDetails"; requestStream: false; @@ -402,7 +411,7 @@ interface IArduinoCoreServiceService_IEnumerateMonitorPortSettings extends grpc. export const ArduinoCoreServiceService: IArduinoCoreServiceService; -export interface IArduinoCoreServiceServer { +export interface IArduinoCoreServiceServer extends grpc.UntypedServiceImplementation { create: grpc.handleUnaryCall; init: grpc.handleServerStreamingCall; destroy: grpc.handleUnaryCall; @@ -412,6 +421,7 @@ export interface IArduinoCoreServiceServer { newSketch: grpc.handleUnaryCall; loadSketch: grpc.handleUnaryCall; archiveSketch: grpc.handleUnaryCall; + setSketchDefaults: grpc.handleUnaryCall; boardDetails: grpc.handleUnaryCall; boardList: grpc.handleUnaryCall; boardListAll: grpc.handleUnaryCall; @@ -468,6 +478,9 @@ export interface IArduinoCoreServiceClient { archiveSketch(request: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchResponse) => void): grpc.ClientUnaryCall; archiveSketch(request: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchResponse) => void): grpc.ClientUnaryCall; archiveSketch(request: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchResponse) => void): grpc.ClientUnaryCall; + setSketchDefaults(request: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsResponse) => void): grpc.ClientUnaryCall; + setSketchDefaults(request: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsResponse) => void): grpc.ClientUnaryCall; + setSketchDefaults(request: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsResponse) => void): grpc.ClientUnaryCall; boardDetails(request: cc_arduino_cli_commands_v1_board_pb.BoardDetailsRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_board_pb.BoardDetailsResponse) => void): grpc.ClientUnaryCall; boardDetails(request: cc_arduino_cli_commands_v1_board_pb.BoardDetailsRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_board_pb.BoardDetailsResponse) => void): grpc.ClientUnaryCall; boardDetails(request: cc_arduino_cli_commands_v1_board_pb.BoardDetailsRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_board_pb.BoardDetailsResponse) => void): grpc.ClientUnaryCall; @@ -568,6 +581,9 @@ export class ArduinoCoreServiceClient extends grpc.Client implements IArduinoCor public archiveSketch(request: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchResponse) => void): grpc.ClientUnaryCall; public archiveSketch(request: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchResponse) => void): grpc.ClientUnaryCall; public archiveSketch(request: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.ArchiveSketchResponse) => void): grpc.ClientUnaryCall; + public setSketchDefaults(request: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsResponse) => void): grpc.ClientUnaryCall; + public setSketchDefaults(request: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsResponse) => void): grpc.ClientUnaryCall; + public setSketchDefaults(request: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsResponse) => void): grpc.ClientUnaryCall; public boardDetails(request: cc_arduino_cli_commands_v1_board_pb.BoardDetailsRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_board_pb.BoardDetailsResponse) => void): grpc.ClientUnaryCall; public boardDetails(request: cc_arduino_cli_commands_v1_board_pb.BoardDetailsRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_board_pb.BoardDetailsResponse) => void): grpc.ClientUnaryCall; public boardDetails(request: cc_arduino_cli_commands_v1_board_pb.BoardDetailsRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_commands_v1_board_pb.BoardDetailsResponse) => void): grpc.ClientUnaryCall; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.js index 49ec3d019..8947c7570 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.js @@ -709,6 +709,28 @@ function deserialize_cc_arduino_cli_commands_v1_PlatformUpgradeResponse(buffer_a return cc_arduino_cli_commands_v1_core_pb.PlatformUpgradeResponse.deserializeBinary(new Uint8Array(buffer_arg)); } +function serialize_cc_arduino_cli_commands_v1_SetSketchDefaultsRequest(arg) { + if (!(arg instanceof cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsRequest)) { + throw new Error('Expected argument of type cc.arduino.cli.commands.v1.SetSketchDefaultsRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_commands_v1_SetSketchDefaultsRequest(buffer_arg) { + return cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_cc_arduino_cli_commands_v1_SetSketchDefaultsResponse(arg) { + if (!(arg instanceof cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsResponse)) { + throw new Error('Expected argument of type cc.arduino.cli.commands.v1.SetSketchDefaultsResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_commands_v1_SetSketchDefaultsResponse(buffer_arg) { + return cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_cc_arduino_cli_commands_v1_SupportedUserFieldsRequest(arg) { if (!(arg instanceof cc_arduino_cli_commands_v1_upload_pb.SupportedUserFieldsRequest)) { throw new Error('Expected argument of type cc.arduino.cli.commands.v1.SupportedUserFieldsRequest'); @@ -975,6 +997,20 @@ archiveSketch: { responseSerialize: serialize_cc_arduino_cli_commands_v1_ArchiveSketchResponse, responseDeserialize: deserialize_cc_arduino_cli_commands_v1_ArchiveSketchResponse, }, + // Sets the sketch default FQBN and Port Address/Protocol in +// the sketch project file (sketch.yaml). These metadata can be retrieved +// using LoadSketch. +setSketchDefaults: { + path: '/cc.arduino.cli.commands.v1.ArduinoCoreService/SetSketchDefaults', + requestStream: false, + responseStream: false, + requestType: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsRequest, + responseType: cc_arduino_cli_commands_v1_commands_pb.SetSketchDefaultsResponse, + requestSerialize: serialize_cc_arduino_cli_commands_v1_SetSketchDefaultsRequest, + requestDeserialize: deserialize_cc_arduino_cli_commands_v1_SetSketchDefaultsRequest, + responseSerialize: serialize_cc_arduino_cli_commands_v1_SetSketchDefaultsResponse, + responseDeserialize: deserialize_cc_arduino_cli_commands_v1_SetSketchDefaultsResponse, + }, // BOARD COMMANDS // -------------- // diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts index fc1c8b525..73ed929aa 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts @@ -38,7 +38,6 @@ export class CreateResponse extends jspb.Message { getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): CreateResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): CreateResponse.AsObject; static toObject(includeInstance: boolean, msg: CreateResponse): CreateResponse.AsObject; @@ -61,14 +60,11 @@ export class InitRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): InitRequest; - getProfile(): string; setProfile(value: string): InitRequest; - getSketchPath(): string; setSketchPath(value: string): InitRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): InitRequest.AsObject; static toObject(includeInstance: boolean, msg: InitRequest): InitRequest.AsObject; @@ -94,19 +90,16 @@ export class InitResponse extends jspb.Message { getInitProgress(): InitResponse.Progress | undefined; setInitProgress(value?: InitResponse.Progress): InitResponse; - hasError(): boolean; clearError(): void; getError(): google_rpc_status_pb.Status | undefined; setError(value?: google_rpc_status_pb.Status): InitResponse; - hasProfile(): boolean; clearProfile(): void; getProfile(): cc_arduino_cli_commands_v1_common_pb.Profile | undefined; setProfile(value?: cc_arduino_cli_commands_v1_common_pb.Profile): InitResponse; - getMessageCase(): InitResponse.MessageCase; serializeBinary(): Uint8Array; @@ -134,13 +127,11 @@ export namespace InitResponse { getDownloadProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setDownloadProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): Progress; - hasTaskProgress(): boolean; clearTaskProgress(): void; getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): Progress; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Progress.AsObject; static toObject(includeInstance: boolean, msg: Progress): Progress.AsObject; @@ -161,13 +152,9 @@ export namespace InitResponse { export enum MessageCase { MESSAGE_NOT_SET = 0, - - INIT_PROGRESS = 1, - - ERROR = 2, - - PROFILE = 3, - + INIT_PROGRESS = 1, + ERROR = 2, + PROFILE = 3, } } @@ -175,11 +162,9 @@ export namespace InitResponse { export class FailedInstanceInitError extends jspb.Message { getReason(): FailedInstanceInitReason; setReason(value: FailedInstanceInitReason): FailedInstanceInitError; - getMessage(): string; setMessage(value: string): FailedInstanceInitError; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): FailedInstanceInitError.AsObject; static toObject(includeInstance: boolean, msg: FailedInstanceInitError): FailedInstanceInitError.AsObject; @@ -204,7 +189,6 @@ export class DestroyRequest extends jspb.Message { getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): DestroyRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DestroyRequest.AsObject; static toObject(includeInstance: boolean, msg: DestroyRequest): DestroyRequest.AsObject; @@ -244,11 +228,9 @@ export class UpdateIndexRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): UpdateIndexRequest; - getIgnoreCustomPackageIndexes(): boolean; setIgnoreCustomPackageIndexes(value: boolean): UpdateIndexRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UpdateIndexRequest.AsObject; static toObject(includeInstance: boolean, msg: UpdateIndexRequest): UpdateIndexRequest.AsObject; @@ -273,7 +255,6 @@ export class UpdateIndexResponse extends jspb.Message { getDownloadProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setDownloadProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): UpdateIndexResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UpdateIndexResponse.AsObject; static toObject(includeInstance: boolean, msg: UpdateIndexResponse): UpdateIndexResponse.AsObject; @@ -297,7 +278,6 @@ export class UpdateLibrariesIndexRequest extends jspb.Message { getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): UpdateLibrariesIndexRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UpdateLibrariesIndexRequest.AsObject; static toObject(includeInstance: boolean, msg: UpdateLibrariesIndexRequest): UpdateLibrariesIndexRequest.AsObject; @@ -321,7 +301,6 @@ export class UpdateLibrariesIndexResponse extends jspb.Message { getDownloadProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setDownloadProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): UpdateLibrariesIndexResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UpdateLibrariesIndexResponse.AsObject; static toObject(includeInstance: boolean, msg: UpdateLibrariesIndexResponse): UpdateLibrariesIndexResponse.AsObject; @@ -359,7 +338,6 @@ export class VersionResponse extends jspb.Message { getVersion(): string; setVersion(value: string): VersionResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): VersionResponse.AsObject; static toObject(includeInstance: boolean, msg: VersionResponse): VersionResponse.AsObject; @@ -377,22 +355,13 @@ export namespace VersionResponse { } export class NewSketchRequest extends jspb.Message { - - hasInstance(): boolean; - clearInstance(): void; - getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; - setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): NewSketchRequest; - getSketchName(): string; setSketchName(value: string): NewSketchRequest; - getSketchDir(): string; setSketchDir(value: string): NewSketchRequest; - getOverwrite(): boolean; setOverwrite(value: boolean): NewSketchRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): NewSketchRequest.AsObject; static toObject(includeInstance: boolean, msg: NewSketchRequest): NewSketchRequest.AsObject; @@ -405,7 +374,6 @@ export class NewSketchRequest extends jspb.Message { export namespace NewSketchRequest { export type AsObject = { - instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, sketchName: string, sketchDir: string, overwrite: boolean, @@ -416,7 +384,6 @@ export class NewSketchResponse extends jspb.Message { getMainFile(): string; setMainFile(value: string): NewSketchResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): NewSketchResponse.AsObject; static toObject(includeInstance: boolean, msg: NewSketchResponse): NewSketchResponse.AsObject; @@ -434,16 +401,9 @@ export namespace NewSketchResponse { } export class LoadSketchRequest extends jspb.Message { - - hasInstance(): boolean; - clearInstance(): void; - getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; - setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LoadSketchRequest; - getSketchPath(): string; setSketchPath(value: string): LoadSketchRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LoadSketchRequest.AsObject; static toObject(includeInstance: boolean, msg: LoadSketchRequest): LoadSketchRequest.AsObject; @@ -456,7 +416,6 @@ export class LoadSketchRequest extends jspb.Message { export namespace LoadSketchRequest { export type AsObject = { - instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, sketchPath: string, } } @@ -464,25 +423,26 @@ export namespace LoadSketchRequest { export class LoadSketchResponse extends jspb.Message { getMainFile(): string; setMainFile(value: string): LoadSketchResponse; - getLocationPath(): string; setLocationPath(value: string): LoadSketchResponse; - clearOtherSketchFilesList(): void; getOtherSketchFilesList(): Array; setOtherSketchFilesList(value: Array): LoadSketchResponse; addOtherSketchFiles(value: string, index?: number): string; - clearAdditionalFilesList(): void; getAdditionalFilesList(): Array; setAdditionalFilesList(value: Array): LoadSketchResponse; addAdditionalFiles(value: string, index?: number): string; - clearRootFolderFilesList(): void; getRootFolderFilesList(): Array; setRootFolderFilesList(value: Array): LoadSketchResponse; addRootFolderFiles(value: string, index?: number): string; - + getDefaultFqbn(): string; + setDefaultFqbn(value: string): LoadSketchResponse; + getDefaultPort(): string; + setDefaultPort(value: string): LoadSketchResponse; + getDefaultProtocol(): string; + setDefaultProtocol(value: string): LoadSketchResponse; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LoadSketchResponse.AsObject; @@ -501,23 +461,22 @@ export namespace LoadSketchResponse { otherSketchFilesList: Array, additionalFilesList: Array, rootFolderFilesList: Array, + defaultFqbn: string, + defaultPort: string, + defaultProtocol: string, } } export class ArchiveSketchRequest extends jspb.Message { getSketchPath(): string; setSketchPath(value: string): ArchiveSketchRequest; - getArchivePath(): string; setArchivePath(value: string): ArchiveSketchRequest; - getIncludeBuildDir(): boolean; setIncludeBuildDir(value: boolean): ArchiveSketchRequest; - getOverwrite(): boolean; setOverwrite(value: boolean): ArchiveSketchRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ArchiveSketchRequest.AsObject; static toObject(includeInstance: boolean, msg: ArchiveSketchRequest): ArchiveSketchRequest.AsObject; @@ -554,9 +513,65 @@ export namespace ArchiveSketchResponse { } } +export class SetSketchDefaultsRequest extends jspb.Message { + getSketchPath(): string; + setSketchPath(value: string): SetSketchDefaultsRequest; + getDefaultFqbn(): string; + setDefaultFqbn(value: string): SetSketchDefaultsRequest; + getDefaultPortAddress(): string; + setDefaultPortAddress(value: string): SetSketchDefaultsRequest; + getDefaultPortProtocol(): string; + setDefaultPortProtocol(value: string): SetSketchDefaultsRequest; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SetSketchDefaultsRequest.AsObject; + static toObject(includeInstance: boolean, msg: SetSketchDefaultsRequest): SetSketchDefaultsRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: SetSketchDefaultsRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SetSketchDefaultsRequest; + static deserializeBinaryFromReader(message: SetSketchDefaultsRequest, reader: jspb.BinaryReader): SetSketchDefaultsRequest; +} + +export namespace SetSketchDefaultsRequest { + export type AsObject = { + sketchPath: string, + defaultFqbn: string, + defaultPortAddress: string, + defaultPortProtocol: string, + } +} + +export class SetSketchDefaultsResponse extends jspb.Message { + getDefaultFqbn(): string; + setDefaultFqbn(value: string): SetSketchDefaultsResponse; + getDefaultPortAddress(): string; + setDefaultPortAddress(value: string): SetSketchDefaultsResponse; + getDefaultPortProtocol(): string; + setDefaultPortProtocol(value: string): SetSketchDefaultsResponse; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SetSketchDefaultsResponse.AsObject; + static toObject(includeInstance: boolean, msg: SetSketchDefaultsResponse): SetSketchDefaultsResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: SetSketchDefaultsResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SetSketchDefaultsResponse; + static deserializeBinaryFromReader(message: SetSketchDefaultsResponse, reader: jspb.BinaryReader): SetSketchDefaultsResponse; +} + +export namespace SetSketchDefaultsResponse { + export type AsObject = { + defaultFqbn: string, + defaultPortAddress: string, + defaultPortProtocol: string, + } +} + export enum FailedInstanceInitReason { FAILED_INSTANCE_INIT_REASON_UNSPECIFIED = 0, FAILED_INSTANCE_INIT_REASON_INVALID_INDEX_URL = 1, FAILED_INSTANCE_INIT_REASON_INDEX_LOAD_ERROR = 2, FAILED_INSTANCE_INIT_REASON_TOOL_LOAD_ERROR = 3, + FAILED_INSTANCE_INIT_REASON_INDEX_DOWNLOAD_ERROR = 4, } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js index ead2d3881..00132ca3b 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js @@ -53,6 +53,8 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LoadSketchRequest', null, gl goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LoadSketchResponse', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.NewSketchRequest', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.NewSketchResponse', null, global); +goog.exportSymbol('proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest', null, global); +goog.exportSymbol('proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UpdateIndexRequest', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UpdateIndexResponse', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UpdateLibrariesIndexRequest', null, global); @@ -479,6 +481,48 @@ if (goog.DEBUG && !COMPILED) { */ proto.cc.arduino.cli.commands.v1.ArchiveSketchResponse.displayName = 'proto.cc.arduino.cli.commands.v1.ArchiveSketchResponse'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.displayName = 'proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.displayName = 'proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse'; +} @@ -2733,7 +2777,6 @@ proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.toObject = function( */ proto.cc.arduino.cli.commands.v1.NewSketchRequest.toObject = function(includeInstance, msg) { var f, obj = { - instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), sketchName: jspb.Message.getFieldWithDefault(msg, 2, ""), sketchDir: jspb.Message.getFieldWithDefault(msg, 3, ""), overwrite: jspb.Message.getBooleanFieldWithDefault(msg, 4, false) @@ -2773,11 +2816,6 @@ proto.cc.arduino.cli.commands.v1.NewSketchRequest.deserializeBinaryFromReader = } var field = reader.getFieldNumber(); switch (field) { - case 1: - var value = new cc_arduino_cli_commands_v1_common_pb.Instance; - reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader); - msg.setInstance(value); - break; case 2: var value = /** @type {string} */ (reader.readString()); msg.setSketchName(value); @@ -2819,14 +2857,6 @@ proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.serializeBinary = fu */ proto.cc.arduino.cli.commands.v1.NewSketchRequest.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getInstance(); - if (f != null) { - writer.writeMessage( - 1, - f, - cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter - ); - } f = message.getSketchName(); if (f.length > 0) { writer.writeString( @@ -2851,43 +2881,6 @@ proto.cc.arduino.cli.commands.v1.NewSketchRequest.serializeBinaryToWriter = func }; -/** - * optional Instance instance = 1; - * @return {?proto.cc.arduino.cli.commands.v1.Instance} - */ -proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.getInstance = function() { - return /** @type{?proto.cc.arduino.cli.commands.v1.Instance} */ ( - jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.Instance, 1)); -}; - - -/** - * @param {?proto.cc.arduino.cli.commands.v1.Instance|undefined} value - * @return {!proto.cc.arduino.cli.commands.v1.NewSketchRequest} returns this -*/ -proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.setInstance = function(value) { - return jspb.Message.setWrapperField(this, 1, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.cc.arduino.cli.commands.v1.NewSketchRequest} returns this - */ -proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.clearInstance = function() { - return this.setInstance(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.cc.arduino.cli.commands.v1.NewSketchRequest.prototype.hasInstance = function() { - return jspb.Message.getField(this, 1) != null; -}; - - /** * optional string sketch_name = 2; * @return {string} @@ -3104,7 +3097,6 @@ proto.cc.arduino.cli.commands.v1.LoadSketchRequest.prototype.toObject = function */ proto.cc.arduino.cli.commands.v1.LoadSketchRequest.toObject = function(includeInstance, msg) { var f, obj = { - instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), sketchPath: jspb.Message.getFieldWithDefault(msg, 2, "") }; @@ -3142,11 +3134,6 @@ proto.cc.arduino.cli.commands.v1.LoadSketchRequest.deserializeBinaryFromReader = } var field = reader.getFieldNumber(); switch (field) { - case 1: - var value = new cc_arduino_cli_commands_v1_common_pb.Instance; - reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader); - msg.setInstance(value); - break; case 2: var value = /** @type {string} */ (reader.readString()); msg.setSketchPath(value); @@ -3180,14 +3167,6 @@ proto.cc.arduino.cli.commands.v1.LoadSketchRequest.prototype.serializeBinary = f */ proto.cc.arduino.cli.commands.v1.LoadSketchRequest.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getInstance(); - if (f != null) { - writer.writeMessage( - 1, - f, - cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter - ); - } f = message.getSketchPath(); if (f.length > 0) { writer.writeString( @@ -3198,43 +3177,6 @@ proto.cc.arduino.cli.commands.v1.LoadSketchRequest.serializeBinaryToWriter = fun }; -/** - * optional Instance instance = 1; - * @return {?proto.cc.arduino.cli.commands.v1.Instance} - */ -proto.cc.arduino.cli.commands.v1.LoadSketchRequest.prototype.getInstance = function() { - return /** @type{?proto.cc.arduino.cli.commands.v1.Instance} */ ( - jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.Instance, 1)); -}; - - -/** - * @param {?proto.cc.arduino.cli.commands.v1.Instance|undefined} value - * @return {!proto.cc.arduino.cli.commands.v1.LoadSketchRequest} returns this -*/ -proto.cc.arduino.cli.commands.v1.LoadSketchRequest.prototype.setInstance = function(value) { - return jspb.Message.setWrapperField(this, 1, value); -}; - - -/** - * Clears the message field making it undefined. - * @return {!proto.cc.arduino.cli.commands.v1.LoadSketchRequest} returns this - */ -proto.cc.arduino.cli.commands.v1.LoadSketchRequest.prototype.clearInstance = function() { - return this.setInstance(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.cc.arduino.cli.commands.v1.LoadSketchRequest.prototype.hasInstance = function() { - return jspb.Message.getField(this, 1) != null; -}; - - /** * optional string sketch_path = 2; * @return {string} @@ -3296,7 +3238,10 @@ proto.cc.arduino.cli.commands.v1.LoadSketchResponse.toObject = function(includeI locationPath: jspb.Message.getFieldWithDefault(msg, 2, ""), otherSketchFilesList: (f = jspb.Message.getRepeatedField(msg, 3)) == null ? undefined : f, additionalFilesList: (f = jspb.Message.getRepeatedField(msg, 4)) == null ? undefined : f, - rootFolderFilesList: (f = jspb.Message.getRepeatedField(msg, 5)) == null ? undefined : f + rootFolderFilesList: (f = jspb.Message.getRepeatedField(msg, 5)) == null ? undefined : f, + defaultFqbn: jspb.Message.getFieldWithDefault(msg, 6, ""), + defaultPort: jspb.Message.getFieldWithDefault(msg, 7, ""), + defaultProtocol: jspb.Message.getFieldWithDefault(msg, 8, "") }; if (includeInstance) { @@ -3353,6 +3298,18 @@ proto.cc.arduino.cli.commands.v1.LoadSketchResponse.deserializeBinaryFromReader var value = /** @type {string} */ (reader.readString()); msg.addRootFolderFiles(value); break; + case 6: + var value = /** @type {string} */ (reader.readString()); + msg.setDefaultFqbn(value); + break; + case 7: + var value = /** @type {string} */ (reader.readString()); + msg.setDefaultPort(value); + break; + case 8: + var value = /** @type {string} */ (reader.readString()); + msg.setDefaultProtocol(value); + break; default: reader.skipField(); break; @@ -3417,6 +3374,27 @@ proto.cc.arduino.cli.commands.v1.LoadSketchResponse.serializeBinaryToWriter = fu f ); } + f = message.getDefaultFqbn(); + if (f.length > 0) { + writer.writeString( + 6, + f + ); + } + f = message.getDefaultPort(); + if (f.length > 0) { + writer.writeString( + 7, + f + ); + } + f = message.getDefaultProtocol(); + if (f.length > 0) { + writer.writeString( + 8, + f + ); + } }; @@ -3567,6 +3545,60 @@ proto.cc.arduino.cli.commands.v1.LoadSketchResponse.prototype.clearRootFolderFil }; +/** + * optional string default_fqbn = 6; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.LoadSketchResponse.prototype.getDefaultFqbn = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 6, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.LoadSketchResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.LoadSketchResponse.prototype.setDefaultFqbn = function(value) { + return jspb.Message.setProto3StringField(this, 6, value); +}; + + +/** + * optional string default_port = 7; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.LoadSketchResponse.prototype.getDefaultPort = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 7, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.LoadSketchResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.LoadSketchResponse.prototype.setDefaultPort = function(value) { + return jspb.Message.setProto3StringField(this, 7, value); +}; + + +/** + * optional string default_protocol = 8; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.LoadSketchResponse.prototype.getDefaultProtocol = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 8, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.LoadSketchResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.LoadSketchResponse.prototype.setDefaultProtocol = function(value) { + return jspb.Message.setProto3StringField(this, 8, value); +}; + + @@ -3888,6 +3920,416 @@ proto.cc.arduino.cli.commands.v1.ArchiveSketchResponse.serializeBinaryToWriter = }; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.toObject = function(includeInstance, msg) { + var f, obj = { + sketchPath: jspb.Message.getFieldWithDefault(msg, 1, ""), + defaultFqbn: jspb.Message.getFieldWithDefault(msg, 2, ""), + defaultPortAddress: jspb.Message.getFieldWithDefault(msg, 3, ""), + defaultPortProtocol: jspb.Message.getFieldWithDefault(msg, 4, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest; + return proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setSketchPath(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setDefaultFqbn(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setDefaultPortAddress(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setDefaultPortProtocol(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getSketchPath(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getDefaultFqbn(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getDefaultPortAddress(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getDefaultPortProtocol(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } +}; + + +/** + * optional string sketch_path = 1; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.getSketchPath = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.setSketchPath = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string default_fqbn = 2; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.getDefaultFqbn = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.setDefaultFqbn = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string default_port_address = 3; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.getDefaultPortAddress = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.setDefaultPortAddress = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string default_port_protocol = 4; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.getDefaultPortProtocol = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsRequest.prototype.setDefaultPortProtocol = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.toObject = function(includeInstance, msg) { + var f, obj = { + defaultFqbn: jspb.Message.getFieldWithDefault(msg, 1, ""), + defaultPortAddress: jspb.Message.getFieldWithDefault(msg, 2, ""), + defaultPortProtocol: jspb.Message.getFieldWithDefault(msg, 3, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse; + return proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setDefaultFqbn(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setDefaultPortAddress(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setDefaultPortProtocol(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getDefaultFqbn(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getDefaultPortAddress(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getDefaultPortProtocol(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } +}; + + +/** + * optional string default_fqbn = 1; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.prototype.getDefaultFqbn = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.prototype.setDefaultFqbn = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string default_port_address = 2; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.prototype.getDefaultPortAddress = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.prototype.setDefaultPortAddress = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string default_port_protocol = 3; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.prototype.getDefaultPortProtocol = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.SetSketchDefaultsResponse.prototype.setDefaultPortProtocol = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + /** * @enum {number} */ @@ -3895,7 +4337,8 @@ proto.cc.arduino.cli.commands.v1.FailedInstanceInitReason = { FAILED_INSTANCE_INIT_REASON_UNSPECIFIED: 0, FAILED_INSTANCE_INIT_REASON_INVALID_INDEX_URL: 1, FAILED_INSTANCE_INIT_REASON_INDEX_LOAD_ERROR: 2, - FAILED_INSTANCE_INIT_REASON_TOOL_LOAD_ERROR: 3 + FAILED_INSTANCE_INIT_REASON_TOOL_LOAD_ERROR: 3, + FAILED_INSTANCE_INIT_REASON_INDEX_DOWNLOAD_ERROR: 4 }; goog.object.extend(exports, proto.cc.arduino.cli.commands.v1); diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts index 98d9117fe..7df25a374 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts @@ -10,7 +10,6 @@ export class Instance extends jspb.Message { getId(): number; setId(value: number): Instance; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Instance.AsObject; static toObject(includeInstance: boolean, msg: Instance): Instance.AsObject; @@ -34,19 +33,16 @@ export class DownloadProgress extends jspb.Message { getStart(): DownloadProgressStart | undefined; setStart(value?: DownloadProgressStart): DownloadProgress; - hasUpdate(): boolean; clearUpdate(): void; getUpdate(): DownloadProgressUpdate | undefined; setUpdate(value?: DownloadProgressUpdate): DownloadProgress; - hasEnd(): boolean; clearEnd(): void; getEnd(): DownloadProgressEnd | undefined; setEnd(value?: DownloadProgressEnd): DownloadProgress; - getMessageCase(): DownloadProgress.MessageCase; serializeBinary(): Uint8Array; @@ -68,13 +64,9 @@ export namespace DownloadProgress { export enum MessageCase { MESSAGE_NOT_SET = 0, - - START = 1, - - UPDATE = 2, - - END = 3, - + START = 1, + UPDATE = 2, + END = 3, } } @@ -82,11 +74,9 @@ export namespace DownloadProgress { export class DownloadProgressStart extends jspb.Message { getUrl(): string; setUrl(value: string): DownloadProgressStart; - getLabel(): string; setLabel(value: string): DownloadProgressStart; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DownloadProgressStart.AsObject; static toObject(includeInstance: boolean, msg: DownloadProgressStart): DownloadProgressStart.AsObject; @@ -107,11 +97,9 @@ export namespace DownloadProgressStart { export class DownloadProgressUpdate extends jspb.Message { getDownloaded(): number; setDownloaded(value: number): DownloadProgressUpdate; - getTotalSize(): number; setTotalSize(value: number): DownloadProgressUpdate; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DownloadProgressUpdate.AsObject; static toObject(includeInstance: boolean, msg: DownloadProgressUpdate): DownloadProgressUpdate.AsObject; @@ -132,11 +120,9 @@ export namespace DownloadProgressUpdate { export class DownloadProgressEnd extends jspb.Message { getSuccess(): boolean; setSuccess(value: boolean): DownloadProgressEnd; - getMessage(): string; setMessage(value: string): DownloadProgressEnd; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DownloadProgressEnd.AsObject; static toObject(includeInstance: boolean, msg: DownloadProgressEnd): DownloadProgressEnd.AsObject; @@ -157,17 +143,13 @@ export namespace DownloadProgressEnd { export class TaskProgress extends jspb.Message { getName(): string; setName(value: string): TaskProgress; - getMessage(): string; setMessage(value: string): TaskProgress; - getCompleted(): boolean; setCompleted(value: boolean): TaskProgress; - getPercent(): number; setPercent(value: number): TaskProgress; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): TaskProgress.AsObject; static toObject(includeInstance: boolean, msg: TaskProgress): TaskProgress.AsObject; @@ -190,14 +172,11 @@ export namespace TaskProgress { export class Programmer extends jspb.Message { getPlatform(): string; setPlatform(value: string): Programmer; - getId(): string; setId(value: string): Programmer; - getName(): string; setName(value: string): Programmer; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Programmer.AsObject; static toObject(includeInstance: boolean, msg: Programmer): Programmer.AsObject; @@ -219,54 +198,40 @@ export namespace Programmer { export class Platform extends jspb.Message { getId(): string; setId(value: string): Platform; - getInstalled(): string; setInstalled(value: string): Platform; - getLatest(): string; setLatest(value: string): Platform; - getName(): string; setName(value: string): Platform; - getMaintainer(): string; setMaintainer(value: string): Platform; - getWebsite(): string; setWebsite(value: string): Platform; - getEmail(): string; setEmail(value: string): Platform; - clearBoardsList(): void; getBoardsList(): Array; setBoardsList(value: Array): Platform; addBoards(value?: Board, index?: number): Board; - getManuallyInstalled(): boolean; setManuallyInstalled(value: boolean): Platform; - getDeprecated(): boolean; setDeprecated(value: boolean): Platform; - clearTypeList(): void; getTypeList(): Array; setTypeList(value: Array): Platform; addType(value: string, index?: number): string; - hasHelp(): boolean; clearHelp(): void; getHelp(): HelpResources | undefined; setHelp(value?: HelpResources): Platform; - getIndexed(): boolean; setIndexed(value: boolean): Platform; - getMissingMetadata(): boolean; setMissingMetadata(value: boolean): Platform; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Platform.AsObject; static toObject(includeInstance: boolean, msg: Platform): Platform.AsObject; @@ -299,17 +264,13 @@ export namespace Platform { export class InstalledPlatformReference extends jspb.Message { getId(): string; setId(value: string): InstalledPlatformReference; - getVersion(): string; setVersion(value: string): InstalledPlatformReference; - getInstallDir(): string; setInstallDir(value: string): InstalledPlatformReference; - getPackageUrl(): string; setPackageUrl(value: string): InstalledPlatformReference; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): InstalledPlatformReference.AsObject; static toObject(includeInstance: boolean, msg: InstalledPlatformReference): InstalledPlatformReference.AsObject; @@ -332,11 +293,9 @@ export namespace InstalledPlatformReference { export class Board extends jspb.Message { getName(): string; setName(value: string): Board; - getFqbn(): string; setFqbn(value: string): Board; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Board.AsObject; static toObject(includeInstance: boolean, msg: Board): Board.AsObject; @@ -357,11 +316,9 @@ export namespace Board { export class Profile extends jspb.Message { getName(): string; setName(value: string): Profile; - getFqbn(): string; setFqbn(value: string): Profile; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Profile.AsObject; static toObject(includeInstance: boolean, msg: Profile): Profile.AsObject; @@ -383,7 +340,6 @@ export class HelpResources extends jspb.Message { getOnline(): string; setOnline(value: string): HelpResources; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): HelpResources.AsObject; static toObject(includeInstance: boolean, msg: HelpResources): HelpResources.AsObject; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts index a4633d90d..d24160005 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts @@ -15,90 +15,65 @@ export class CompileRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): CompileRequest; - getFqbn(): string; setFqbn(value: string): CompileRequest; - getSketchPath(): string; setSketchPath(value: string): CompileRequest; - getShowProperties(): boolean; setShowProperties(value: boolean): CompileRequest; - getPreprocess(): boolean; setPreprocess(value: boolean): CompileRequest; - getBuildCachePath(): string; setBuildCachePath(value: string): CompileRequest; - getBuildPath(): string; setBuildPath(value: string): CompileRequest; - clearBuildPropertiesList(): void; getBuildPropertiesList(): Array; setBuildPropertiesList(value: Array): CompileRequest; addBuildProperties(value: string, index?: number): string; - getWarnings(): string; setWarnings(value: string): CompileRequest; - getVerbose(): boolean; setVerbose(value: boolean): CompileRequest; - getQuiet(): boolean; setQuiet(value: boolean): CompileRequest; - getJobs(): number; setJobs(value: number): CompileRequest; - clearLibrariesList(): void; getLibrariesList(): Array; setLibrariesList(value: Array): CompileRequest; addLibraries(value: string, index?: number): string; - getOptimizeForDebug(): boolean; setOptimizeForDebug(value: boolean): CompileRequest; - getExportDir(): string; setExportDir(value: string): CompileRequest; - getClean(): boolean; setClean(value: boolean): CompileRequest; - getCreateCompilationDatabaseOnly(): boolean; setCreateCompilationDatabaseOnly(value: boolean): CompileRequest; - getSourceOverrideMap(): jspb.Map; clearSourceOverrideMap(): void; - hasExportBinaries(): boolean; clearExportBinaries(): void; getExportBinaries(): google_protobuf_wrappers_pb.BoolValue | undefined; setExportBinaries(value?: google_protobuf_wrappers_pb.BoolValue): CompileRequest; - clearLibraryList(): void; getLibraryList(): Array; setLibraryList(value: Array): CompileRequest; addLibrary(value: string, index?: number): string; - getKeysKeychain(): string; setKeysKeychain(value: string): CompileRequest; - getSignKey(): string; setSignKey(value: string): CompileRequest; - getEncryptKey(): string; setEncryptKey(value: string): CompileRequest; - getSkipLibrariesDiscovery(): boolean; setSkipLibrariesDiscovery(value: boolean): CompileRequest; - getDoNotExpandBuildProperties(): boolean; setDoNotExpandBuildProperties(value: boolean): CompileRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): CompileRequest.AsObject; static toObject(includeInstance: boolean, msg: CompileRequest): CompileRequest.AsObject; @@ -145,49 +120,40 @@ export class CompileResponse extends jspb.Message { getOutStream_asU8(): Uint8Array; getOutStream_asB64(): string; setOutStream(value: Uint8Array | string): CompileResponse; - getErrStream(): Uint8Array | string; getErrStream_asU8(): Uint8Array; getErrStream_asB64(): string; setErrStream(value: Uint8Array | string): CompileResponse; - getBuildPath(): string; setBuildPath(value: string): CompileResponse; - clearUsedLibrariesList(): void; getUsedLibrariesList(): Array; setUsedLibrariesList(value: Array): CompileResponse; addUsedLibraries(value?: cc_arduino_cli_commands_v1_lib_pb.Library, index?: number): cc_arduino_cli_commands_v1_lib_pb.Library; - clearExecutableSectionsSizeList(): void; getExecutableSectionsSizeList(): Array; setExecutableSectionsSizeList(value: Array): CompileResponse; addExecutableSectionsSize(value?: ExecutableSectionSize, index?: number): ExecutableSectionSize; - hasBoardPlatform(): boolean; clearBoardPlatform(): void; getBoardPlatform(): cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference | undefined; setBoardPlatform(value?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference): CompileResponse; - hasBuildPlatform(): boolean; clearBuildPlatform(): void; getBuildPlatform(): cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference | undefined; setBuildPlatform(value?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference): CompileResponse; - hasProgress(): boolean; clearProgress(): void; getProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): CompileResponse; - clearBuildPropertiesList(): void; getBuildPropertiesList(): Array; setBuildPropertiesList(value: Array): CompileResponse; addBuildProperties(value: string, index?: number): string; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): CompileResponse.AsObject; static toObject(includeInstance: boolean, msg: CompileResponse): CompileResponse.AsObject; @@ -215,14 +181,11 @@ export namespace CompileResponse { export class ExecutableSectionSize extends jspb.Message { getName(): string; setName(value: string): ExecutableSectionSize; - getSize(): number; setSize(value: number): ExecutableSectionSize; - getMaxSize(): number; setMaxSize(value: number): ExecutableSectionSize; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ExecutableSectionSize.AsObject; static toObject(includeInstance: boolean, msg: ExecutableSectionSize): ExecutableSectionSize.AsObject; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.d.ts index a4d62a8f7..3034f8446 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.d.ts @@ -13,23 +13,17 @@ export class PlatformInstallRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): PlatformInstallRequest; - getPlatformPackage(): string; setPlatformPackage(value: string): PlatformInstallRequest; - getArchitecture(): string; setArchitecture(value: string): PlatformInstallRequest; - getVersion(): string; setVersion(value: string): PlatformInstallRequest; - getSkipPostInstall(): boolean; setSkipPostInstall(value: boolean): PlatformInstallRequest; - getNoOverwrite(): boolean; setNoOverwrite(value: boolean): PlatformInstallRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformInstallRequest.AsObject; static toObject(includeInstance: boolean, msg: PlatformInstallRequest): PlatformInstallRequest.AsObject; @@ -58,13 +52,11 @@ export class PlatformInstallResponse extends jspb.Message { getProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): PlatformInstallResponse; - hasTaskProgress(): boolean; clearTaskProgress(): void; getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): PlatformInstallResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformInstallResponse.AsObject; static toObject(includeInstance: boolean, msg: PlatformInstallResponse): PlatformInstallResponse.AsObject; @@ -105,17 +97,13 @@ export class PlatformDownloadRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): PlatformDownloadRequest; - getPlatformPackage(): string; setPlatformPackage(value: string): PlatformDownloadRequest; - getArchitecture(): string; setArchitecture(value: string): PlatformDownloadRequest; - getVersion(): string; setVersion(value: string): PlatformDownloadRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformDownloadRequest.AsObject; static toObject(includeInstance: boolean, msg: PlatformDownloadRequest): PlatformDownloadRequest.AsObject; @@ -142,7 +130,6 @@ export class PlatformDownloadResponse extends jspb.Message { getProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): PlatformDownloadResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformDownloadResponse.AsObject; static toObject(includeInstance: boolean, msg: PlatformDownloadResponse): PlatformDownloadResponse.AsObject; @@ -165,14 +152,11 @@ export class PlatformUninstallRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): PlatformUninstallRequest; - getPlatformPackage(): string; setPlatformPackage(value: string): PlatformUninstallRequest; - getArchitecture(): string; setArchitecture(value: string): PlatformUninstallRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformUninstallRequest.AsObject; static toObject(includeInstance: boolean, msg: PlatformUninstallRequest): PlatformUninstallRequest.AsObject; @@ -198,7 +182,6 @@ export class PlatformUninstallResponse extends jspb.Message { getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): PlatformUninstallResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformUninstallResponse.AsObject; static toObject(includeInstance: boolean, msg: PlatformUninstallResponse): PlatformUninstallResponse.AsObject; @@ -238,17 +221,13 @@ export class PlatformUpgradeRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): PlatformUpgradeRequest; - getPlatformPackage(): string; setPlatformPackage(value: string): PlatformUpgradeRequest; - getArchitecture(): string; setArchitecture(value: string): PlatformUpgradeRequest; - getSkipPostInstall(): boolean; setSkipPostInstall(value: boolean): PlatformUpgradeRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformUpgradeRequest.AsObject; static toObject(includeInstance: boolean, msg: PlatformUpgradeRequest): PlatformUpgradeRequest.AsObject; @@ -275,19 +254,16 @@ export class PlatformUpgradeResponse extends jspb.Message { getProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): PlatformUpgradeResponse; - hasTaskProgress(): boolean; clearTaskProgress(): void; getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): PlatformUpgradeResponse; - hasPlatform(): boolean; clearPlatform(): void; getPlatform(): cc_arduino_cli_commands_v1_common_pb.Platform | undefined; setPlatform(value?: cc_arduino_cli_commands_v1_common_pb.Platform): PlatformUpgradeResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformUpgradeResponse.AsObject; static toObject(includeInstance: boolean, msg: PlatformUpgradeResponse): PlatformUpgradeResponse.AsObject; @@ -312,14 +288,11 @@ export class PlatformSearchRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): PlatformSearchRequest; - getSearchArgs(): string; setSearchArgs(value: string): PlatformSearchRequest; - getAllVersions(): boolean; setAllVersions(value: boolean): PlatformSearchRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformSearchRequest.AsObject; static toObject(includeInstance: boolean, msg: PlatformSearchRequest): PlatformSearchRequest.AsObject; @@ -344,7 +317,6 @@ export class PlatformSearchResponse extends jspb.Message { setSearchOutputList(value: Array): PlatformSearchResponse; addSearchOutput(value?: cc_arduino_cli_commands_v1_common_pb.Platform, index?: number): cc_arduino_cli_commands_v1_common_pb.Platform; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformSearchResponse.AsObject; static toObject(includeInstance: boolean, msg: PlatformSearchResponse): PlatformSearchResponse.AsObject; @@ -367,14 +339,11 @@ export class PlatformListRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): PlatformListRequest; - getUpdatableOnly(): boolean; setUpdatableOnly(value: boolean): PlatformListRequest; - getAll(): boolean; setAll(value: boolean): PlatformListRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformListRequest.AsObject; static toObject(includeInstance: boolean, msg: PlatformListRequest): PlatformListRequest.AsObject; @@ -399,7 +368,6 @@ export class PlatformListResponse extends jspb.Message { setInstalledPlatformsList(value: Array): PlatformListResponse; addInstalledPlatforms(value?: cc_arduino_cli_commands_v1_common_pb.Platform, index?: number): cc_arduino_cli_commands_v1_common_pb.Platform; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformListResponse.AsObject; static toObject(includeInstance: boolean, msg: PlatformListResponse): PlatformListResponse.AsObject; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts index 27866d15a..e0b50ba58 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts @@ -13,14 +13,11 @@ export class LibraryDownloadRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibraryDownloadRequest; - getName(): string; setName(value: string): LibraryDownloadRequest; - getVersion(): string; setVersion(value: string): LibraryDownloadRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryDownloadRequest.AsObject; static toObject(includeInstance: boolean, msg: LibraryDownloadRequest): LibraryDownloadRequest.AsObject; @@ -46,7 +43,6 @@ export class LibraryDownloadResponse extends jspb.Message { getProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): LibraryDownloadResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryDownloadResponse.AsObject; static toObject(includeInstance: boolean, msg: LibraryDownloadResponse): LibraryDownloadResponse.AsObject; @@ -69,23 +65,17 @@ export class LibraryInstallRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibraryInstallRequest; - getName(): string; setName(value: string): LibraryInstallRequest; - getVersion(): string; setVersion(value: string): LibraryInstallRequest; - getNoDeps(): boolean; setNoDeps(value: boolean): LibraryInstallRequest; - getNoOverwrite(): boolean; setNoOverwrite(value: boolean): LibraryInstallRequest; - getInstallLocation(): LibraryInstallLocation; setInstallLocation(value: LibraryInstallLocation): LibraryInstallRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryInstallRequest.AsObject; static toObject(includeInstance: boolean, msg: LibraryInstallRequest): LibraryInstallRequest.AsObject; @@ -114,13 +104,11 @@ export class LibraryInstallResponse extends jspb.Message { getProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): LibraryInstallResponse; - hasTaskProgress(): boolean; clearTaskProgress(): void; getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): LibraryInstallResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryInstallResponse.AsObject; static toObject(includeInstance: boolean, msg: LibraryInstallResponse): LibraryInstallResponse.AsObject; @@ -144,14 +132,11 @@ export class LibraryUpgradeRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibraryUpgradeRequest; - getName(): string; setName(value: string): LibraryUpgradeRequest; - getNoDeps(): boolean; setNoDeps(value: boolean): LibraryUpgradeRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryUpgradeRequest.AsObject; static toObject(includeInstance: boolean, msg: LibraryUpgradeRequest): LibraryUpgradeRequest.AsObject; @@ -177,13 +162,11 @@ export class LibraryUpgradeResponse extends jspb.Message { getProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): LibraryUpgradeResponse; - hasTaskProgress(): boolean; clearTaskProgress(): void; getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): LibraryUpgradeResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryUpgradeResponse.AsObject; static toObject(includeInstance: boolean, msg: LibraryUpgradeResponse): LibraryUpgradeResponse.AsObject; @@ -207,14 +190,11 @@ export class LibraryUninstallRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibraryUninstallRequest; - getName(): string; setName(value: string): LibraryUninstallRequest; - getVersion(): string; setVersion(value: string): LibraryUninstallRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryUninstallRequest.AsObject; static toObject(includeInstance: boolean, msg: LibraryUninstallRequest): LibraryUninstallRequest.AsObject; @@ -240,7 +220,6 @@ export class LibraryUninstallResponse extends jspb.Message { getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): LibraryUninstallResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryUninstallResponse.AsObject; static toObject(includeInstance: boolean, msg: LibraryUninstallResponse): LibraryUninstallResponse.AsObject; @@ -264,7 +243,6 @@ export class LibraryUpgradeAllRequest extends jspb.Message { getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibraryUpgradeAllRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryUpgradeAllRequest.AsObject; static toObject(includeInstance: boolean, msg: LibraryUpgradeAllRequest): LibraryUpgradeAllRequest.AsObject; @@ -288,13 +266,11 @@ export class LibraryUpgradeAllResponse extends jspb.Message { getProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined; setProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): LibraryUpgradeAllResponse; - hasTaskProgress(): boolean; clearTaskProgress(): void; getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): LibraryUpgradeAllResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryUpgradeAllResponse.AsObject; static toObject(includeInstance: boolean, msg: LibraryUpgradeAllResponse): LibraryUpgradeAllResponse.AsObject; @@ -318,14 +294,11 @@ export class LibraryResolveDependenciesRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibraryResolveDependenciesRequest; - getName(): string; setName(value: string): LibraryResolveDependenciesRequest; - getVersion(): string; setVersion(value: string): LibraryResolveDependenciesRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryResolveDependenciesRequest.AsObject; static toObject(includeInstance: boolean, msg: LibraryResolveDependenciesRequest): LibraryResolveDependenciesRequest.AsObject; @@ -350,7 +323,6 @@ export class LibraryResolveDependenciesResponse extends jspb.Message { setDependenciesList(value: Array): LibraryResolveDependenciesResponse; addDependencies(value?: LibraryDependencyStatus, index?: number): LibraryDependencyStatus; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryResolveDependenciesResponse.AsObject; static toObject(includeInstance: boolean, msg: LibraryResolveDependenciesResponse): LibraryResolveDependenciesResponse.AsObject; @@ -370,14 +342,11 @@ export namespace LibraryResolveDependenciesResponse { export class LibraryDependencyStatus extends jspb.Message { getName(): string; setName(value: string): LibraryDependencyStatus; - getVersionRequired(): string; setVersionRequired(value: string): LibraryDependencyStatus; - getVersionInstalled(): string; setVersionInstalled(value: string): LibraryDependencyStatus; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryDependencyStatus.AsObject; static toObject(includeInstance: boolean, msg: LibraryDependencyStatus): LibraryDependencyStatus.AsObject; @@ -402,13 +371,12 @@ export class LibrarySearchRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibrarySearchRequest; - getQuery(): string; setQuery(value: string): LibrarySearchRequest; - getOmitReleasesDetails(): boolean; setOmitReleasesDetails(value: boolean): LibrarySearchRequest; - + getSearchArgs(): string; + setSearchArgs(value: string): LibrarySearchRequest; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibrarySearchRequest.AsObject; @@ -425,6 +393,7 @@ export namespace LibrarySearchRequest { instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, query: string, omitReleasesDetails: boolean, + searchArgs: string, } } @@ -433,11 +402,9 @@ export class LibrarySearchResponse extends jspb.Message { getLibrariesList(): Array; setLibrariesList(value: Array): LibrarySearchResponse; addLibraries(value?: SearchedLibrary, index?: number): SearchedLibrary; - getStatus(): LibrarySearchStatus; setStatus(value: LibrarySearchStatus): LibrarySearchResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibrarySearchResponse.AsObject; static toObject(includeInstance: boolean, msg: LibrarySearchResponse): LibrarySearchResponse.AsObject; @@ -459,22 +426,18 @@ export class SearchedLibrary extends jspb.Message { getName(): string; setName(value: string): SearchedLibrary; - getReleasesMap(): jspb.Map; clearReleasesMap(): void; - hasLatest(): boolean; clearLatest(): void; getLatest(): LibraryRelease | undefined; setLatest(value?: LibraryRelease): SearchedLibrary; - clearAvailableVersionsList(): void; getAvailableVersionsList(): Array; setAvailableVersionsList(value: Array): SearchedLibrary; addAvailableVersions(value: string, index?: number): string; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SearchedLibrary.AsObject; static toObject(includeInstance: boolean, msg: SearchedLibrary): SearchedLibrary.AsObject; @@ -498,55 +461,42 @@ export namespace SearchedLibrary { export class LibraryRelease extends jspb.Message { getAuthor(): string; setAuthor(value: string): LibraryRelease; - getVersion(): string; setVersion(value: string): LibraryRelease; - getMaintainer(): string; setMaintainer(value: string): LibraryRelease; - getSentence(): string; setSentence(value: string): LibraryRelease; - getParagraph(): string; setParagraph(value: string): LibraryRelease; - getWebsite(): string; setWebsite(value: string): LibraryRelease; - getCategory(): string; setCategory(value: string): LibraryRelease; - clearArchitecturesList(): void; getArchitecturesList(): Array; setArchitecturesList(value: Array): LibraryRelease; addArchitectures(value: string, index?: number): string; - clearTypesList(): void; getTypesList(): Array; setTypesList(value: Array): LibraryRelease; addTypes(value: string, index?: number): string; - hasResources(): boolean; clearResources(): void; getResources(): DownloadResource | undefined; setResources(value?: DownloadResource): LibraryRelease; - getLicense(): string; setLicense(value: string): LibraryRelease; - clearProvidesIncludesList(): void; getProvidesIncludesList(): Array; setProvidesIncludesList(value: Array): LibraryRelease; addProvidesIncludes(value: string, index?: number): string; - clearDependenciesList(): void; getDependenciesList(): Array; setDependenciesList(value: Array): LibraryRelease; addDependencies(value?: LibraryDependency, index?: number): LibraryDependency; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryRelease.AsObject; static toObject(includeInstance: boolean, msg: LibraryRelease): LibraryRelease.AsObject; @@ -578,11 +528,9 @@ export namespace LibraryRelease { export class LibraryDependency extends jspb.Message { getName(): string; setName(value: string): LibraryDependency; - getVersionConstraint(): string; setVersionConstraint(value: string): LibraryDependency; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryDependency.AsObject; static toObject(includeInstance: boolean, msg: LibraryDependency): LibraryDependency.AsObject; @@ -603,20 +551,15 @@ export namespace LibraryDependency { export class DownloadResource extends jspb.Message { getUrl(): string; setUrl(value: string): DownloadResource; - getArchiveFilename(): string; setArchiveFilename(value: string): DownloadResource; - getChecksum(): string; setChecksum(value: string): DownloadResource; - getSize(): number; setSize(value: number): DownloadResource; - getCachePath(): string; setCachePath(value: string): DownloadResource; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DownloadResource.AsObject; static toObject(includeInstance: boolean, msg: DownloadResource): DownloadResource.AsObject; @@ -643,20 +586,15 @@ export class LibraryListRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibraryListRequest; - getAll(): boolean; setAll(value: boolean): LibraryListRequest; - getUpdatable(): boolean; setUpdatable(value: boolean): LibraryListRequest; - getName(): string; setName(value: string): LibraryListRequest; - getFqbn(): string; setFqbn(value: string): LibraryListRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryListRequest.AsObject; static toObject(includeInstance: boolean, msg: LibraryListRequest): LibraryListRequest.AsObject; @@ -683,7 +621,6 @@ export class LibraryListResponse extends jspb.Message { setInstalledLibrariesList(value: Array): LibraryListResponse; addInstalledLibraries(value?: InstalledLibrary, index?: number): InstalledLibrary; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryListResponse.AsObject; static toObject(includeInstance: boolean, msg: LibraryListResponse): LibraryListResponse.AsObject; @@ -707,13 +644,11 @@ export class InstalledLibrary extends jspb.Message { getLibrary(): Library | undefined; setLibrary(value?: Library): InstalledLibrary; - hasRelease(): boolean; clearRelease(): void; getRelease(): LibraryRelease | undefined; setRelease(value?: LibraryRelease): InstalledLibrary; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): InstalledLibrary.AsObject; static toObject(includeInstance: boolean, msg: InstalledLibrary): InstalledLibrary.AsObject; @@ -734,93 +669,67 @@ export namespace InstalledLibrary { export class Library extends jspb.Message { getName(): string; setName(value: string): Library; - getAuthor(): string; setAuthor(value: string): Library; - getMaintainer(): string; setMaintainer(value: string): Library; - getSentence(): string; setSentence(value: string): Library; - getParagraph(): string; setParagraph(value: string): Library; - getWebsite(): string; setWebsite(value: string): Library; - getCategory(): string; setCategory(value: string): Library; - clearArchitecturesList(): void; getArchitecturesList(): Array; setArchitecturesList(value: Array): Library; addArchitectures(value: string, index?: number): string; - clearTypesList(): void; getTypesList(): Array; setTypesList(value: Array): Library; addTypes(value: string, index?: number): string; - getInstallDir(): string; setInstallDir(value: string): Library; - getSourceDir(): string; setSourceDir(value: string): Library; - getUtilityDir(): string; setUtilityDir(value: string): Library; - getContainerPlatform(): string; setContainerPlatform(value: string): Library; - getDotALinkage(): boolean; setDotALinkage(value: boolean): Library; - getPrecompiled(): boolean; setPrecompiled(value: boolean): Library; - getLdFlags(): string; setLdFlags(value: string): Library; - getIsLegacy(): boolean; setIsLegacy(value: boolean): Library; - getVersion(): string; setVersion(value: string): Library; - getLicense(): string; setLicense(value: string): Library; - getPropertiesMap(): jspb.Map; clearPropertiesMap(): void; - getLocation(): LibraryLocation; setLocation(value: LibraryLocation): Library; - getLayout(): LibraryLayout; setLayout(value: LibraryLayout): Library; - clearExamplesList(): void; getExamplesList(): Array; setExamplesList(value: Array): Library; addExamples(value: string, index?: number): string; - clearProvidesIncludesList(): void; getProvidesIncludesList(): Array; setProvidesIncludesList(value: Array): Library; addProvidesIncludes(value: string, index?: number): string; - getCompatibleWithMap(): jspb.Map; clearCompatibleWithMap(): void; - getInDevelopment(): boolean; setInDevelopment(value: boolean): Library; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Library.AsObject; static toObject(includeInstance: boolean, msg: Library): Library.AsObject; @@ -870,14 +779,11 @@ export class ZipLibraryInstallRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): ZipLibraryInstallRequest; - getPath(): string; setPath(value: string): ZipLibraryInstallRequest; - getOverwrite(): boolean; setOverwrite(value: boolean): ZipLibraryInstallRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ZipLibraryInstallRequest.AsObject; static toObject(includeInstance: boolean, msg: ZipLibraryInstallRequest): ZipLibraryInstallRequest.AsObject; @@ -903,7 +809,6 @@ export class ZipLibraryInstallResponse extends jspb.Message { getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): ZipLibraryInstallResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ZipLibraryInstallResponse.AsObject; static toObject(includeInstance: boolean, msg: ZipLibraryInstallResponse): ZipLibraryInstallResponse.AsObject; @@ -926,14 +831,11 @@ export class GitLibraryInstallRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): GitLibraryInstallRequest; - getUrl(): string; setUrl(value: string): GitLibraryInstallRequest; - getOverwrite(): boolean; setOverwrite(value: boolean): GitLibraryInstallRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): GitLibraryInstallRequest.AsObject; static toObject(includeInstance: boolean, msg: GitLibraryInstallRequest): GitLibraryInstallRequest.AsObject; @@ -959,7 +861,6 @@ export class GitLibraryInstallResponse extends jspb.Message { getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined; setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): GitLibraryInstallResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): GitLibraryInstallResponse.AsObject; static toObject(includeInstance: boolean, msg: GitLibraryInstallResponse): GitLibraryInstallResponse.AsObject; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js index fa7cc1365..7ec2e0bbf 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js @@ -3209,7 +3209,8 @@ proto.cc.arduino.cli.commands.v1.LibrarySearchRequest.toObject = function(includ var f, obj = { instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), query: jspb.Message.getFieldWithDefault(msg, 2, ""), - omitReleasesDetails: jspb.Message.getBooleanFieldWithDefault(msg, 3, false) + omitReleasesDetails: jspb.Message.getBooleanFieldWithDefault(msg, 3, false), + searchArgs: jspb.Message.getFieldWithDefault(msg, 4, "") }; if (includeInstance) { @@ -3259,6 +3260,10 @@ proto.cc.arduino.cli.commands.v1.LibrarySearchRequest.deserializeBinaryFromReade var value = /** @type {boolean} */ (reader.readBool()); msg.setOmitReleasesDetails(value); break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setSearchArgs(value); + break; default: reader.skipField(); break; @@ -3310,6 +3315,13 @@ proto.cc.arduino.cli.commands.v1.LibrarySearchRequest.serializeBinaryToWriter = f ); } + f = message.getSearchArgs(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } }; @@ -3386,6 +3398,24 @@ proto.cc.arduino.cli.commands.v1.LibrarySearchRequest.prototype.setOmitReleasesD }; +/** + * optional string search_args = 4; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.LibrarySearchRequest.prototype.getSearchArgs = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.LibrarySearchRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.LibrarySearchRequest.prototype.setSearchArgs = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + /** * List of repeated fields within this message type. diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts index 4d65f4723..49140fcb5 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts @@ -15,27 +15,22 @@ export class MonitorRequest extends jspb.Message { getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): MonitorRequest; - hasPort(): boolean; clearPort(): void; getPort(): cc_arduino_cli_commands_v1_port_pb.Port | undefined; setPort(value?: cc_arduino_cli_commands_v1_port_pb.Port): MonitorRequest; - getFqbn(): string; setFqbn(value: string): MonitorRequest; - getTxData(): Uint8Array | string; getTxData_asU8(): Uint8Array; getTxData_asB64(): string; setTxData(value: Uint8Array | string): MonitorRequest; - hasPortConfiguration(): boolean; clearPortConfiguration(): void; getPortConfiguration(): MonitorPortConfiguration | undefined; setPortConfiguration(value?: MonitorPortConfiguration): MonitorRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): MonitorRequest.AsObject; static toObject(includeInstance: boolean, msg: MonitorRequest): MonitorRequest.AsObject; @@ -62,7 +57,6 @@ export class MonitorPortConfiguration extends jspb.Message { setSettingsList(value: Array): MonitorPortConfiguration; addSettings(value?: MonitorPortSetting, index?: number): MonitorPortSetting; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): MonitorPortConfiguration.AsObject; static toObject(includeInstance: boolean, msg: MonitorPortConfiguration): MonitorPortConfiguration.AsObject; @@ -82,21 +76,17 @@ export namespace MonitorPortConfiguration { export class MonitorResponse extends jspb.Message { getError(): string; setError(value: string): MonitorResponse; - getRxData(): Uint8Array | string; getRxData_asU8(): Uint8Array; getRxData_asB64(): string; setRxData(value: Uint8Array | string): MonitorResponse; - clearAppliedSettingsList(): void; getAppliedSettingsList(): Array; setAppliedSettingsList(value: Array): MonitorResponse; addAppliedSettings(value?: MonitorPortSetting, index?: number): MonitorPortSetting; - getSuccess(): boolean; setSuccess(value: boolean): MonitorResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): MonitorResponse.AsObject; static toObject(includeInstance: boolean, msg: MonitorResponse): MonitorResponse.AsObject; @@ -119,11 +109,9 @@ export namespace MonitorResponse { export class MonitorPortSetting extends jspb.Message { getSettingId(): string; setSettingId(value: string): MonitorPortSetting; - getValue(): string; setValue(value: string): MonitorPortSetting; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): MonitorPortSetting.AsObject; static toObject(includeInstance: boolean, msg: MonitorPortSetting): MonitorPortSetting.AsObject; @@ -147,14 +135,11 @@ export class EnumerateMonitorPortSettingsRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): EnumerateMonitorPortSettingsRequest; - getPortProtocol(): string; setPortProtocol(value: string): EnumerateMonitorPortSettingsRequest; - getFqbn(): string; setFqbn(value: string): EnumerateMonitorPortSettingsRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): EnumerateMonitorPortSettingsRequest.AsObject; static toObject(includeInstance: boolean, msg: EnumerateMonitorPortSettingsRequest): EnumerateMonitorPortSettingsRequest.AsObject; @@ -179,7 +164,6 @@ export class EnumerateMonitorPortSettingsResponse extends jspb.Message { setSettingsList(value: Array): EnumerateMonitorPortSettingsResponse; addSettings(value?: MonitorPortSettingDescriptor, index?: number): MonitorPortSettingDescriptor; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): EnumerateMonitorPortSettingsResponse.AsObject; static toObject(includeInstance: boolean, msg: EnumerateMonitorPortSettingsResponse): EnumerateMonitorPortSettingsResponse.AsObject; @@ -199,22 +183,17 @@ export namespace EnumerateMonitorPortSettingsResponse { export class MonitorPortSettingDescriptor extends jspb.Message { getSettingId(): string; setSettingId(value: string): MonitorPortSettingDescriptor; - getLabel(): string; setLabel(value: string): MonitorPortSettingDescriptor; - getType(): string; setType(value: string): MonitorPortSettingDescriptor; - clearEnumValuesList(): void; getEnumValuesList(): Array; setEnumValuesList(value: Array): MonitorPortSettingDescriptor; addEnumValues(value: string, index?: number): string; - getValue(): string; setValue(value: string): MonitorPortSettingDescriptor; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): MonitorPortSettingDescriptor.AsObject; static toObject(includeInstance: boolean, msg: MonitorPortSettingDescriptor): MonitorPortSettingDescriptor.AsObject; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb.d.ts index 17c863eea..8904be349 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb.d.ts @@ -9,24 +9,18 @@ import * as jspb from "google-protobuf"; export class Port extends jspb.Message { getAddress(): string; setAddress(value: string): Port; - getLabel(): string; setLabel(value: string): Port; - getProtocol(): string; setProtocol(value: string): Port; - getProtocolLabel(): string; setProtocolLabel(value: string): Port; - getPropertiesMap(): jspb.Map; clearPropertiesMap(): void; - getHardwareId(): string; setHardwareId(value: string): Port; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Port.AsObject; static toObject(includeInstance: boolean, msg: Port): Port.AsObject; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts index 4427473a6..6c82e5557 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts @@ -14,42 +14,31 @@ export class UploadRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): UploadRequest; - getFqbn(): string; setFqbn(value: string): UploadRequest; - getSketchPath(): string; setSketchPath(value: string): UploadRequest; - hasPort(): boolean; clearPort(): void; getPort(): cc_arduino_cli_commands_v1_port_pb.Port | undefined; setPort(value?: cc_arduino_cli_commands_v1_port_pb.Port): UploadRequest; - getVerbose(): boolean; setVerbose(value: boolean): UploadRequest; - getVerify(): boolean; setVerify(value: boolean): UploadRequest; - getImportFile(): string; setImportFile(value: string): UploadRequest; - getImportDir(): string; setImportDir(value: string): UploadRequest; - getProgrammer(): string; setProgrammer(value: string): UploadRequest; - getDryRun(): boolean; setDryRun(value: boolean): UploadRequest; - getUserFieldsMap(): jspb.Map; clearUserFieldsMap(): void; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UploadRequest.AsObject; static toObject(includeInstance: boolean, msg: UploadRequest): UploadRequest.AsObject; @@ -78,16 +67,27 @@ export namespace UploadRequest { } export class UploadResponse extends jspb.Message { + + hasOutStream(): boolean; + clearOutStream(): void; getOutStream(): Uint8Array | string; getOutStream_asU8(): Uint8Array; getOutStream_asB64(): string; setOutStream(value: Uint8Array | string): UploadResponse; + hasErrStream(): boolean; + clearErrStream(): void; getErrStream(): Uint8Array | string; getErrStream_asU8(): Uint8Array; getErrStream_asB64(): string; setErrStream(value: Uint8Array | string): UploadResponse; + hasResult(): boolean; + clearResult(): void; + getResult(): UploadResult | undefined; + setResult(value?: UploadResult): UploadResponse; + + getMessageCase(): UploadResponse.MessageCase; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UploadResponse.AsObject; @@ -103,6 +103,38 @@ export namespace UploadResponse { export type AsObject = { outStream: Uint8Array | string, errStream: Uint8Array | string, + result?: UploadResult.AsObject, + } + + export enum MessageCase { + MESSAGE_NOT_SET = 0, + OUT_STREAM = 1, + ERR_STREAM = 2, + RESULT = 3, + } + +} + +export class UploadResult extends jspb.Message { + + hasUpdatedUploadPort(): boolean; + clearUpdatedUploadPort(): void; + getUpdatedUploadPort(): cc_arduino_cli_commands_v1_port_pb.Port | undefined; + setUpdatedUploadPort(value?: cc_arduino_cli_commands_v1_port_pb.Port): UploadResult; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): UploadResult.AsObject; + static toObject(includeInstance: boolean, msg: UploadResult): UploadResult.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: UploadResult, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): UploadResult; + static deserializeBinaryFromReader(message: UploadResult, reader: jspb.BinaryReader): UploadResult; +} + +export namespace UploadResult { + export type AsObject = { + updatedUploadPort?: cc_arduino_cli_commands_v1_port_pb.Port.AsObject, } } @@ -129,42 +161,31 @@ export class UploadUsingProgrammerRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): UploadUsingProgrammerRequest; - getFqbn(): string; setFqbn(value: string): UploadUsingProgrammerRequest; - getSketchPath(): string; setSketchPath(value: string): UploadUsingProgrammerRequest; - hasPort(): boolean; clearPort(): void; getPort(): cc_arduino_cli_commands_v1_port_pb.Port | undefined; setPort(value?: cc_arduino_cli_commands_v1_port_pb.Port): UploadUsingProgrammerRequest; - getVerbose(): boolean; setVerbose(value: boolean): UploadUsingProgrammerRequest; - getVerify(): boolean; setVerify(value: boolean): UploadUsingProgrammerRequest; - getImportFile(): string; setImportFile(value: string): UploadUsingProgrammerRequest; - getImportDir(): string; setImportDir(value: string): UploadUsingProgrammerRequest; - getProgrammer(): string; setProgrammer(value: string): UploadUsingProgrammerRequest; - getDryRun(): boolean; setDryRun(value: boolean): UploadUsingProgrammerRequest; - getUserFieldsMap(): jspb.Map; clearUserFieldsMap(): void; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UploadUsingProgrammerRequest.AsObject; static toObject(includeInstance: boolean, msg: UploadUsingProgrammerRequest): UploadUsingProgrammerRequest.AsObject; @@ -197,13 +218,11 @@ export class UploadUsingProgrammerResponse extends jspb.Message { getOutStream_asU8(): Uint8Array; getOutStream_asB64(): string; setOutStream(value: Uint8Array | string): UploadUsingProgrammerResponse; - getErrStream(): Uint8Array | string; getErrStream_asU8(): Uint8Array; getErrStream_asB64(): string; setErrStream(value: Uint8Array | string): UploadUsingProgrammerResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UploadUsingProgrammerResponse.AsObject; static toObject(includeInstance: boolean, msg: UploadUsingProgrammerResponse): UploadUsingProgrammerResponse.AsObject; @@ -227,33 +246,25 @@ export class BurnBootloaderRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): BurnBootloaderRequest; - getFqbn(): string; setFqbn(value: string): BurnBootloaderRequest; - hasPort(): boolean; clearPort(): void; getPort(): cc_arduino_cli_commands_v1_port_pb.Port | undefined; setPort(value?: cc_arduino_cli_commands_v1_port_pb.Port): BurnBootloaderRequest; - getVerbose(): boolean; setVerbose(value: boolean): BurnBootloaderRequest; - getVerify(): boolean; setVerify(value: boolean): BurnBootloaderRequest; - getProgrammer(): string; setProgrammer(value: string): BurnBootloaderRequest; - getDryRun(): boolean; setDryRun(value: boolean): BurnBootloaderRequest; - getUserFieldsMap(): jspb.Map; clearUserFieldsMap(): void; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BurnBootloaderRequest.AsObject; static toObject(includeInstance: boolean, msg: BurnBootloaderRequest): BurnBootloaderRequest.AsObject; @@ -283,13 +294,11 @@ export class BurnBootloaderResponse extends jspb.Message { getOutStream_asU8(): Uint8Array; getOutStream_asB64(): string; setOutStream(value: Uint8Array | string): BurnBootloaderResponse; - getErrStream(): Uint8Array | string; getErrStream_asU8(): Uint8Array; getErrStream_asB64(): string; setErrStream(value: Uint8Array | string): BurnBootloaderResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): BurnBootloaderResponse.AsObject; static toObject(includeInstance: boolean, msg: BurnBootloaderResponse): BurnBootloaderResponse.AsObject; @@ -313,11 +322,9 @@ export class ListProgrammersAvailableForUploadRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): ListProgrammersAvailableForUploadRequest; - getFqbn(): string; setFqbn(value: string): ListProgrammersAvailableForUploadRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ListProgrammersAvailableForUploadRequest.AsObject; static toObject(includeInstance: boolean, msg: ListProgrammersAvailableForUploadRequest): ListProgrammersAvailableForUploadRequest.AsObject; @@ -341,7 +348,6 @@ export class ListProgrammersAvailableForUploadResponse extends jspb.Message { setProgrammersList(value: Array): ListProgrammersAvailableForUploadResponse; addProgrammers(value?: cc_arduino_cli_commands_v1_common_pb.Programmer, index?: number): cc_arduino_cli_commands_v1_common_pb.Programmer; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ListProgrammersAvailableForUploadResponse.AsObject; static toObject(includeInstance: boolean, msg: ListProgrammersAvailableForUploadResponse): ListProgrammersAvailableForUploadResponse.AsObject; @@ -364,14 +370,11 @@ export class SupportedUserFieldsRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): SupportedUserFieldsRequest; - getFqbn(): string; setFqbn(value: string): SupportedUserFieldsRequest; - getProtocol(): string; setProtocol(value: string): SupportedUserFieldsRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SupportedUserFieldsRequest.AsObject; static toObject(includeInstance: boolean, msg: SupportedUserFieldsRequest): SupportedUserFieldsRequest.AsObject; @@ -393,17 +396,13 @@ export namespace SupportedUserFieldsRequest { export class UserField extends jspb.Message { getToolId(): string; setToolId(value: string): UserField; - getName(): string; setName(value: string): UserField; - getLabel(): string; setLabel(value: string): UserField; - getSecret(): boolean; setSecret(value: boolean): UserField; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): UserField.AsObject; static toObject(includeInstance: boolean, msg: UserField): UserField.AsObject; @@ -429,7 +428,6 @@ export class SupportedUserFieldsResponse extends jspb.Message { setUserFieldsList(value: Array): SupportedUserFieldsResponse; addUserFields(value?: UserField, index?: number): UserField; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SupportedUserFieldsResponse.AsObject; static toObject(includeInstance: boolean, msg: SupportedUserFieldsResponse): SupportedUserFieldsResponse.AsObject; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js index c38618ad6..937a819c4 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js @@ -34,6 +34,8 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest', goog.exportSymbol('proto.cc.arduino.cli.commands.v1.SupportedUserFieldsResponse', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UploadRequest', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UploadResponse', null, global); +goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UploadResponse.MessageCase', null, global); +goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UploadResult', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UploadUsingProgrammerRequest', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UploadUsingProgrammerResponse', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.UserField', null, global); @@ -69,7 +71,7 @@ if (goog.DEBUG && !COMPILED) { * @constructor */ proto.cc.arduino.cli.commands.v1.UploadResponse = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.cc.arduino.cli.commands.v1.UploadResponse.oneofGroups_); }; goog.inherits(proto.cc.arduino.cli.commands.v1.UploadResponse, jspb.Message); if (goog.DEBUG && !COMPILED) { @@ -79,6 +81,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.cc.arduino.cli.commands.v1.UploadResponse.displayName = 'proto.cc.arduino.cli.commands.v1.UploadResponse'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.commands.v1.UploadResult = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.commands.v1.UploadResult, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.cc.arduino.cli.commands.v1.UploadResult.displayName = 'proto.cc.arduino.cli.commands.v1.UploadResult'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -765,6 +788,33 @@ proto.cc.arduino.cli.commands.v1.UploadRequest.prototype.clearUserFieldsMap = fu +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.oneofGroups_ = [[1,2,3]]; + +/** + * @enum {number} + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.MessageCase = { + MESSAGE_NOT_SET: 0, + OUT_STREAM: 1, + ERR_STREAM: 2, + RESULT: 3 +}; + +/** + * @return {proto.cc.arduino.cli.commands.v1.UploadResponse.MessageCase} + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.getMessageCase = function() { + return /** @type {proto.cc.arduino.cli.commands.v1.UploadResponse.MessageCase} */(jspb.Message.computeOneofCase(this, proto.cc.arduino.cli.commands.v1.UploadResponse.oneofGroups_[0])); +}; + if (jspb.Message.GENERATE_TO_OBJECT) { @@ -797,7 +847,8 @@ proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.toObject = function(op proto.cc.arduino.cli.commands.v1.UploadResponse.toObject = function(includeInstance, msg) { var f, obj = { outStream: msg.getOutStream_asB64(), - errStream: msg.getErrStream_asB64() + errStream: msg.getErrStream_asB64(), + result: (f = msg.getResult()) && proto.cc.arduino.cli.commands.v1.UploadResult.toObject(includeInstance, f) }; if (includeInstance) { @@ -842,6 +893,11 @@ proto.cc.arduino.cli.commands.v1.UploadResponse.deserializeBinaryFromReader = fu var value = /** @type {!Uint8Array} */ (reader.readBytes()); msg.setErrStream(value); break; + case 3: + var value = new proto.cc.arduino.cli.commands.v1.UploadResult; + reader.readMessage(value,proto.cc.arduino.cli.commands.v1.UploadResult.deserializeBinaryFromReader); + msg.setResult(value); + break; default: reader.skipField(); break; @@ -871,20 +927,28 @@ proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.serializeBinary = func */ proto.cc.arduino.cli.commands.v1.UploadResponse.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getOutStream_asU8(); - if (f.length > 0) { + f = /** @type {!(string|Uint8Array)} */ (jspb.Message.getField(message, 1)); + if (f != null) { writer.writeBytes( 1, f ); } - f = message.getErrStream_asU8(); - if (f.length > 0) { + f = /** @type {!(string|Uint8Array)} */ (jspb.Message.getField(message, 2)); + if (f != null) { writer.writeBytes( 2, f ); } + f = message.getResult(); + if (f != null) { + writer.writeMessage( + 3, + f, + proto.cc.arduino.cli.commands.v1.UploadResult.serializeBinaryToWriter + ); + } }; @@ -926,7 +990,25 @@ proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.getOutStream_asU8 = fu * @return {!proto.cc.arduino.cli.commands.v1.UploadResponse} returns this */ proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.setOutStream = function(value) { - return jspb.Message.setProto3BytesField(this, 1, value); + return jspb.Message.setOneofField(this, 1, proto.cc.arduino.cli.commands.v1.UploadResponse.oneofGroups_[0], value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.cc.arduino.cli.commands.v1.UploadResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.clearOutStream = function() { + return jspb.Message.setOneofField(this, 1, proto.cc.arduino.cli.commands.v1.UploadResponse.oneofGroups_[0], undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.hasOutStream = function() { + return jspb.Message.getField(this, 1) != null; }; @@ -968,7 +1050,213 @@ proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.getErrStream_asU8 = fu * @return {!proto.cc.arduino.cli.commands.v1.UploadResponse} returns this */ proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.setErrStream = function(value) { - return jspb.Message.setProto3BytesField(this, 2, value); + return jspb.Message.setOneofField(this, 2, proto.cc.arduino.cli.commands.v1.UploadResponse.oneofGroups_[0], value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.cc.arduino.cli.commands.v1.UploadResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.clearErrStream = function() { + return jspb.Message.setOneofField(this, 2, proto.cc.arduino.cli.commands.v1.UploadResponse.oneofGroups_[0], undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.hasErrStream = function() { + return jspb.Message.getField(this, 2) != null; +}; + + +/** + * optional UploadResult result = 3; + * @return {?proto.cc.arduino.cli.commands.v1.UploadResult} + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.getResult = function() { + return /** @type{?proto.cc.arduino.cli.commands.v1.UploadResult} */ ( + jspb.Message.getWrapperField(this, proto.cc.arduino.cli.commands.v1.UploadResult, 3)); +}; + + +/** + * @param {?proto.cc.arduino.cli.commands.v1.UploadResult|undefined} value + * @return {!proto.cc.arduino.cli.commands.v1.UploadResponse} returns this +*/ +proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.setResult = function(value) { + return jspb.Message.setOneofWrapperField(this, 3, proto.cc.arduino.cli.commands.v1.UploadResponse.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.cc.arduino.cli.commands.v1.UploadResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.clearResult = function() { + return this.setResult(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.UploadResponse.prototype.hasResult = function() { + return jspb.Message.getField(this, 3) != null; +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.commands.v1.UploadResult.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.commands.v1.UploadResult.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.commands.v1.UploadResult} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.UploadResult.toObject = function(includeInstance, msg) { + var f, obj = { + updatedUploadPort: (f = msg.getUpdatedUploadPort()) && cc_arduino_cli_commands_v1_port_pb.Port.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.commands.v1.UploadResult} + */ +proto.cc.arduino.cli.commands.v1.UploadResult.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.commands.v1.UploadResult; + return proto.cc.arduino.cli.commands.v1.UploadResult.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.commands.v1.UploadResult} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.commands.v1.UploadResult} + */ +proto.cc.arduino.cli.commands.v1.UploadResult.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new cc_arduino_cli_commands_v1_port_pb.Port; + reader.readMessage(value,cc_arduino_cli_commands_v1_port_pb.Port.deserializeBinaryFromReader); + msg.setUpdatedUploadPort(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.commands.v1.UploadResult.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.commands.v1.UploadResult.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.commands.v1.UploadResult} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.UploadResult.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getUpdatedUploadPort(); + if (f != null) { + writer.writeMessage( + 1, + f, + cc_arduino_cli_commands_v1_port_pb.Port.serializeBinaryToWriter + ); + } +}; + + +/** + * optional Port updated_upload_port = 1; + * @return {?proto.cc.arduino.cli.commands.v1.Port} + */ +proto.cc.arduino.cli.commands.v1.UploadResult.prototype.getUpdatedUploadPort = function() { + return /** @type{?proto.cc.arduino.cli.commands.v1.Port} */ ( + jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_port_pb.Port, 1)); +}; + + +/** + * @param {?proto.cc.arduino.cli.commands.v1.Port|undefined} value + * @return {!proto.cc.arduino.cli.commands.v1.UploadResult} returns this +*/ +proto.cc.arduino.cli.commands.v1.UploadResult.prototype.setUpdatedUploadPort = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.cc.arduino.cli.commands.v1.UploadResult} returns this + */ +proto.cc.arduino.cli.commands.v1.UploadResult.prototype.clearUpdatedUploadPort = function() { + return this.setUpdatedUploadPort(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.UploadResult.prototype.hasUpdatedUploadPort = function() { + return jspb.Message.getField(this, 1) != null; }; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/debug/v1/debug_grpc_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/debug/v1/debug_grpc_pb.d.ts index ad1daba69..a5e384a95 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/debug/v1/debug_grpc_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/debug/v1/debug_grpc_pb.d.ts @@ -5,7 +5,6 @@ /* eslint-disable */ import * as grpc from "@grpc/grpc-js"; -import {handleClientStreamingCall} from "@grpc/grpc-js/build/src/server-call"; import * as cc_arduino_cli_debug_v1_debug_pb from "../../../../../cc/arduino/cli/debug/v1/debug_pb"; import * as cc_arduino_cli_commands_v1_common_pb from "../../../../../cc/arduino/cli/commands/v1/common_pb"; import * as cc_arduino_cli_commands_v1_port_pb from "../../../../../cc/arduino/cli/commands/v1/port_pb"; @@ -36,7 +35,7 @@ interface IDebugServiceService_IGetDebugConfig extends grpc.MethodDefinition; getDebugConfig: grpc.handleUnaryCall; } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/debug/v1/debug_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/debug/v1/debug_pb.d.ts index 7f353d557..5a8f0d0f6 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/debug/v1/debug_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/debug/v1/debug_pb.d.ts @@ -14,16 +14,13 @@ export class DebugRequest extends jspb.Message { clearDebugRequest(): void; getDebugRequest(): DebugConfigRequest | undefined; setDebugRequest(value?: DebugConfigRequest): DebugRequest; - getData(): Uint8Array | string; getData_asU8(): Uint8Array; getData_asB64(): string; setData(value: Uint8Array | string): DebugRequest; - getSendInterrupt(): boolean; setSendInterrupt(value: boolean): DebugRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DebugRequest.AsObject; static toObject(includeInstance: boolean, msg: DebugRequest): DebugRequest.AsObject; @@ -48,29 +45,22 @@ export class DebugConfigRequest extends jspb.Message { clearInstance(): void; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): DebugConfigRequest; - getFqbn(): string; setFqbn(value: string): DebugConfigRequest; - getSketchPath(): string; setSketchPath(value: string): DebugConfigRequest; - hasPort(): boolean; clearPort(): void; getPort(): cc_arduino_cli_commands_v1_port_pb.Port | undefined; setPort(value?: cc_arduino_cli_commands_v1_port_pb.Port): DebugConfigRequest; - getInterpreter(): string; setInterpreter(value: string): DebugConfigRequest; - getImportDir(): string; setImportDir(value: string): DebugConfigRequest; - getProgrammer(): string; setProgrammer(value: string): DebugConfigRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DebugConfigRequest.AsObject; static toObject(includeInstance: boolean, msg: DebugConfigRequest): DebugConfigRequest.AsObject; @@ -98,11 +88,9 @@ export class DebugResponse extends jspb.Message { getData_asU8(): Uint8Array; getData_asB64(): string; setData(value: Uint8Array | string): DebugResponse; - getError(): string; setError(value: string): DebugResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): DebugResponse.AsObject; static toObject(includeInstance: boolean, msg: DebugResponse): DebugResponse.AsObject; @@ -123,31 +111,23 @@ export namespace DebugResponse { export class GetDebugConfigResponse extends jspb.Message { getExecutable(): string; setExecutable(value: string): GetDebugConfigResponse; - getToolchain(): string; setToolchain(value: string): GetDebugConfigResponse; - getToolchainPath(): string; setToolchainPath(value: string): GetDebugConfigResponse; - getToolchainPrefix(): string; setToolchainPrefix(value: string): GetDebugConfigResponse; - getServer(): string; setServer(value: string): GetDebugConfigResponse; - getServerPath(): string; setServerPath(value: string): GetDebugConfigResponse; - getToolchainConfigurationMap(): jspb.Map; clearToolchainConfigurationMap(): void; - getServerConfigurationMap(): jspb.Map; clearServerConfigurationMap(): void; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): GetDebugConfigResponse.AsObject; static toObject(includeInstance: boolean, msg: GetDebugConfigResponse): GetDebugConfigResponse.AsObject; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb.d.ts index f08b64d21..c5284c159 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb.d.ts @@ -5,7 +5,6 @@ /* eslint-disable */ import * as grpc from "@grpc/grpc-js"; -import {handleClientStreamingCall} from "@grpc/grpc-js/build/src/server-call"; import * as cc_arduino_cli_settings_v1_settings_pb from "../../../../../cc/arduino/cli/settings/v1/settings_pb"; interface ISettingsServiceService extends grpc.ServiceDefinition { @@ -14,6 +13,7 @@ interface ISettingsServiceService extends grpc.ServiceDefinition { @@ -61,15 +61,25 @@ interface ISettingsServiceService_IWrite extends grpc.MethodDefinition; responseDeserialize: grpc.deserialize; } +interface ISettingsServiceService_IDelete extends grpc.MethodDefinition { + path: "/cc.arduino.cli.settings.v1.SettingsService/Delete"; + requestStream: false; + responseStream: false; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} export const SettingsServiceService: ISettingsServiceService; -export interface ISettingsServiceServer { +export interface ISettingsServiceServer extends grpc.UntypedServiceImplementation { getAll: grpc.handleUnaryCall; merge: grpc.handleUnaryCall; getValue: grpc.handleUnaryCall; setValue: grpc.handleUnaryCall; write: grpc.handleUnaryCall; + delete: grpc.handleUnaryCall; } export interface ISettingsServiceClient { @@ -88,6 +98,9 @@ export interface ISettingsServiceClient { write(request: cc_arduino_cli_settings_v1_settings_pb.WriteRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall; write(request: cc_arduino_cli_settings_v1_settings_pb.WriteRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall; write(request: cc_arduino_cli_settings_v1_settings_pb.WriteRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall; + delete(request: cc_arduino_cli_settings_v1_settings_pb.DeleteRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.DeleteResponse) => void): grpc.ClientUnaryCall; + delete(request: cc_arduino_cli_settings_v1_settings_pb.DeleteRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.DeleteResponse) => void): grpc.ClientUnaryCall; + delete(request: cc_arduino_cli_settings_v1_settings_pb.DeleteRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.DeleteResponse) => void): grpc.ClientUnaryCall; } export class SettingsServiceClient extends grpc.Client implements ISettingsServiceClient { @@ -107,4 +120,7 @@ export class SettingsServiceClient extends grpc.Client implements ISettingsServi public write(request: cc_arduino_cli_settings_v1_settings_pb.WriteRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall; public write(request: cc_arduino_cli_settings_v1_settings_pb.WriteRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall; public write(request: cc_arduino_cli_settings_v1_settings_pb.WriteRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall; + public delete(request: cc_arduino_cli_settings_v1_settings_pb.DeleteRequest, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.DeleteResponse) => void): grpc.ClientUnaryCall; + public delete(request: cc_arduino_cli_settings_v1_settings_pb.DeleteRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.DeleteResponse) => void): grpc.ClientUnaryCall; + public delete(request: cc_arduino_cli_settings_v1_settings_pb.DeleteRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: cc_arduino_cli_settings_v1_settings_pb.DeleteResponse) => void): grpc.ClientUnaryCall; } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb.js index fd3549cdc..76c399866 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_grpc_pb.js @@ -19,6 +19,28 @@ 'use strict'; var cc_arduino_cli_settings_v1_settings_pb = require('../../../../../cc/arduino/cli/settings/v1/settings_pb.js'); +function serialize_cc_arduino_cli_settings_v1_DeleteRequest(arg) { + if (!(arg instanceof cc_arduino_cli_settings_v1_settings_pb.DeleteRequest)) { + throw new Error('Expected argument of type cc.arduino.cli.settings.v1.DeleteRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_settings_v1_DeleteRequest(buffer_arg) { + return cc_arduino_cli_settings_v1_settings_pb.DeleteRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_cc_arduino_cli_settings_v1_DeleteResponse(arg) { + if (!(arg instanceof cc_arduino_cli_settings_v1_settings_pb.DeleteResponse)) { + throw new Error('Expected argument of type cc.arduino.cli.settings.v1.DeleteResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_settings_v1_DeleteResponse(buffer_arg) { + return cc_arduino_cli_settings_v1_settings_pb.DeleteResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_cc_arduino_cli_settings_v1_GetAllRequest(arg) { if (!(arg instanceof cc_arduino_cli_settings_v1_settings_pb.GetAllRequest)) { throw new Error('Expected argument of type cc.arduino.cli.settings.v1.GetAllRequest'); @@ -193,5 +215,17 @@ write: { responseSerialize: serialize_cc_arduino_cli_settings_v1_WriteResponse, responseDeserialize: deserialize_cc_arduino_cli_settings_v1_WriteResponse, }, + // Deletes an entry and rewrites the file settings +delete: { + path: '/cc.arduino.cli.settings.v1.SettingsService/Delete', + requestStream: false, + responseStream: false, + requestType: cc_arduino_cli_settings_v1_settings_pb.DeleteRequest, + responseType: cc_arduino_cli_settings_v1_settings_pb.DeleteResponse, + requestSerialize: serialize_cc_arduino_cli_settings_v1_DeleteRequest, + requestDeserialize: deserialize_cc_arduino_cli_settings_v1_DeleteRequest, + responseSerialize: serialize_cc_arduino_cli_settings_v1_DeleteResponse, + responseDeserialize: deserialize_cc_arduino_cli_settings_v1_DeleteResponse, + }, }; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_pb.d.ts index 2453b6879..03fdbf449 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_pb.d.ts @@ -10,7 +10,6 @@ export class GetAllResponse extends jspb.Message { getJsonData(): string; setJsonData(value: string): GetAllResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): GetAllResponse.AsObject; static toObject(includeInstance: boolean, msg: GetAllResponse): GetAllResponse.AsObject; @@ -31,7 +30,6 @@ export class MergeRequest extends jspb.Message { getJsonData(): string; setJsonData(value: string): MergeRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): MergeRequest.AsObject; static toObject(includeInstance: boolean, msg: MergeRequest): MergeRequest.AsObject; @@ -51,11 +49,9 @@ export namespace MergeRequest { export class GetValueResponse extends jspb.Message { getKey(): string; setKey(value: string): GetValueResponse; - getJsonData(): string; setJsonData(value: string): GetValueResponse; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): GetValueResponse.AsObject; static toObject(includeInstance: boolean, msg: GetValueResponse): GetValueResponse.AsObject; @@ -76,11 +72,9 @@ export namespace GetValueResponse { export class SetValueRequest extends jspb.Message { getKey(): string; setKey(value: string): SetValueRequest; - getJsonData(): string; setJsonData(value: string): SetValueRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SetValueRequest.AsObject; static toObject(includeInstance: boolean, msg: SetValueRequest): SetValueRequest.AsObject; @@ -119,7 +113,6 @@ export class GetValueRequest extends jspb.Message { getKey(): string; setKey(value: string): GetValueRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): GetValueRequest.AsObject; static toObject(includeInstance: boolean, msg: GetValueRequest): GetValueRequest.AsObject; @@ -174,7 +167,6 @@ export class WriteRequest extends jspb.Message { getFilePath(): string; setFilePath(value: string): WriteRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): WriteRequest.AsObject; static toObject(includeInstance: boolean, msg: WriteRequest): WriteRequest.AsObject; @@ -207,3 +199,40 @@ export namespace WriteResponse { export type AsObject = { } } + +export class DeleteRequest extends jspb.Message { + getKey(): string; + setKey(value: string): DeleteRequest; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): DeleteRequest.AsObject; + static toObject(includeInstance: boolean, msg: DeleteRequest): DeleteRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: DeleteRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): DeleteRequest; + static deserializeBinaryFromReader(message: DeleteRequest, reader: jspb.BinaryReader): DeleteRequest; +} + +export namespace DeleteRequest { + export type AsObject = { + key: string, + } +} + +export class DeleteResponse extends jspb.Message { + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): DeleteResponse.AsObject; + static toObject(includeInstance: boolean, msg: DeleteResponse): DeleteResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: DeleteResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): DeleteResponse; + static deserializeBinaryFromReader(message: DeleteResponse, reader: jspb.BinaryReader): DeleteResponse; +} + +export namespace DeleteResponse { + export type AsObject = { + } +} diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_pb.js index 585bd6473..a00c4ffe8 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/settings/v1/settings_pb.js @@ -21,6 +21,8 @@ var global = (function() { return Function('return this')(); }.call(null)); +goog.exportSymbol('proto.cc.arduino.cli.settings.v1.DeleteRequest', null, global); +goog.exportSymbol('proto.cc.arduino.cli.settings.v1.DeleteResponse', null, global); goog.exportSymbol('proto.cc.arduino.cli.settings.v1.GetAllRequest', null, global); goog.exportSymbol('proto.cc.arduino.cli.settings.v1.GetAllResponse', null, global); goog.exportSymbol('proto.cc.arduino.cli.settings.v1.GetValueRequest', null, global); @@ -241,6 +243,48 @@ if (goog.DEBUG && !COMPILED) { */ proto.cc.arduino.cli.settings.v1.WriteResponse.displayName = 'proto.cc.arduino.cli.settings.v1.WriteResponse'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.settings.v1.DeleteRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.settings.v1.DeleteRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.cc.arduino.cli.settings.v1.DeleteRequest.displayName = 'proto.cc.arduino.cli.settings.v1.DeleteRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.settings.v1.DeleteResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.settings.v1.DeleteResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.cc.arduino.cli.settings.v1.DeleteResponse.displayName = 'proto.cc.arduino.cli.settings.v1.DeleteResponse'; +} @@ -1485,4 +1529,235 @@ proto.cc.arduino.cli.settings.v1.WriteResponse.serializeBinaryToWriter = functio }; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.settings.v1.DeleteRequest.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.settings.v1.DeleteRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.settings.v1.DeleteRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.v1.DeleteRequest.toObject = function(includeInstance, msg) { + var f, obj = { + key: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.settings.v1.DeleteRequest} + */ +proto.cc.arduino.cli.settings.v1.DeleteRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.settings.v1.DeleteRequest; + return proto.cc.arduino.cli.settings.v1.DeleteRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.settings.v1.DeleteRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.settings.v1.DeleteRequest} + */ +proto.cc.arduino.cli.settings.v1.DeleteRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setKey(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.settings.v1.DeleteRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.settings.v1.DeleteRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.settings.v1.DeleteRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.v1.DeleteRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getKey(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string key = 1; + * @return {string} + */ +proto.cc.arduino.cli.settings.v1.DeleteRequest.prototype.getKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.settings.v1.DeleteRequest} returns this + */ +proto.cc.arduino.cli.settings.v1.DeleteRequest.prototype.setKey = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.settings.v1.DeleteResponse.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.settings.v1.DeleteResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.settings.v1.DeleteResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.v1.DeleteResponse.toObject = function(includeInstance, msg) { + var f, obj = { + + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.settings.v1.DeleteResponse} + */ +proto.cc.arduino.cli.settings.v1.DeleteResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.settings.v1.DeleteResponse; + return proto.cc.arduino.cli.settings.v1.DeleteResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.settings.v1.DeleteResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.settings.v1.DeleteResponse} + */ +proto.cc.arduino.cli.settings.v1.DeleteResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.settings.v1.DeleteResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.settings.v1.DeleteResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.settings.v1.DeleteResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.v1.DeleteResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; +}; + + goog.object.extend(exports, proto.cc.arduino.cli.settings.v1); diff --git a/arduino-ide-extension/src/node/cli-protocol/google/rpc/status_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/google/rpc/status_pb.d.ts index 91afcb7d5..c89b2f378 100644 --- a/arduino-ide-extension/src/node/cli-protocol/google/rpc/status_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/google/rpc/status_pb.d.ts @@ -10,16 +10,13 @@ import * as google_protobuf_any_pb from "google-protobuf/google/protobuf/any_pb" export class Status extends jspb.Message { getCode(): number; setCode(value: number): Status; - getMessage(): string; setMessage(value: string): Status; - clearDetailsList(): void; getDetailsList(): Array; setDetailsList(value: Array): Status; addDetails(value?: google_protobuf_any_pb.Any, index?: number): google_protobuf_any_pb.Any; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Status.AsObject; static toObject(includeInstance: boolean, msg: Status): Status.AsObject; diff --git a/arduino-ide-extension/src/node/core-service-impl.ts b/arduino-ide-extension/src/node/core-service-impl.ts index 253dcd383..a8dfab98b 100644 --- a/arduino-ide-extension/src/node/core-service-impl.ts +++ b/arduino-ide-extension/src/node/core-service-impl.ts @@ -3,13 +3,14 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import { relative } from 'node:path'; import * as jspb from 'google-protobuf'; import { BoolValue } from 'google-protobuf/google/protobuf/wrappers_pb'; -import { ClientReadableStream } from '@grpc/grpc-js'; +import type { ClientReadableStream } from '@grpc/grpc-js'; import { CompilerWarnings, CoreService, CoreError, CompileSummary, isCompileSummary, + isUploadResponse, } from '../common/protocol/core-service'; import { CompileRequest, @@ -25,7 +26,13 @@ import { UploadUsingProgrammerResponse, } from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb'; import { ResponseService } from '../common/protocol/response-service'; -import { OutputMessage, Port } from '../common/protocol'; +import { + resolveDetectedPort, + OutputMessage, + PortIdentifier, + Port, + UploadResponse as ApiUploadResponse, +} from '../common/protocol'; import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; import { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb'; import { ApplicationError, CommandService, Disposable, nls } from '@theia/core'; @@ -36,8 +43,8 @@ import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb'; import { firstToUpperCase, notEmpty } from '../common/utils'; import { ServiceError } from './service-error'; import { ExecuteWithProgress, ProgressResponse } from './grpc-progressible'; -import { BoardDiscovery } from './board-discovery'; -import { Mutable } from '@theia/core/lib/common/types'; +import type { Mutable } from '@theia/core/lib/common/types'; +import { BoardDiscovery, createApiPort } from './board-discovery'; namespace Uploadable { export type Request = UploadRequest | UploadUsingProgrammerRequest; @@ -50,13 +57,10 @@ type CompileSummaryFragment = Partial>; export class CoreServiceImpl extends CoreClientAware implements CoreService { @inject(ResponseService) private readonly responseService: ResponseService; - @inject(MonitorManager) private readonly monitorManager: MonitorManager; - @inject(CommandService) private readonly commandService: CommandService; - @inject(BoardDiscovery) private readonly boardDiscovery: BoardDiscovery; @@ -172,7 +176,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { return request; } - upload(options: CoreService.Options.Upload): Promise { + upload(options: CoreService.Options.Upload): Promise { const { usingProgrammer } = options; return this.doUpload( options, @@ -201,14 +205,45 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { ) => (request: REQ) => ClientReadableStream, errorCtor: ApplicationError.Constructor, task: string - ): Promise { + ): Promise { + const portBeforeUpload = options.port; + const uploadResponseFragment: Mutable> = { + portAfterUpload: options.port, // assume no port changes during the upload + }; const coreClient = await this.coreClient; const { client, instance } = coreClient; const progressHandler = this.createProgressHandler(options); - const handler = this.createOnDataHandler(progressHandler); + // Track responses for port changes. No port changes are expected when uploading using a programmer. + const updateUploadResponseFragmentHandler = (response: RESP) => { + if (response instanceof UploadResponse) { + // TODO: this instanceof should not be here but in `upload`. the upload and upload using programmer gRPC APIs are not symmetric + const uploadResult = response.getResult(); + if (uploadResult) { + const port = uploadResult.getUpdatedUploadPort(); + if (port) { + uploadResponseFragment.portAfterUpload = createApiPort(port); + console.info( + `Received port after upload [${ + options.port ? Port.keyOf(options.port) : '' + }, ${options.fqbn}, ${ + options.sketch.name + }]. Before port: ${JSON.stringify( + portBeforeUpload + )}, after port: ${JSON.stringify( + uploadResponseFragment.portAfterUpload + )}` + ); + } + } + } + }; + const handler = this.createOnDataHandler( + progressHandler, + updateUploadResponseFragmentHandler + ); const grpcCall = responseFactory(client); return this.notifyUploadWillStart(options).then(() => - new Promise((resolve, reject) => { + new Promise((resolve, reject) => { grpcCall(this.initUploadRequest(request, options, instance)) .on('data', handler.onData) .on('error', (error) => { @@ -234,10 +269,28 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { ); } }) - .on('end', resolve); + .on('end', () => { + if (isUploadResponse(uploadResponseFragment)) { + resolve(uploadResponseFragment); + } else { + reject( + new Error( + `Could not detect the port after the upload. Upload options were: ${JSON.stringify( + options + )}, upload response was: ${JSON.stringify( + uploadResponseFragment + )}` + ) + ); + } + }); }).finally(async () => { handler.dispose(); - await this.notifyUploadDidFinish(options); + await this.notifyUploadDidFinish( + Object.assign(options, { + afterPort: uploadResponseFragment.portAfterUpload, + }) + ); }) ); } @@ -302,7 +355,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { .on('end', resolve); }).finally(async () => { handler.dispose(); - await this.notifyUploadDidFinish(options); + await this.notifyUploadDidFinish( + Object.assign(options, { afterPort: options.port }) + ); }) ); } @@ -379,21 +434,25 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { port, }: { fqbn?: string | undefined; - port?: Port | undefined; + port?: PortIdentifier; }): Promise { - this.boardDiscovery.setUploadInProgress(true); - return this.monitorManager.notifyUploadStarted(fqbn, port); + if (fqbn && port) { + return this.monitorManager.notifyUploadStarted(fqbn, port); + } } private async notifyUploadDidFinish({ fqbn, port, + afterPort, }: { fqbn?: string | undefined; - port?: Port | undefined; + port?: PortIdentifier; + afterPort?: PortIdentifier; }): Promise { - this.boardDiscovery.setUploadInProgress(false); - return this.monitorManager.notifyUploadFinished(fqbn, port); + if (fqbn && port && afterPort) { + return this.monitorManager.notifyUploadFinished(fqbn, port, afterPort); + } } private mergeSourceOverrides( @@ -410,21 +469,28 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { } } - private createPort(port: Port | undefined): RpcPort | undefined { + private createPort( + port: PortIdentifier | undefined, + resolve: (port: PortIdentifier) => Port | undefined = (port) => + resolveDetectedPort(port, this.boardDiscovery.detectedPorts) + ): RpcPort | undefined { if (!port) { return undefined; } + const resolvedPort = resolve(port); const rpcPort = new RpcPort(); - rpcPort.setAddress(port.address); - rpcPort.setLabel(port.addressLabel); rpcPort.setProtocol(port.protocol); - rpcPort.setProtocolLabel(port.protocolLabel); - if (port.hardwareId !== undefined) { - rpcPort.setHardwareId(port.hardwareId); - } - if (port.properties) { - for (const [key, value] of Object.entries(port.properties)) { - rpcPort.getPropertiesMap().set(key, value); + rpcPort.setAddress(port.address); + if (resolvedPort) { + rpcPort.setLabel(resolvedPort.addressLabel); + rpcPort.setProtocolLabel(resolvedPort.protocolLabel); + if (resolvedPort.hardwareId !== undefined) { + rpcPort.setHardwareId(resolvedPort.hardwareId); + } + if (resolvedPort.properties) { + for (const [key, value] of Object.entries(resolvedPort.properties)) { + rpcPort.getPropertiesMap().set(key, value); + } } } return rpcPort; diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 4eea5b46a..9931e3937 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -7,6 +7,8 @@ import { MonitorSettings, PluggableMonitorSettings, Port, + PortIdentifier, + portIdentifierEquals, } from '../common/protocol'; import { CoreClientAware } from './core-client-provider'; import { MonitorService } from './monitor-service'; @@ -180,13 +182,7 @@ export class MonitorManager extends CoreClientAware { * @param fqbn the FQBN of the board connected to port * @param port port to monitor */ - async notifyUploadStarted(fqbn?: string, port?: Port): Promise { - if (!fqbn || !port) { - // We have no way of knowing which monitor - // to retrieve if we don't have this information. - return; - } - + async notifyUploadStarted(fqbn: string, port: PortIdentifier): Promise { const monitorID = this.monitorID(fqbn, port); this.addToMonitorIDsByUploadState('uploadInProgress', monitorID); @@ -204,41 +200,44 @@ export class MonitorManager extends CoreClientAware { * Notifies the monitor service of that board/port combination * that an upload process started on that exact board/port combination. * @param fqbn the FQBN of the board connected to port - * @param port port to monitor + * @param beforePort port to monitor * @returns a Status object to know if the process has been * started or if there have been errors. */ async notifyUploadFinished( - fqbn?: string | undefined, - port?: Port + fqbn: string | undefined, + beforePort: PortIdentifier, + afterPort: PortIdentifier ): Promise { let portDidChangeOnUpload = false; + const beforeMonitorID = this.monitorID(fqbn, beforePort); + this.removeFromMonitorIDsByUploadState('uploadInProgress', beforeMonitorID); - // We have no way of knowing which monitor - // to retrieve if we don't have this information. - if (fqbn && port) { - const monitorID = this.monitorID(fqbn, port); - this.removeFromMonitorIDsByUploadState('uploadInProgress', monitorID); - - const monitor = this.monitorServices.get(monitorID); - if (monitor) { + const monitor = this.monitorServices.get(beforeMonitorID); + if (monitor) { + if (portIdentifierEquals(beforePort, afterPort)) { await monitor.start(); + } else { + await monitor.stop(); } + } - // this monitorID will only be present in "disposedForUpload" - // if the upload changed the board port - portDidChangeOnUpload = this.monitorIDIsInUploadState( + // this monitorID will only be present in "disposedForUpload" + // if the upload changed the board port + portDidChangeOnUpload = this.monitorIDIsInUploadState( + 'disposedForUpload', + beforeMonitorID + ); + if (portDidChangeOnUpload) { + this.removeFromMonitorIDsByUploadState( 'disposedForUpload', - monitorID + beforeMonitorID ); - if (portDidChangeOnUpload) { - this.removeFromMonitorIDsByUploadState('disposedForUpload', monitorID); - } - - // in case a service was paused but not disposed - this.removeFromMonitorIDsByUploadState('pausedForUpload', monitorID); } + // in case a service was paused but not disposed + this.removeFromMonitorIDsByUploadState('pausedForUpload', beforeMonitorID); + await this.startQueuedServices(portDidChangeOnUpload); } @@ -256,7 +255,7 @@ export class MonitorManager extends CoreClientAware { serviceStartParams: [, port], connectToClient, } of queued) { - const boardsState = await this.boardsService.getState(); + const boardsState = await this.boardsService.getDetectedPorts(); const boardIsStillOnPort = Object.keys(boardsState) .map((connection: string) => { const portAddress = connection.split('|')[0]; @@ -355,7 +354,7 @@ export class MonitorManager extends CoreClientAware { * @param port * @returns a unique monitor ID */ - private monitorID(fqbn: string | undefined, port: Port): MonitorID { + private monitorID(fqbn: string | undefined, port: PortIdentifier): MonitorID { const splitFqbn = fqbn?.split(':') || []; const shortenedFqbn = splitFqbn.slice(0, 3).join(':') || ''; return `${shortenedFqbn}-${port.address}-${port.protocol}`; diff --git a/arduino-ide-extension/src/node/notification-service-server.ts b/arduino-ide-extension/src/node/notification-service-server.ts index ce6a96304..cd3cac91e 100644 --- a/arduino-ide-extension/src/node/notification-service-server.ts +++ b/arduino-ide-extension/src/node/notification-service-server.ts @@ -2,7 +2,6 @@ import { injectable } from '@theia/core/shared/inversify'; import type { NotificationServiceServer, NotificationServiceClient, - AttachedBoardsChangeEvent, BoardsPackage, LibraryPackage, ConfigState, @@ -11,6 +10,7 @@ import type { IndexUpdateWillStartParams, IndexUpdateDidCompleteParams, IndexUpdateDidFailParams, + DetectedPorts, } from '../common/protocol'; @injectable() @@ -69,9 +69,9 @@ export class NotificationServiceServerImpl this.clients.forEach((client) => client.notifyLibraryDidUninstall(event)); } - notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void { + notifyDetectedPortsDidChange(event: { detectedPorts: DetectedPorts }): void { this.clients.forEach((client) => - client.notifyAttachedBoardsDidChange(event) + client.notifyDetectedPortsDidChange(event) ); } diff --git a/arduino-ide-extension/src/node/sketches-service-impl.ts b/arduino-ide-extension/src/node/sketches-service-impl.ts index d3ad0e58e..95493bb19 100644 --- a/arduino-ide-extension/src/node/sketches-service-impl.ts +++ b/arduino-ide-extension/src/node/sketches-service-impl.ts @@ -6,7 +6,7 @@ import path from 'node:path'; import glob from 'glob'; import crypto from 'node:crypto'; import PQueue from 'p-queue'; -import { Mutable } from '@theia/core/lib/common/types'; +import type { Mutable } from '@theia/core/lib/common/types'; import URI from '@theia/core/lib/common/uri'; import { ILogger } from '@theia/core/lib/common/logger'; import { FileUri } from '@theia/core/lib/node/file-uri'; @@ -128,11 +128,11 @@ export class SketchesServiceImpl uri: string, detectInvalidSketchNameError = true ): Promise { - const { client, instance } = await this.coreClient; + const { client } = await this.coreClient; const req = new LoadSketchRequest(); const requestSketchPath = FileUri.fsPath(uri); req.setSketchPath(requestSketchPath); - req.setInstance(instance); + // TODO: since the instance is not required on the request, can IDE2 do this faster or have a dedicated client for the sketch loading? const stat = new Deferred(); lstat(requestSketchPath, (err, result) => err ? stat.resolve(err) : stat.resolve(result) diff --git a/arduino-ide-extension/src/test/browser/board-service-provider.test.ts b/arduino-ide-extension/src/test/browser/board-service-provider.test.ts new file mode 100644 index 000000000..335f64176 --- /dev/null +++ b/arduino-ide-extension/src/test/browser/board-service-provider.test.ts @@ -0,0 +1,429 @@ +import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom'; +const disableJSDOM = enableJSDOM(); + +import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; +FrontendApplicationConfigProvider.set({}); + +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { + LocalStorageService, + StorageService, +} from '@theia/core/lib/browser/storage-service'; +import { WindowService } from '@theia/core/lib/browser/window/window-service'; +import { CommandContribution } from '@theia/core/lib/common/command'; +import { + Disposable, + DisposableCollection, +} from '@theia/core/lib/common/disposable'; +import { MessageService } from '@theia/core/lib/common/message-service'; +import { MockLogger } from '@theia/core/lib/common/test/mock-logger'; +import { Container, ContainerModule } from '@theia/core/shared/inversify'; +import { expect } from 'chai'; +import { BoardsDataStore } from '../../browser/boards/boards-data-store'; +import { BoardsServiceProvider } from '../../browser/boards/boards-service-provider'; +import { NotificationCenter } from '../../browser/notification-center'; +import { StorageWrapper } from '../../browser/storage-wrapper'; +import { + BoardIdentifierChangeEvent, + BoardsConfig, + BoardsConfigChangeEvent, + BoardsService, + DetectedPorts, + Port, + PortIdentifierChangeEvent, +} from '../../common/protocol/boards-service'; +import { NotificationServiceServer } from '../../common/protocol/notification-service'; +import { bindCommon, ConsoleLogger } from '../common/common-test-bindings'; +import { + detectedPort, + esp32S3DevModule, + mkr1000, + mkr1000SerialPort, + undiscoveredSerialPort, + uno, + unoSerialPort, +} from '../common/fixtures'; + +disableJSDOM(); + +describe('board-service-provider', () => { + let toDisposeAfterEach: DisposableCollection; + let boardsServiceProvider: BoardsServiceProvider; + let notificationCenter: NotificationCenter; + + beforeEach(async () => { + const container = createContainer(); + container.get( + FrontendApplicationStateService + ).state = 'ready'; + boardsServiceProvider = container.get( + BoardsServiceProvider + ); + notificationCenter = container.get(NotificationCenter); + toDisposeAfterEach = new DisposableCollection( + Disposable.create(() => boardsServiceProvider.onStop()) + ); + boardsServiceProvider.onStart(); + await boardsServiceProvider.ready; + }); + + afterEach(() => { + toDisposeAfterEach.dispose(); + }); + + it('should update the port (port identifier)', () => { + boardsServiceProvider['_boardsConfig'] = { + selectedBoard: uno, + selectedPort: unoSerialPort, + }; + const events: BoardsConfigChangeEvent[] = []; + toDisposeAfterEach.push( + boardsServiceProvider.onBoardsConfigDidChange((event) => + events.push(event) + ) + ); + const didUpdate = boardsServiceProvider.updateConfig(mkr1000SerialPort); + expect(didUpdate).to.be.true; + const expectedEvent: PortIdentifierChangeEvent = { + previousSelectedPort: unoSerialPort, + selectedPort: mkr1000SerialPort, + }; + expect(events).deep.equals([expectedEvent]); + }); + + it('should update the port (boards config)', () => { + boardsServiceProvider['_boardsConfig'] = { + selectedBoard: uno, + selectedPort: unoSerialPort, + }; + const events: BoardsConfigChangeEvent[] = []; + toDisposeAfterEach.push( + boardsServiceProvider.onBoardsConfigDidChange((event) => + events.push(event) + ) + ); + const didUpdate = boardsServiceProvider.updateConfig({ + selectedPort: mkr1000SerialPort, + selectedBoard: uno, + }); + expect(didUpdate).to.be.true; + const expectedEvent: PortIdentifierChangeEvent = { + previousSelectedPort: unoSerialPort, + selectedPort: mkr1000SerialPort, + }; + expect(events).deep.equals([expectedEvent]); + }); + + it('should not update the port if did not change (port identifier)', () => { + boardsServiceProvider['_boardsConfig'] = { + selectedBoard: uno, + selectedPort: unoSerialPort, + }; + const events: BoardsConfigChangeEvent[] = []; + toDisposeAfterEach.push( + boardsServiceProvider.onBoardsConfigDidChange((event) => + events.push(event) + ) + ); + const didUpdate = boardsServiceProvider.updateConfig(unoSerialPort); + expect(didUpdate).to.be.false; + expect(events).to.be.empty; + }); + + it('should update the board (board identifier)', () => { + boardsServiceProvider['_boardsConfig'] = { + selectedBoard: uno, + selectedPort: unoSerialPort, + }; + const events: BoardsConfigChangeEvent[] = []; + toDisposeAfterEach.push( + boardsServiceProvider.onBoardsConfigDidChange((event) => + events.push(event) + ) + ); + const didUpdate = boardsServiceProvider.updateConfig(mkr1000); + expect(didUpdate).to.be.true; + const expectedEvent: BoardIdentifierChangeEvent = { + previousSelectedBoard: uno, + selectedBoard: mkr1000, + }; + expect(events).deep.equals([expectedEvent]); + }); + + it('should update the board (boards config)', () => { + boardsServiceProvider['_boardsConfig'] = { + selectedBoard: uno, + selectedPort: unoSerialPort, + }; + const events: BoardsConfigChangeEvent[] = []; + toDisposeAfterEach.push( + boardsServiceProvider.onBoardsConfigDidChange((event) => + events.push(event) + ) + ); + const didUpdate = boardsServiceProvider.updateConfig({ + selectedBoard: mkr1000, + selectedPort: unoSerialPort, + }); + expect(didUpdate).to.be.true; + const expectedEvent: BoardIdentifierChangeEvent = { + previousSelectedBoard: uno, + selectedBoard: mkr1000, + }; + expect(events).deep.equals([expectedEvent]); + }); + + it('should not update the board if did not change (board identifier)', () => { + boardsServiceProvider['_boardsConfig'] = { + selectedBoard: uno, + selectedPort: unoSerialPort, + }; + const events: BoardsConfigChangeEvent[] = []; + toDisposeAfterEach.push( + boardsServiceProvider.onBoardsConfigDidChange((event) => + events.push(event) + ) + ); + const didUpdate = boardsServiceProvider.updateConfig(uno); + expect(didUpdate).to.be.false; + expect(events).to.be.empty; + }); + + it('should update both the board and port', () => { + boardsServiceProvider['_boardsConfig'] = { + selectedBoard: uno, + selectedPort: unoSerialPort, + }; + const events: BoardsConfigChangeEvent[] = []; + toDisposeAfterEach.push( + boardsServiceProvider.onBoardsConfigDidChange((event) => + events.push(event) + ) + ); + const didUpdate = boardsServiceProvider.updateConfig({ + selectedBoard: mkr1000, + selectedPort: mkr1000SerialPort, + }); + expect(didUpdate).to.be.true; + const expectedEvent: BoardIdentifierChangeEvent & + PortIdentifierChangeEvent = { + previousSelectedBoard: uno, + selectedBoard: mkr1000, + previousSelectedPort: unoSerialPort, + selectedPort: mkr1000SerialPort, + }; + expect(events).deep.equals([expectedEvent]); + }); + + it('should update neither the board nor the port if did not change', () => { + boardsServiceProvider['_boardsConfig'] = { + selectedBoard: uno, + selectedPort: unoSerialPort, + }; + const events: BoardsConfigChangeEvent[] = []; + toDisposeAfterEach.push( + boardsServiceProvider.onBoardsConfigDidChange((event) => + events.push(event) + ) + ); + const didUpdate = boardsServiceProvider.updateConfig({ + selectedBoard: uno, + selectedPort: unoSerialPort, + }); + expect(didUpdate).to.be.false; + expect(events).to.be.empty; + }); + + it('should detect a port change and find selection index', () => { + let boardList = boardsServiceProvider.boardList; + const didUpdate = boardsServiceProvider.updateConfig({ + selectedBoard: uno, + selectedPort: unoSerialPort, + }); + expect(didUpdate).to.be.true; + expect(boardList.selectedIndex).to.be.equal(-1); + let selectedItem = boardList.items[boardList.selectedIndex]; + expect(selectedItem).to.be.undefined; + + // attach board + notificationCenter.notifyDetectedPortsDidChange({ + detectedPorts: { + ...detectedPort(unoSerialPort, uno), + }, + }); + boardList = boardsServiceProvider.boardList; + expect(boardsServiceProvider.boardList.selectedIndex).to.be.equal(0); + selectedItem = boardList.items[boardList.selectedIndex]; + expect(selectedItem.board).to.be.deep.equal(uno); + expect(selectedItem.port).to.be.deep.equal(unoSerialPort); + + // detach board + notificationCenter.notifyDetectedPortsDidChange({ + detectedPorts: {}, + }); + boardList = boardsServiceProvider.boardList; + expect(boardsServiceProvider.boardList.selectedIndex).to.be.equal(-1); + selectedItem = boardList.items[boardList.selectedIndex]; + expect(selectedItem).to.be.undefined; + }); + + it('should update the board selection history for the port', () => { + notificationCenter.notifyDetectedPortsDidChange({ + detectedPorts: { + ...detectedPort(undiscoveredSerialPort), + ...detectedPort(unoSerialPort, uno), + ...detectedPort(mkr1000SerialPort, mkr1000), + }, + }); + + boardsServiceProvider.updateConfig({ + selectedBoard: esp32S3DevModule, + selectedPort: undiscoveredSerialPort, + }); + + expect(boardsServiceProvider['_boardListHistory']).to.be.deep.equal({ + [Port.keyOf(undiscoveredSerialPort)]: esp32S3DevModule, + }); + + boardsServiceProvider.updateConfig({ + selectedBoard: esp32S3DevModule, + selectedPort: unoSerialPort, + }); + + expect(boardsServiceProvider['_boardListHistory']).to.be.deep.equal({ + [Port.keyOf(undiscoveredSerialPort)]: esp32S3DevModule, + [Port.keyOf(unoSerialPort)]: esp32S3DevModule, + }); + + boardsServiceProvider.updateConfig({ + selectedBoard: uno, + selectedPort: unoSerialPort, + }); + + expect(boardsServiceProvider['_boardListHistory']).to.be.deep.equal({ + [Port.keyOf(undiscoveredSerialPort)]: esp32S3DevModule, + }); + }); + + type UpdateBoardListHistoryParams = Parameters< + BoardsServiceProvider['maybeUpdateBoardListHistory'] + >[0]; + type BoardListHistoryUpdateResult = ReturnType< + BoardsServiceProvider['maybeUpdateBoardListHistory'] + >; + interface BoardListHistoryTestSuite { + readonly init: BoardsConfig; + readonly detectedPorts?: DetectedPorts; + readonly params: UpdateBoardListHistoryParams; + readonly expected: BoardListHistoryUpdateResult; + /** + * Optional test title extension. + */ + readonly description?: string; + /** + * Optional test assertions. + */ + readonly assert?: ( + actual: BoardListHistoryUpdateResult, + service: BoardsServiceProvider + ) => void; + } + + const boardListHistoryTestSuites: BoardListHistoryTestSuite[] = [ + { + description: "'portToSelect' is undefined", + init: { selectedBoard: uno, selectedPort: unoSerialPort }, + params: { boardToSelect: mkr1000, portToSelect: undefined }, + expected: undefined, + }, + { + description: "'boardToSelect' is undefined", + init: { selectedBoard: uno, selectedPort: unoSerialPort }, + params: { boardToSelect: undefined, portToSelect: mkr1000SerialPort }, + expected: undefined, + }, + { + description: "'selectedBoard' fallback when 'ignore-board'", + init: { selectedBoard: uno, selectedPort: unoSerialPort }, + params: { + boardToSelect: 'ignore-board', + portToSelect: mkr1000SerialPort, + }, + expected: { [Port.keyOf(mkr1000SerialPort)]: uno }, + }, + { + description: "'selectedPort' fallback when 'ignore-port'", + init: { selectedBoard: uno, selectedPort: unoSerialPort }, + params: { + boardToSelect: mkr1000, + portToSelect: 'ignore-port', + }, + expected: { [Port.keyOf(unoSerialPort)]: mkr1000 }, + }, + { + description: + 'unsets history when board+port is from a detected port from a discovered board', + init: { selectedBoard: undefined, selectedPort: undefined }, + params: { + boardToSelect: uno, + portToSelect: unoSerialPort, + }, + detectedPorts: { + ...detectedPort(unoSerialPort, uno), + }, + expected: { [Port.keyOf(unoSerialPort)]: undefined }, + }, + ]; + boardListHistoryTestSuites.forEach((suite, index) => + it(`should handle board list history updates (${ + suite.description ? suite.description : `#${index + 1}` + })`, () => { + const { init, params, expected, assert, detectedPorts } = suite; + boardsServiceProvider['_boardsConfig'] = init; + if (detectedPorts) { + notificationCenter.notifyDetectedPortsDidChange({ detectedPorts }); + } + const actual = + boardsServiceProvider['maybeUpdateBoardListHistory'](params); + expect(actual).to.be.deep.equal(expected); + assert?.(actual, boardsServiceProvider); + }) + ); + + function createContainer(): Container { + const container = new Container({ defaultScope: 'Singleton' }); + container.load( + new ContainerModule((bind, unbind, isBound, rebind) => { + bindCommon(bind); + bind(MessageService).toConstantValue({}); + bind(BoardsService).toConstantValue({ + getDetectedPorts() { + return {}; + }, + }); + bind(NotificationCenter).toSelf().inSingletonScope(); + bind(NotificationServiceServer).toConstantValue(< + NotificationServiceServer + >{ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setClient(_) { + // nothing + }, + }); + bind(FrontendApplicationStateService).toSelf().inSingletonScope(); + bind(BoardsDataStore).toConstantValue({}); + bind(LocalStorageService).toSelf().inSingletonScope(); + bind(WindowService).toConstantValue({}); + bind(StorageService).toService(LocalStorageService); + bind(CommandContribution).toService(StorageWrapper); + bind(BoardsServiceProvider).toSelf().inSingletonScope(); + // IDE2's test console logger does not support `Loggable` arg. + // Rebind logger to suppress `[Function (anonymous)]` messages in tests when the storage service is initialized without `window.localStorage`. + // https://github.com/eclipse-theia/theia/blob/04c8cf07843ea67402131132e033cdd54900c010/packages/core/src/browser/storage-service.ts#L60 + bind(MockLogger).toSelf().inSingletonScope(); + rebind(ConsoleLogger).toService(MockLogger); + }) + ); + return container; + } +}); diff --git a/arduino-ide-extension/src/test/browser/boards-auto-installer.test.ts b/arduino-ide-extension/src/test/browser/boards-auto-installer.test.ts deleted file mode 100644 index 00570fff6..000000000 --- a/arduino-ide-extension/src/test/browser/boards-auto-installer.test.ts +++ /dev/null @@ -1,247 +0,0 @@ -// import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom'; -// const disableJSDOM = enableJSDOM(); - -// import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; -// import { ApplicationProps } from '@theia/application-package/lib/application-props'; -// FrontendApplicationConfigProvider.set({ -// ...ApplicationProps.DEFAULT.frontend.config, -// }); - -// import { MessageService } from '@theia/core'; -// import { BoardsServiceProvider } from '../../browser/boards/boards-service-provider'; -// import { BoardsListWidgetFrontendContribution } from '../../browser/boards/boards-widget-frontend-contribution'; -// import { -// Board, -// BoardsPackage, -// BoardsService, -// Port, -// ResponseServiceArduino, -// } from '../../common/protocol'; -// import { IMock, It, Mock, Times } from 'typemoq'; -// import { Container, ContainerModule } from '@theia/core/shared/inversify'; -// import { BoardsAutoInstaller } from '../../browser/boards/boards-auto-installer'; -// import { BoardsConfig } from '../../browser/boards/boards-config'; -// import { tick } from '../utils'; -// import { ListWidget } from '../../browser/widgets/component-list/list-widget'; - -// disableJSDOM(); - -// const aBoard: Board = { -// fqbn: 'some:board:fqbn', -// name: 'Some Arduino Board', -// port: { address: '/lol/port1234', protocol: 'serial' }, -// }; -// const aPort: Port = { -// address: aBoard.port!.address, -// protocol: aBoard.port!.protocol, -// }; -// const aBoardConfig: BoardsConfig.Config = { -// selectedBoard: aBoard, -// selectedPort: aPort, -// }; -// const aPackage: BoardsPackage = { -// author: 'someAuthor', -// availableVersions: ['some.ver.sion', 'some.other.version'], -// boards: [aBoard], -// deprecated: false, -// description: 'Some Arduino Board, Some Other Arduino Board', -// id: 'some:arduinoCoreId', -// installable: true, -// moreInfoLink: 'http://www.some-url.lol/', -// name: 'Some Arduino Package', -// summary: 'Boards included in this package:', -// }; - -// const anInstalledPackage: BoardsPackage = { -// ...aPackage, -// installedVersion: 'some.ver.sion', -// }; - -// describe('BoardsAutoInstaller', () => { -// let subject: BoardsAutoInstaller; -// let messageService: IMock; -// let boardsService: IMock; -// let boardsServiceClient: IMock; -// let responseService: IMock; -// let boardsManagerFrontendContribution: IMock; -// let boardsManagerWidget: IMock>; - -// let testContainer: Container; - -// beforeEach(() => { -// testContainer = new Container(); -// messageService = Mock.ofType(); -// boardsService = Mock.ofType(); -// boardsServiceClient = Mock.ofType(); -// responseService = Mock.ofType(); -// boardsManagerFrontendContribution = -// Mock.ofType(); -// boardsManagerWidget = Mock.ofType>(); - -// boardsManagerWidget.setup((b) => -// b.refresh(aPackage.name.toLocaleLowerCase()) -// ); - -// boardsManagerFrontendContribution -// .setup((b) => b.openView({ reveal: true })) -// .returns(async () => boardsManagerWidget.object); - -// messageService -// .setup((m) => m.showProgress(It.isAny(), It.isAny())) -// .returns(async () => ({ -// cancel: () => null, -// id: '', -// report: () => null, -// result: Promise.resolve(''), -// })); - -// responseService -// .setup((r) => r.onProgressDidChange(It.isAny())) -// .returns(() => ({ dispose: () => null })); - -// const module = new ContainerModule((bind) => { -// bind(BoardsAutoInstaller).toSelf(); -// bind(MessageService).toConstantValue(messageService.object); -// bind(BoardsService).toConstantValue(boardsService.object); -// bind(BoardsServiceProvider).toConstantValue(boardsServiceClient.object); -// bind(ResponseServiceArduino).toConstantValue(responseService.object); -// bind(BoardsListWidgetFrontendContribution).toConstantValue( -// boardsManagerFrontendContribution.object -// ); -// }); - -// testContainer.load(module); -// subject = testContainer.get(BoardsAutoInstaller); -// }); - -// context('when it starts', () => { -// it('should register to the BoardsServiceClient in order to check the packages every a new board is plugged in', () => { -// subject.onStart(); -// boardsServiceClient.verify( -// (b) => b.onBoardsConfigChanged(It.isAny()), -// Times.once() -// ); -// }); - -// context('and it checks the installable packages', () => { -// context(`and a port and a board a selected`, () => { -// beforeEach(() => { -// boardsServiceClient -// .setup((b) => b.boardsConfig) -// .returns(() => aBoardConfig); -// }); -// context('if no package for the board is already installed', () => { -// context('if a candidate package for the board is found', () => { -// beforeEach(() => { -// boardsService -// .setup((b) => b.search(It.isValue({}))) -// .returns(async () => [aPackage]); -// }); -// it('should show a notification suggesting to install that package', async () => { -// messageService -// .setup((m) => -// m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()) -// ) -// .returns(() => Promise.resolve('Install Manually')); -// subject.onStart(); -// await tick(); -// messageService.verify( -// (m) => -// m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()), -// Times.once() -// ); -// }); -// context(`if the answer to the message is 'Yes'`, () => { -// beforeEach(() => { -// messageService -// .setup((m) => -// m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()) -// ) -// .returns(() => Promise.resolve('Yes')); -// }); -// it('should install the package', async () => { -// subject.onStart(); - -// await tick(); - -// messageService.verify( -// (m) => m.showProgress(It.isAny(), It.isAny()), -// Times.once() -// ); -// }); -// }); -// context( -// `if the answer to the message is 'Install Manually'`, -// () => { -// beforeEach(() => { -// messageService -// .setup((m) => -// m.info( -// It.isAnyString(), -// It.isAnyString(), -// It.isAnyString() -// ) -// ) -// .returns(() => Promise.resolve('Install Manually')); -// }); -// it('should open the boards manager widget', () => { -// subject.onStart(); -// }); -// } -// ); -// }); -// context('if a candidate package for the board is not found', () => { -// beforeEach(() => { -// boardsService -// .setup((b) => b.search(It.isValue({}))) -// .returns(async () => []); -// }); -// it('should do nothing', async () => { -// subject.onStart(); -// await tick(); -// messageService.verify( -// (m) => -// m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()), -// Times.never() -// ); -// }); -// }); -// }); -// context( -// 'if one of the packages for the board is already installed', -// () => { -// beforeEach(() => { -// boardsService -// .setup((b) => b.search(It.isValue({}))) -// .returns(async () => [aPackage, anInstalledPackage]); -// messageService -// .setup((m) => -// m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()) -// ) -// .returns(() => Promise.resolve('Yes')); -// }); -// it('should do nothing', async () => { -// subject.onStart(); -// await tick(); -// messageService.verify( -// (m) => -// m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()), -// Times.never() -// ); -// }); -// } -// ); -// }); -// context('and there is no selected board or port', () => { -// it('should do nothing', async () => { -// subject.onStart(); -// await tick(); -// messageService.verify( -// (m) => m.info(It.isAnyString(), It.isAnyString(), It.isAnyString()), -// Times.never() -// ); -// }); -// }); -// }); -// }); -// }); diff --git a/arduino-ide-extension/src/test/browser/browser-test-bindings.ts b/arduino-ide-extension/src/test/browser/browser-test-bindings.ts new file mode 100644 index 000000000..9165765f7 --- /dev/null +++ b/arduino-ide-extension/src/test/browser/browser-test-bindings.ts @@ -0,0 +1,8 @@ +import { Container, ContainerModule } from '@theia/core/shared/inversify'; +import { bindCommon } from '../common/common-test-bindings'; + +export function createBaseContainer(): Container { + const container = new Container({ defaultScope: 'Singleton' }); + container.load(new ContainerModule((bind) => bindCommon(bind))); + return container; +} diff --git a/arduino-ide-extension/src/test/browser/fixtures/boards.ts b/arduino-ide-extension/src/test/browser/fixtures/boards.ts index 16256f3ab..f42f9a757 100644 --- a/arduino-ide-extension/src/test/browser/fixtures/boards.ts +++ b/arduino-ide-extension/src/test/browser/fixtures/boards.ts @@ -1,44 +1,36 @@ -import { BoardsConfig } from '../../../browser/boards/boards-config'; -import { Board, BoardsPackage, Port } from '../../../common/protocol'; +import type { + Board, + BoardsConfig, + BoardsPackage, + Port, +} from '../../../common/protocol'; export const aBoard: Board = { fqbn: 'some:board:fqbn', name: 'Some Arduino Board', - port: { - address: '/lol/port1234', - addressLabel: '/lol/port1234', - protocol: 'serial', - protocolLabel: 'Serial Port (USB)', - }, -}; -export const aPort: Port = { - address: aBoard.port!.address, - addressLabel: aBoard.port!.addressLabel, - protocol: aBoard.port!.protocol, - protocolLabel: aBoard.port!.protocolLabel, -}; -export const aBoardConfig: BoardsConfig.Config = { - selectedBoard: aBoard, +}; +const aPort: Port = { + address: '/lol/port1234', + addressLabel: '/lol/port1234', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', +}; +export const aBoardsConfig: BoardsConfig = { + selectedBoard: { name: aBoard.name, fqbn: aBoard.fqbn }, selectedPort: aPort, }; export const anotherBoard: Board = { fqbn: 'another:board:fqbn', name: 'Another Arduino Board', - port: { - address: '/kek/port5678', - addressLabel: '/kek/port5678', - protocol: 'serial', - protocolLabel: 'Serial Port (USB)', - }, }; export const anotherPort: Port = { - address: anotherBoard.port!.address, - addressLabel: anotherBoard.port!.addressLabel, - protocol: anotherBoard.port!.protocol, - protocolLabel: anotherBoard.port!.protocolLabel, + address: '/kek/port5678', + addressLabel: '/kek/port5678', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', }; -export const anotherBoardConfig: BoardsConfig.Config = { - selectedBoard: anotherBoard, +export const anotherBoardsConfig: BoardsConfig = { + selectedBoard: { name: anotherBoard.name, fqbn: anotherBoard.fqbn }, selectedPort: anotherPort, }; diff --git a/arduino-ide-extension/src/test/common/board-list.test.ts b/arduino-ide-extension/src/test/common/board-list.test.ts new file mode 100644 index 000000000..b7d301356 --- /dev/null +++ b/arduino-ide-extension/src/test/common/board-list.test.ts @@ -0,0 +1,588 @@ +import { expect } from 'chai'; +import { + BoardListLabels, + createBoardList, + EditBoardsConfigActionParams, + isInferredBoardListItem, + isMultiBoardsBoardListItem, + SelectBoardsConfigActionParams, +} from '../../common/protocol/board-list'; +import { + emptyBoardsConfig, + notConnected, + selectBoard, + unconfirmedBoard, +} from '../../common/protocol/boards-service'; +import { + arduinoNanoEsp32, + bluetoothSerialPort, + builtinSerialPort, + createPort, + detectedPort, + detectedPorts, + esp32NanoEsp32, + esp32S3Box, + esp32S3DevModule, + history, + mkr1000, + mkr1000NetworkPort, + mkr1000SerialPort, + nanoEsp32DetectsMultipleEsp32BoardsSerialPort, + nanoEsp32SerialPort, + undiscoveredSerialPort, + undiscoveredUsbToUARTSerialPort, + uno, + unoSerialPort, +} from './fixtures'; + +describe('board-list', () => { + describe('boardList#labels', () => { + it('should handle no selected board+port', () => { + const { labels } = createBoardList({}); + const expected: BoardListLabels = { + boardLabel: selectBoard, + portProtocol: undefined, + selected: false, + tooltip: selectBoard, + }; + expect(labels).to.be.deep.equal(expected); + }); + + it('should handle port selected (port detected)', () => { + const { labels } = createBoardList( + { + ...detectedPort(unoSerialPort, uno), + }, + { selectedBoard: undefined, selectedPort: unoSerialPort } + ); + const expected: BoardListLabels = { + boardLabel: selectBoard, + portProtocol: undefined, + selected: false, + tooltip: unoSerialPort.address, + }; + expect(labels).to.be.deep.equal(expected); + }); + + it('should handle port selected (port not detected)', () => { + const { labels } = createBoardList( + { + ...detectedPort(mkr1000SerialPort, mkr1000), + }, + { selectedBoard: undefined, selectedPort: unoSerialPort } + ); + const expected: BoardListLabels = { + boardLabel: selectBoard, + portProtocol: undefined, + selected: false, + tooltip: `${unoSerialPort.address} ${notConnected}`, + }; + expect(labels).to.be.deep.equal(expected); + }); + + it('should handle board selected (with FQBN)', () => { + const { labels } = createBoardList( + {}, + { selectedBoard: uno, selectedPort: undefined } + ); + const expected: BoardListLabels = { + boardLabel: uno.name, + portProtocol: undefined, + selected: false, + tooltip: `${uno.name} (${uno.fqbn})`, + }; + expect(labels).to.be.deep.equal(expected); + }); + + it('should handle board selected (no FQBN)', () => { + const { labels } = createBoardList( + {}, + { + selectedBoard: { name: 'my board', fqbn: undefined }, + selectedPort: undefined, + } + ); + const expected: BoardListLabels = { + boardLabel: 'my board', + portProtocol: undefined, + selected: false, + tooltip: 'my board', + }; + expect(labels).to.be.deep.equal(expected); + }); + + it('should handle both selected (port not detected)', () => { + const { labels } = createBoardList( + { + ...detectedPort(mkr1000SerialPort, mkr1000), + }, + { selectedBoard: mkr1000, selectedPort: unoSerialPort } + ); + const expected: BoardListLabels = { + boardLabel: mkr1000.name, + portProtocol: 'serial', + selected: false, + tooltip: `${mkr1000.name} (${mkr1000.fqbn})\n${unoSerialPort.address} ${notConnected}`, + }; + expect(labels).to.be.deep.equal(expected); + }); + + it('should handle both selected (board not discovered)', () => { + const { labels } = createBoardList( + { + ...detectedPort(unoSerialPort, uno), + }, + { selectedBoard: mkr1000, selectedPort: unoSerialPort } + ); + const expected: BoardListLabels = { + boardLabel: mkr1000.name, + portProtocol: 'serial', + selected: false, + tooltip: `${mkr1000.name} (${mkr1000.fqbn})\n${unoSerialPort.address}`, + }; + expect(labels).to.be.deep.equal(expected); + }); + + it('should handle both selected (no FQBN)', () => { + const { labels } = createBoardList( + { + ...detectedPort(unoSerialPort, { name: 'my board', fqbn: undefined }), + }, + { + selectedBoard: { name: 'my board', fqbn: undefined }, + selectedPort: unoSerialPort, + } + ); + const expected: BoardListLabels = { + boardLabel: 'my board', + portProtocol: 'serial', + selected: true, + tooltip: `my board\n${unoSerialPort.address}`, + }; + expect(labels).to.be.deep.equal(expected); + }); + + it('should handle both selected', () => { + const { labels } = createBoardList( + { + ...detectedPort(mkr1000NetworkPort, mkr1000), + }, + { selectedBoard: mkr1000, selectedPort: mkr1000NetworkPort } + ); + const expected: BoardListLabels = { + boardLabel: mkr1000.name, + portProtocol: 'network', + selected: true, + tooltip: `${mkr1000.name} (${mkr1000.fqbn})\n${mkr1000NetworkPort.address}`, + }; + expect(labels).to.be.deep.equal(expected); + }); + }); + + describe('createBoardList', () => { + it('should sort the items deterministically', () => { + const { items } = createBoardList(detectedPorts); + + expect(items.length).to.be.equal(Object.keys(detectedPorts).length); + expect(items[0].board).deep.equal(mkr1000); + expect(items[1].board).deep.equal(uno); + expect(items[2].board).is.undefined; + expect(isMultiBoardsBoardListItem(items[2])).to.be.true; + const boards2 = isMultiBoardsBoardListItem(items[2]) + ? items[2].boards + : undefined; + expect(boards2).deep.equal([arduinoNanoEsp32, esp32NanoEsp32]); + expect(items[3].board).is.undefined; + expect(isMultiBoardsBoardListItem(items[3])).to.be.true; + const boards3 = isMultiBoardsBoardListItem(items[3]) + ? items[3].boards + : undefined; + expect(boards3).deep.equal([esp32S3Box, esp32S3DevModule]); + expect(items[4].port).deep.equal(builtinSerialPort); + expect(items[5].port).deep.equal(bluetoothSerialPort); + expect(items[6].port).deep.equal(undiscoveredSerialPort); + expect(items[7].port).deep.equal(undiscoveredUsbToUARTSerialPort); + expect(items[8].port.protocol).equal('network'); + expect(items[8].board).deep.equal(mkr1000); + }); + + it('should sort Arduino items before others', () => { + const detectedPorts = { + ...detectedPort(createPort('a'), { name: 'aa', fqbn: 'arduino:a:a' }), + ...detectedPort(createPort('b'), { name: 'ab', fqbn: 'other:a:b' }), + ...detectedPort(createPort('c'), { name: 'ac', fqbn: 'arduino:a:c' }), + }; + const { items } = createBoardList(detectedPorts); + + expect(items.length).to.be.equal(3); + expect(items[0].board?.name).to.be.equal('aa'); + expect(items[1].board?.name).to.be.equal('ac'); + expect(items[2].board?.name).to.be.equal('ab'); + }); + + it('should sort items by inferred board if any', () => { + const portA = createPort('portA'); + const portB = createPort('portB'); + const detectedPorts = { + ...detectedPort(portA), + ...detectedPort(portB), + }; + const boardListHistory = { + ...history(portA, { name: 'bbb', fqbn: undefined }), + ...history(portB, { name: 'aaa', fqbn: undefined }), + }; + const { items } = createBoardList( + detectedPorts, + emptyBoardsConfig(), + boardListHistory + ); + + expect(items.length).to.be.equal(2); + expect(items[0].port.address).to.be.equal('portB'); + expect(items[0].board).to.be.undefined; + const inferredBoardA = isInferredBoardListItem(items[0]) + ? items[0].inferredBoard + : undefined; + expect(inferredBoardA).to.be.not.undefined; + expect(inferredBoardA?.name).to.be.equal('aaa'); + + expect(items[1].port.address).to.be.equal('portA'); + expect(items[1].board).to.be.undefined; + expect(isInferredBoardListItem(items[1])).to.be.true; + const inferredBoardB = isInferredBoardListItem(items[1]) + ? items[1].inferredBoard + : undefined; + expect(inferredBoardB).to.be.not.undefined; + expect(inferredBoardB?.name).to.be.equal('bbb'); + }); + + it('should sort ambiguous boards with unique board name before other ambiguous boards', () => { + const portA = createPort('portA'); + const portB = createPort('portB'); + const unique_ArduinoZZZ = { fqbn: 'arduino:e:f', name: 'zzz' }; + const unique_OtherZZZ = { fqbn: 'a:b:c', name: 'zzz' }; + const nonUnique_AAA = { fqbn: 'g:h:i', name: 'aaa' }; + const nonUnique_BBB = { fqbn: 'j:k:l', name: 'bbb' }; + const detectedPorts = { + ...detectedPort(portA, nonUnique_AAA, nonUnique_BBB), + ...detectedPort(portB, unique_OtherZZZ, unique_ArduinoZZZ), + }; + const { items } = createBoardList(detectedPorts); + + expect(items.length).to.be.equal(2); + expect(isMultiBoardsBoardListItem(items[0])).to.be.true; + const ambiguousBoardWithUniqueName = isMultiBoardsBoardListItem(items[0]) + ? items[0] + : undefined; + expect(ambiguousBoardWithUniqueName).to.be.not.undefined; + expect(ambiguousBoardWithUniqueName?.labels.boardLabel).to.be.equal( + unique_ArduinoZZZ.name + ); + expect(ambiguousBoardWithUniqueName?.port).to.be.deep.equal(portB); + expect(ambiguousBoardWithUniqueName?.boards).to.be.deep.equal([ + unique_ArduinoZZZ, + unique_OtherZZZ, + ]); + + expect(isMultiBoardsBoardListItem(items[1])).to.be.true; + const ambiguousBoardWithoutName = isMultiBoardsBoardListItem(items[1]) + ? items[1] + : undefined; + expect(ambiguousBoardWithoutName).to.be.not.undefined; + expect(ambiguousBoardWithoutName?.labels.boardLabel).to.be.equal( + unconfirmedBoard + ); + expect(ambiguousBoardWithoutName?.port).to.be.deep.equal(portA); + expect(ambiguousBoardWithoutName?.boards).to.be.deep.equal([ + nonUnique_AAA, + nonUnique_BBB, + ]); + }); + + it('should detect when a discovered board is overridden by a historical selection', () => { + const otherBoard = { name: 'other', fqbn: 'a:b:c' }; + const detectedPorts = { + ...detectedPort(unoSerialPort, uno), + }; + const boardListHistory = { + ...history(unoSerialPort, otherBoard), + }; + const { items } = createBoardList( + detectedPorts, + emptyBoardsConfig(), + boardListHistory + ); + + expect(items.length).to.be.equal(1); + const inferredBoard = isInferredBoardListItem(items[0]) + ? items[0] + : undefined; + expect(inferredBoard).is.not.undefined; + expect(inferredBoard?.inferredBoard).to.be.deep.equal(otherBoard); + expect(inferredBoard?.board).to.be.deep.equal(uno); + }); + + describe('defaultAction', () => { + it("'select' should be the default action for identifier boards", () => { + const { items } = createBoardList({ + ...detectedPort(mkr1000SerialPort, mkr1000), + }); + const item = items[0]; + expect(item.defaultAction.type).to.be.equal('select-boards-config'); + expect(item.defaultAction.params).to.be.deep.equal({ + selectedPort: mkr1000SerialPort, + selectedBoard: mkr1000, + }); + }); + + it("'select' should be the default action for manually selected items (no discovered boards)", () => { + const { items } = createBoardList( + { + ...detectedPort(undiscoveredSerialPort), + }, + emptyBoardsConfig(), + { + ...history(undiscoveredSerialPort, uno), + } + ); + const item = items[0]; + expect(item.defaultAction.type).to.be.equal('select-boards-config'); + expect(item.defaultAction.params).to.be.deep.equal({ + selectedPort: undiscoveredSerialPort, + selectedBoard: uno, + }); + }); + + it("'select' should be the default action for manually selected items (ambiguous boards)", () => { + const { items } = createBoardList( + { + ...detectedPort( + nanoEsp32SerialPort, + arduinoNanoEsp32, + esp32NanoEsp32 + ), + }, + emptyBoardsConfig(), + { + ...history(nanoEsp32SerialPort, mkr1000), + } + ); + const item = items[0]; + expect(item.defaultAction.type).to.be.equal('select-boards-config'); + expect(item.defaultAction.params).to.be.deep.equal({ + selectedBoard: mkr1000, + selectedPort: nanoEsp32SerialPort, + }); + }); + + it("'edit' should be the default action for ports with no boards", () => { + const { items } = createBoardList({ + ...detectedPort(undiscoveredSerialPort), + }); + const item = items[0]; + expect(item).to.be.not.undefined; + expect(item.defaultAction.type).to.be.equal('edit-boards-config'); + const params = item.defaultAction.params; + const expectedParams: EditBoardsConfigActionParams = { + query: '', + portToSelect: undiscoveredSerialPort, + }; + expect(params).to.be.deep.equal(expectedParams); + }); + + it("'edit' should be the default action for ports with multiple boards (unique board name)", () => { + const { items } = createBoardList({ + ...detectedPort( + nanoEsp32SerialPort, + arduinoNanoEsp32, + esp32NanoEsp32 + ), + }); + const item = items[0]; + expect(item).to.be.not.undefined; + expect(item.defaultAction.type).to.be.equal('edit-boards-config'); + const params = item.defaultAction.params; + const expectedParams: EditBoardsConfigActionParams = { + query: arduinoNanoEsp32.name, + portToSelect: nanoEsp32SerialPort, + searchSet: [arduinoNanoEsp32, esp32NanoEsp32], + }; + expect(params).to.be.deep.equal(expectedParams); + }); + + it("'edit' should be the default action for ports with multiple boards (no unique board name)", () => { + const { items } = createBoardList({ + ...detectedPort( + nanoEsp32DetectsMultipleEsp32BoardsSerialPort, + esp32S3DevModule, + esp32S3Box + ), + }); + const item = items[0]; + expect(item).to.be.not.undefined; + expect(item.defaultAction.type).to.be.equal('edit-boards-config'); + const params = item.defaultAction.params; + const expectedParams: EditBoardsConfigActionParams = { + query: '', + portToSelect: nanoEsp32DetectsMultipleEsp32BoardsSerialPort, + searchSet: [esp32S3Box, esp32S3DevModule], + }; + expect(params).to.be.deep.equal(expectedParams); + }); + }); + + describe('otherActions', () => { + it('should provide no other actions for identified board', () => { + const { items } = createBoardList({ + ...detectedPort(mkr1000SerialPort, mkr1000), + }); + const item = items[0]; + expect(item.otherActions).to.be.empty; + }); + + it('should provide no other actions for identified board (when historical revision is self)', () => { + const { items } = createBoardList( + { + ...detectedPort(mkr1000SerialPort, mkr1000), + }, + emptyBoardsConfig(), + { + ...history(mkr1000SerialPort, mkr1000), + } + ); + const item = items[0]; + expect(item.otherActions).to.be.empty; + }); + + it('should provide no other actions for unknown boards', () => { + const { items } = createBoardList({ + ...detectedPort(undiscoveredSerialPort), + }); + const item = items[0]; + expect(item.otherActions).to.be.empty; + }); + + it('should provide no other actions for ambiguous boards', () => { + const { items } = createBoardList({ + ...detectedPort( + nanoEsp32SerialPort, + arduinoNanoEsp32, + esp32NanoEsp32 + ), + }); + const item = items[0]; + expect(item.otherActions).to.be.empty; + }); + + it("should provide 'edit' action for unidentified items with manually selected board", () => { + const { items } = createBoardList( + { + ...detectedPort(undiscoveredSerialPort), + }, + emptyBoardsConfig(), + { + ...history(undiscoveredSerialPort, uno), + } + ); + const item = items[0]; + const expectedParams: EditBoardsConfigActionParams = { + query: uno.name, + portToSelect: undiscoveredSerialPort, + }; + expect(item.otherActions).to.be.deep.equal({ + edit: { + params: expectedParams, + type: 'edit-boards-config', + }, + }); + }); + + it("should provide 'edit' action for ambiguous items with manually selected board (unique board name)", () => { + const { items } = createBoardList( + { + ...detectedPort( + nanoEsp32SerialPort, + esp32NanoEsp32, + arduinoNanoEsp32 + ), + }, + emptyBoardsConfig(), + { + ...history(nanoEsp32SerialPort, arduinoNanoEsp32), + } + ); + const item = items[0]; + const expectedParams: EditBoardsConfigActionParams = { + query: arduinoNanoEsp32.name, + portToSelect: nanoEsp32SerialPort, + searchSet: [arduinoNanoEsp32, esp32NanoEsp32], + }; + expect(item.otherActions).to.be.deep.equal({ + edit: { + params: expectedParams, + type: 'edit-boards-config', + }, + }); + }); + + it("should provide 'edit' action for ambiguous items with manually selected board (no unique board name)", () => { + const { items } = createBoardList( + { + ...detectedPort( + nanoEsp32DetectsMultipleEsp32BoardsSerialPort, + esp32S3Box, + esp32S3DevModule + ), + }, + emptyBoardsConfig(), + { + ...history(nanoEsp32DetectsMultipleEsp32BoardsSerialPort, uno), + } + ); + const item = items[0]; + const expectedParams: EditBoardsConfigActionParams = { + query: '', + portToSelect: nanoEsp32DetectsMultipleEsp32BoardsSerialPort, + searchSet: [esp32S3Box, esp32S3DevModule], + }; + expect(item.otherActions).to.be.deep.equal({ + edit: { + params: expectedParams, + type: 'edit-boards-config', + }, + }); + }); + + it("should provide 'edit' and 'revert' actions for identified items with a manually overridden board", () => { + const { items } = createBoardList( + { + ...detectedPort(mkr1000SerialPort, mkr1000), + }, + emptyBoardsConfig(), + { + ...history(mkr1000SerialPort, uno), + } + ); + const item = items[0]; + const expectedEditParams: EditBoardsConfigActionParams = { + query: uno.name, + portToSelect: mkr1000SerialPort, + }; + const expectedRevertParams: SelectBoardsConfigActionParams = { + selectedBoard: mkr1000, + selectedPort: item.port, + }; + expect(item.otherActions).to.be.deep.equal({ + edit: { + params: expectedEditParams, + type: 'edit-boards-config', + }, + revert: { + params: expectedRevertParams, + type: 'select-boards-config', + }, + }); + }); + }); + }); +}); diff --git a/arduino-ide-extension/src/test/common/boards-service.test.ts b/arduino-ide-extension/src/test/common/boards-service.test.ts index d2cae5a53..f70bd2a7d 100644 --- a/arduino-ide-extension/src/test/common/boards-service.test.ts +++ b/arduino-ide-extension/src/test/common/boards-service.test.ts @@ -1,8 +1,8 @@ -import { Deferred } from '@theia/core/lib/common/promise-util'; -import { Mutable } from '@theia/core/lib/common/types'; +import type { Mutable } from '@theia/core/lib/common/types'; import { expect } from 'chai'; import { - AttachedBoardsChangeEvent, + boardIdentifierEquals, + boardIdentifierComparator, BoardInfo, getBoardInfo, noNativeSerialPort, @@ -11,88 +11,116 @@ import { selectPortForInfo, unknownBoard, } from '../../common/protocol'; +import { createBoardList } from '../../common/protocol/board-list'; import { firstToUpperCase } from '../../common/utils'; describe('boards-service', () => { - describe('AttachedBoardsChangeEvent', () => { - it('should detect one attached port', () => { - const event = { - oldState: { - boards: [ - { - name: 'Arduino MKR1000', - fqbn: 'arduino:samd:mkr1000', - port: '/dev/cu.usbmodem14601', - }, - { - name: 'Arduino Uno', - fqbn: 'arduino:avr:uno', - port: '/dev/cu.usbmodem14501', - }, - ], - ports: [ - { - protocol: 'serial', - address: '/dev/cu.usbmodem14501', - }, - { - protocol: 'serial', - address: '/dev/cu.usbmodem14601', - }, - { - protocol: 'serial', - address: '/dev/cu.Bluetooth-Incoming-Port', - }, - { protocol: 'serial', address: '/dev/cu.MALS' }, - { protocol: 'serial', address: '/dev/cu.SOC' }, - ], - }, - newState: { - boards: [ - { - name: 'Arduino MKR1000', - fqbn: 'arduino:samd:mkr1000', - port: '/dev/cu.usbmodem1460', - }, - { - name: 'Arduino Uno', - fqbn: 'arduino:avr:uno', - port: '/dev/cu.usbmodem14501', - }, - ], - ports: [ - { - protocol: 'serial', - address: '/dev/cu.SLAB_USBtoUART', - }, - { - protocol: 'serial', - address: '/dev/cu.usbmodem14501', - }, - { - protocol: 'serial', - address: '/dev/cu.usbmodem14601', - }, - { - protocol: 'serial', - address: '/dev/cu.Bluetooth-Incoming-Port', - }, - { protocol: 'serial', address: '/dev/cu.MALS' }, - { protocol: 'serial', address: '/dev/cu.SOC' }, - ], - }, - }; - const diff = AttachedBoardsChangeEvent.diff(event); - expect(diff.attached.boards).to.be.empty; // tslint:disable-line:no-unused-expression - expect(diff.detached.boards).to.be.empty; // tslint:disable-line:no-unused-expression - expect(diff.detached.ports).to.be.empty; // tslint:disable-line:no-unused-expression - expect(diff.attached.ports.length).to.be.equal(1); - expect(diff.attached.ports[0].address).to.be.equal( - '/dev/cu.SLAB_USBtoUART' + describe('boardIdentifierEquals', () => { + it('should not be equal when the names equal but the FQBNs are different', () => { + const actual = boardIdentifierEquals( + { name: 'a', fqbn: 'a:b:c' }, + { name: 'a', fqbn: 'x:y:z' } + ); + expect(actual).to.be.false; + }); + + it('should not be equal when the names equal but the FQBNs are different (undefined)', () => { + const actual = boardIdentifierEquals( + { name: 'a', fqbn: 'a:b:c' }, + { name: 'a', fqbn: undefined } + ); + expect(actual).to.be.false; + }); + + it("should be equal when the names do not match but the FQBNs are the same (it's something IDE2 assumes to be handled by the platform or CLI)", () => { + const actual = boardIdentifierEquals( + { name: 'a', fqbn: 'a:b:c' }, + { name: 'b', fqbn: 'a:b:c' } + ); + expect(actual).to.be.true; + }); + + it('should be equal when the names equal and the FQBNs are missing', () => { + const actual = boardIdentifierEquals( + { name: 'a', fqbn: undefined }, + { name: 'a', fqbn: undefined } + ); + expect(actual).to.be.true; + }); + + it('should be equal when both the name and FQBN are the same, but one of the FQBN has board config options', () => { + const actual = boardIdentifierEquals( + { name: 'a', fqbn: 'a:b:c:menu_1=value' }, + { name: 'a', fqbn: 'a:b:c' } + ); + expect(actual).to.be.true; + }); + + it('should not be equal when both the name and FQBN are the same, but one of the FQBN has board config options (looseFqbn: false)', () => { + const actual = boardIdentifierEquals( + { name: 'a', fqbn: 'a:b:c:menu_1=value' }, + { name: 'a', fqbn: 'a:b:c' }, + { looseFqbn: false } ); + expect(actual).to.be.false; }); }); + describe('boardIdentifierComparator', () => { + it('should sort items before falsy', () => + expect( + boardIdentifierComparator({ name: 'a', fqbn: 'a:b:c' }, undefined) + ).to.be.equal(-1)); + + it("should sort 'arduino' boards before others", () => + expect( + boardIdentifierComparator( + { name: 'b', fqbn: 'arduino:b:c' }, + { name: 'a', fqbn: 'x:y:z' } + ) + ).to.be.equal(-1)); + + it("should sort 'arduino' boards before others (other is falsy)", () => + expect( + boardIdentifierComparator( + { name: 'b', fqbn: 'arduino:b:c' }, + { name: 'a', fqbn: undefined } + ) + ).to.be.equal(-1)); + + it("should sort boards by 'name' (with FQBNs)", () => + expect( + boardIdentifierComparator( + { name: 'b', fqbn: 'a:b:c' }, + { name: 'a', fqbn: 'x:y:z' } + ) + ).to.be.equal(1)); + + it("should sort boards by 'name' (no FQBNs)", () => + expect( + boardIdentifierComparator( + { name: 'b', fqbn: undefined }, + { name: 'a', fqbn: undefined } + ) + ).to.be.equal(1)); + + it("should sort boards by 'name' (one FQBN)", () => + expect( + boardIdentifierComparator( + { name: 'b', fqbn: 'a:b:c' }, + { name: 'a', fqbn: undefined } + ) + ).to.be.equal(1)); + + it("should sort boards by 'name' (both 'arduino' vendor)", () => + expect( + boardIdentifierComparator( + { name: 'b', fqbn: 'arduino:b:c' }, + { name: 'a', fqbn: 'arduino:y:z' } + ) + ).to.be.equal(1)); + }); + describe('getBoardInfo', () => { const vid = '0x0'; const pid = '0x1'; @@ -112,7 +140,7 @@ describe('boards-service', () => { }); it('should handle when no port is selected', async () => { - const info = await getBoardInfo(undefined, never()); + const info = await getBoardInfo(createBoardList({})); expect(info).to.be.equal(selectPortForInfo); }); @@ -125,7 +153,11 @@ describe('boards-service', () => { protocolLabel: firstToUpperCase(protocol), protocol, }; - const info = await getBoardInfo(selectedPort, never()); + const boardList = createBoardList( + { [Port.keyOf(selectedPort)]: { port: selectedPort } }, + { selectedPort, selectedBoard: undefined } + ); + const info = await getBoardInfo(boardList); expect(info).to.be.equal(nonSerialPort); }) ); @@ -140,18 +172,26 @@ describe('boards-service', () => { ]; for (const properties of insufficientProperties) { const port = selectedPort(properties); - const info = await getBoardInfo(port, { - [Port.keyOf(port)]: [port, []], - }); + const boardList = createBoardList( + { + [Port.keyOf(port)]: { port }, + }, + { selectedPort: port, selectedBoard: undefined } + ); + const info = await getBoardInfo(boardList); expect(info).to.be.equal(noNativeSerialPort); } }); it("should detect a port as non-native serial, if protocol is 'serial' and VID/PID are available", async () => { const port = selectedPort({ vid, pid }); - const info = await getBoardInfo(port, { - [Port.keyOf(port)]: [port, []], - }); + const boardList = createBoardList( + { + [Port.keyOf(port)]: { port }, + }, + { selectedPort: port, selectedBoard: undefined } + ); + const info = await getBoardInfo(boardList); expect(typeof info).to.be.equal('object'); const boardInfo = info; expect(boardInfo.VID).to.be.equal(vid); @@ -162,9 +202,13 @@ describe('boards-service', () => { it("should show the 'SN' even if no matching board was detected for the port", async () => { const port = selectedPort({ vid, pid, serialNumber }); - const info = await getBoardInfo(port, { - [Port.keyOf(port)]: [port, []], - }); + const boardList = createBoardList( + { + [Port.keyOf(port)]: { port }, + }, + { selectedPort: port, selectedBoard: undefined } + ); + const info = await getBoardInfo(boardList); expect(typeof info).to.be.equal('object'); const boardInfo = info; expect(boardInfo.VID).to.be.equal(vid); @@ -175,9 +219,13 @@ describe('boards-service', () => { it("should use the name of the matching board as 'BN' if available", async () => { const port = selectedPort({ vid, pid }); - const info = await getBoardInfo(port, { - [Port.keyOf(port)]: [port, [selectedBoard]], - }); + const boardList = createBoardList( + { + [Port.keyOf(port)]: { port, boards: [selectedBoard] }, + }, + { selectedPort: port, selectedBoard: undefined } + ); + const info = await getBoardInfo(boardList); expect(typeof info).to.be.equal('object'); const boardInfo = info; expect(boardInfo.VID).to.be.equal(vid); @@ -187,7 +235,3 @@ describe('boards-service', () => { }); }); }); - -function never(): Promise { - return new Deferred().promise; -} diff --git a/arduino-ide-extension/src/test/common/common-test-bindings.ts b/arduino-ide-extension/src/test/common/common-test-bindings.ts new file mode 100644 index 000000000..1c1892dc2 --- /dev/null +++ b/arduino-ide-extension/src/test/common/common-test-bindings.ts @@ -0,0 +1,97 @@ +import { + CommandContribution, + CommandRegistry, + CommandService, +} from '@theia/core/lib/common/command'; +import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider'; +import { ILogger, Loggable } from '@theia/core/lib/common/logger'; +import { LogLevel } from '@theia/core/lib/common/logger-protocol'; +import { MockLogger } from '@theia/core/lib/common/test/mock-logger'; +import { injectable, interfaces } from '@theia/core/shared/inversify'; + +export function bindCommon(bind: interfaces.Bind): interfaces.Bind { + bind(ConsoleLogger).toSelf().inSingletonScope(); + bind(ILogger).toService(ConsoleLogger); + bind(CommandRegistry).toSelf().inSingletonScope(); + bind(CommandService).toService(CommandRegistry); + bindContributionProvider(bind, CommandContribution); + return bind; +} + +@injectable() +export class ConsoleLogger extends MockLogger { + override log( + logLevel: number, + arg2: string | Loggable | Error, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...params: any[] + ): Promise { + if (arg2 instanceof Error) { + return this.error(String(arg2), params); + } + switch (logLevel) { + case LogLevel.INFO: + return this.info(arg2, params); + case LogLevel.WARN: + return this.warn(arg2, params); + case LogLevel.TRACE: + return this.trace(arg2, params); + case LogLevel.ERROR: + return this.error(arg2, params); + case LogLevel.FATAL: + return this.fatal(arg2, params); + default: + return this.info(arg2, params); + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + override async info(arg: string | Loggable, ...params: any[]): Promise { + if (params.length) { + console.info(arg, ...params); + } else { + console.info(arg); + } + } + + override async trace( + arg: string | Loggable, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...params: any[] + ): Promise { + if (params.length) { + console.trace(arg, ...params); + } else { + console.trace(arg); + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + override async warn(arg: string | Loggable, ...params: any[]): Promise { + if (params.length) { + console.warn(arg, ...params); + } else { + console.warn(arg); + } + } + + override async error( + arg: string | Loggable, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...params: any[] + ): Promise { + if (params.length) { + console.error(arg, ...params); + } else { + console.error(arg); + } + } + + override async fatal( + arg: string | Loggable, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...params: any[] + ): Promise { + return this.error(arg, params); + } +} diff --git a/arduino-ide-extension/src/test/common/fixtures.ts b/arduino-ide-extension/src/test/common/fixtures.ts new file mode 100644 index 000000000..294984524 --- /dev/null +++ b/arduino-ide-extension/src/test/common/fixtures.ts @@ -0,0 +1,176 @@ +import { + BoardIdentifier, + DetectedPort, + DetectedPorts, + Port, +} from '../../common/protocol/boards-service'; + +export const mkr1000: BoardIdentifier = { + name: 'Arduino MKR1000', + fqbn: 'arduino:samd:mkr1000', +}; +export const uno: BoardIdentifier = { + name: 'Arduino Uno', + fqbn: 'arduino:avr:uno', +}; +export const arduinoNanoEsp32: BoardIdentifier = { + fqbn: 'arduino:esp32:nano_nora', + name: 'Arduino Nano ESP32', +}; +export const esp32NanoEsp32: BoardIdentifier = { + fqbn: 'esp32:esp32:nano_nora', + name: 'Arduino Nano ESP32', +}; +export const esp32S3DevModule: BoardIdentifier = { + name: 'ESP32S3 Dev Module', + fqbn: 'esp32:esp32:esp32s3', +}; +export const esp32S3Box: BoardIdentifier = { + name: 'ESP32-S3-Box', + fqbn: 'esp32:esp32:esp32s3box', +}; + +export const bluetoothSerialPort: Port = { + address: '/dev/cu.Bluetooth-Incoming-Port', + addressLabel: '/dev/cu.Bluetooth-Incoming-Port', + protocol: 'serial', + protocolLabel: 'Serial Port', + properties: {}, + hardwareId: '', +}; +export const builtinSerialPort: Port = { + address: '/dev/cu.BLTH', + addressLabel: '/dev/cu.BLTH', + protocol: 'serial', + protocolLabel: 'Serial Port', + properties: {}, + hardwareId: '', +}; +export const undiscoveredSerialPort: Port = { + address: '/dev/cu.usbserial-0001', + addressLabel: '/dev/cu.usbserial-0001', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0xEA60', + serialNumber: '0001', + vid: '0x10C4', + }, + hardwareId: '0001', +}; +export const mkr1000NetworkPort: Port = { + address: '192.168.0.104', + addressLabel: 'Arduino at 192.168.0.104', + protocol: 'network', + protocolLabel: 'Network Port', + properties: { + '.': 'mkr1000', + auth_upload: 'yes', + board: 'mkr1000', + hostname: 'Arduino.local.', + port: '65280', + ssh_upload: 'no', + tcp_check: 'no', + }, + hardwareId: '', +}; +export const undiscoveredUsbToUARTSerialPort: Port = { + address: '/dev/cu.SLAB_USBtoUART', + addressLabel: '/dev/cu.SLAB_USBtoUART', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0xEA60', + serialNumber: '0001', + vid: '0x10C4', + }, + hardwareId: '0001', +}; +export const mkr1000SerialPort: Port = { + address: '/dev/cu.usbmodem14301', + addressLabel: '/dev/cu.usbmodem14301', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0x804E', + serialNumber: '94A3397C5150435437202020FF150838', + vid: '0x2341', + }, + hardwareId: '94A3397C5150435437202020FF150838', +}; +export const unoSerialPort: Port = { + address: '/dev/cu.usbmodem14201', + addressLabel: '/dev/cu.usbmodem14201', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0x0043', + serialNumber: '75830303934351618212', + vid: '0x2341', + }, + hardwareId: '75830303934351618212', +}; +export const nanoEsp32SerialPort: Port = { + address: '/dev/cu.usbmodem3485187BD9882', + addressLabel: '/dev/cu.usbmodem3485187BD9882', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0x0070', + serialNumber: '3485187BD988', + vid: '0x2341', + }, + hardwareId: '3485187BD988', +}; +export const nanoEsp32DetectsMultipleEsp32BoardsSerialPort: Port = { + address: 'COM41', + addressLabel: 'COM41', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0x1001', + serialNumber: '', + vid: '0x303A', + }, +}; + +export function createPort(address: string, protocol = 'serial'): Port { + return { + address, + addressLabel: `Address label: ${address}`, + protocol, + protocolLabel: `Protocol label: ${protocol}`, + }; +} + +export function detectedPort( + port: Port, + ...boards: BoardIdentifier[] +): { [portKey: string]: DetectedPort } { + return { [Port.keyOf(port)]: boards.length ? { port, boards } : { port } }; +} + +export function history( + port: Port, + board: BoardIdentifier +): { [portKey: string]: BoardIdentifier } { + return { [Port.keyOf(port)]: board }; +} + +export const detectedPorts: DetectedPorts = { + ...detectedPort(builtinSerialPort), + ...detectedPort(bluetoothSerialPort), + ...detectedPort(unoSerialPort, uno), + ...detectedPort(mkr1000SerialPort, mkr1000), + ...detectedPort(mkr1000NetworkPort, mkr1000), + ...detectedPort(undiscoveredSerialPort), + ...detectedPort(undiscoveredUsbToUARTSerialPort), + // multiple discovered on the same port with different board names + ...detectedPort( + nanoEsp32DetectsMultipleEsp32BoardsSerialPort, + esp32S3DevModule, + esp32S3Box + ), + // multiple discovered on the same port with the same board name + ...detectedPort(nanoEsp32SerialPort, arduinoNanoEsp32, esp32NanoEsp32), +}; diff --git a/arduino-ide-extension/src/test/node/boards-service-impl.slow-test.ts b/arduino-ide-extension/src/test/node/boards-service-impl.slow-test.ts index de243d2f6..fbd5aeb4d 100644 --- a/arduino-ide-extension/src/test/node/boards-service-impl.slow-test.ts +++ b/arduino-ide-extension/src/test/node/boards-service-impl.slow-test.ts @@ -2,7 +2,7 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { Container } from '@theia/core/shared/inversify'; import { expect } from 'chai'; import { BoardSearch, BoardsService } from '../../common/protocol'; -import { createBaseContainer, startDaemon } from './test-bindings'; +import { createBaseContainer, startDaemon } from './node-test-bindings'; describe('boards-service-impl', () => { let boardService: BoardsService; diff --git a/arduino-ide-extension/src/test/node/clang-formatter.test.ts b/arduino-ide-extension/src/test/node/clang-formatter.test.ts index b8814a48d..10e5dc543 100644 --- a/arduino-ide-extension/src/test/node/clang-formatter.test.ts +++ b/arduino-ide-extension/src/test/node/clang-formatter.test.ts @@ -12,7 +12,7 @@ import { ClangFormatter, } from '../../node/clang-formatter'; import { spawnCommand } from '../../node/exec-util'; -import { createBaseContainer, startDaemon } from './test-bindings'; +import { createBaseContainer, startDaemon } from './node-test-bindings'; const unformattedContent = `void setup ( ) { pinMode(LED_BUILTIN, OUTPUT); } diff --git a/arduino-ide-extension/src/test/node/core-client-provider.slow-test.ts b/arduino-ide-extension/src/test/node/core-client-provider.slow-test.ts index 8ce8670be..1bc09c929 100644 --- a/arduino-ide-extension/src/test/node/core-client-provider.slow-test.ts +++ b/arduino-ide-extension/src/test/node/core-client-provider.slow-test.ts @@ -3,6 +3,7 @@ import type { MaybePromise } from '@theia/core/lib/common/types'; import { FileUri } from '@theia/core/lib/node/file-uri'; import { Container } from '@theia/core/shared/inversify'; import { expect } from 'chai'; +import { dump, load } from 'js-yaml'; import { promises as fs } from 'node:fs'; import { join } from 'node:path'; import { sync as deleteSync } from 'rimraf'; @@ -23,7 +24,7 @@ import { createCliConfig, newTempConfigDirPath, startDaemon, -} from './test-bindings'; +} from './node-test-bindings'; const timeout = 5 * 60 * 1_000; // five minutes @@ -66,16 +67,11 @@ describe('core-client-provider', () => { const configDirPath = await prepareTestConfigDir(); deleteSync(join(configDirPath, 'data')); - const now = new Date().toISOString(); const container = await startCli(configDirPath, toDispose); await assertFunctionalCli(container, ({ coreClientProvider }) => { const { indexUpdateSummaryBeforeInit } = coreClientProvider; - const libUpdateTimestamp = indexUpdateSummaryBeforeInit['library']; - expect(libUpdateTimestamp).to.be.not.empty; - expect(libUpdateTimestamp.localeCompare(now)).to.be.greaterThan(0); - const platformUpdateTimestamp = indexUpdateSummaryBeforeInit['platform']; - expect(platformUpdateTimestamp).to.be.not.empty; - expect(platformUpdateTimestamp.localeCompare(now)).to.be.greaterThan(0); + expect(indexUpdateSummaryBeforeInit).to.be.not.undefined; + expect(indexUpdateSummaryBeforeInit).to.be.empty; }); }); @@ -90,14 +86,11 @@ describe('core-client-provider', () => { ); deleteSync(primaryPackageIndexPath); - const now = new Date().toISOString(); const container = await startCli(configDirPath, toDispose); await assertFunctionalCli(container, ({ coreClientProvider }) => { const { indexUpdateSummaryBeforeInit } = coreClientProvider; - expect(indexUpdateSummaryBeforeInit['library']).to.be.undefined; - const platformUpdateTimestamp = indexUpdateSummaryBeforeInit['platform']; - expect(platformUpdateTimestamp).to.be.not.empty; - expect(platformUpdateTimestamp.localeCompare(now)).to.be.greaterThan(0); + expect(indexUpdateSummaryBeforeInit).to.be.not.undefined; + expect(indexUpdateSummaryBeforeInit).to.be.empty; }); const rawJson = await fs.readFile(primaryPackageIndexPath, { encoding: 'utf8', @@ -149,14 +142,11 @@ describe('core-client-provider', () => { ); deleteSync(libraryPackageIndexPath); - const now = new Date().toISOString(); const container = await startCli(configDirPath, toDispose); await assertFunctionalCli(container, ({ coreClientProvider }) => { const { indexUpdateSummaryBeforeInit } = coreClientProvider; - const libUpdateTimestamp = indexUpdateSummaryBeforeInit['library']; - expect(libUpdateTimestamp).to.be.not.empty; - expect(libUpdateTimestamp.localeCompare(now)).to.be.greaterThan(0); - expect(indexUpdateSummaryBeforeInit['platform']).to.be.undefined; + expect(indexUpdateSummaryBeforeInit).to.be.not.undefined; + expect(indexUpdateSummaryBeforeInit).to.be.empty; }); const rawJson = await fs.readFile(libraryPackageIndexPath, { encoding: 'utf8', @@ -191,20 +181,38 @@ describe('core-client-provider', () => { const container = await startCli(configDirPath, toDispose); await assertFunctionalCli( container, - async ({ coreClientProvider, boardsService, coreService }) => { + async ({ coreClientProvider, boardsService }) => { const { indexUpdateSummaryBeforeInit } = coreClientProvider; expect(indexUpdateSummaryBeforeInit).to.be.not.undefined; expect(indexUpdateSummaryBeforeInit).to.be.empty; - - // IDE2 cannot recover from a 3rd party package index issue. - // Only when the primary package or library index is corrupt. - // https://github.com/arduino/arduino-ide/issues/2021 - await coreService.updateIndex({ types: ['platform'] }); - await assertTeensyAvailable(boardsService); } ); }); + + it("should recover when invalid 3rd package URL is defined in the CLI config and the 'directories.data' folder is missing", async function () { + this.timeout(timeout); + const configDirPath = await prepareTestConfigDir(); + deleteSync(join(configDirPath, 'data')); + + // set an invalid URL so the CLI will try to download it + const cliConfigPath = join(configDirPath, 'arduino-cli.yaml'); + const rawYaml = await fs.readFile(cliConfigPath, { encoding: 'utf8' }); + const config: DefaultCliConfig = load(rawYaml); + expect(config.board_manager).to.be.undefined; + config.board_manager = { additional_urls: ['https://invalidUrl'] }; + expect(config.board_manager?.additional_urls?.[0]).to.be.equal( + 'https://invalidUrl' + ); + await fs.writeFile(cliConfigPath, dump(config)); + + const container = await startCli(configDirPath, toDispose); + await assertFunctionalCli(container, ({ coreClientProvider }) => { + const { indexUpdateSummaryBeforeInit } = coreClientProvider; + expect(indexUpdateSummaryBeforeInit).to.be.not.undefined; + expect(indexUpdateSummaryBeforeInit).to.be.empty; + }); + }); }); interface Services { @@ -277,7 +285,7 @@ async function prepareTestConfigDir( const params = { configDirPath: newTempConfigDirPath(), configOverrides }; const container = await createContainer(params); const daemon = container.get(ArduinoDaemonImpl); - const cliPath = await daemon.getExecPath(); + const cliPath = daemon.getExecPath(); const configDirUriProvider = container.get(ConfigDirUriProvider); const configDirPath = FileUri.fsPath(configDirUriProvider.configDirUri()); diff --git a/arduino-ide-extension/src/test/node/core-service-impl.slow-test.ts b/arduino-ide-extension/src/test/node/core-service-impl.slow-test.ts index 0a0fd253c..5bf9e5381 100644 --- a/arduino-ide-extension/src/test/node/core-service-impl.slow-test.ts +++ b/arduino-ide-extension/src/test/node/core-service-impl.slow-test.ts @@ -11,7 +11,7 @@ import { SketchesService, isCompileSummary, } from '../../common/protocol'; -import { createBaseContainer, startDaemon } from './test-bindings'; +import { createBaseContainer, startDaemon } from './node-test-bindings'; const testTimeout = 30_000; const setupTimeout = 5 * 60 * 1_000; // five minutes diff --git a/arduino-ide-extension/src/test/node/core-service-impl.test.ts b/arduino-ide-extension/src/test/node/core-service-impl.test.ts index abba88357..ac419dd2d 100644 --- a/arduino-ide-extension/src/test/node/core-service-impl.test.ts +++ b/arduino-ide-extension/src/test/node/core-service-impl.test.ts @@ -1,5 +1,10 @@ import { expect } from 'chai'; -import { Port } from '../../node/cli-protocol/cc/arduino/cli/commands/v1/port_pb'; +import { + PortIdentifier, + portIdentifierEquals, + Port, +} from '../../common/protocol/boards-service'; +import { Port as RpcPort } from '../../node/cli-protocol/cc/arduino/cli/commands/v1/port_pb'; import { CoreServiceImpl } from '../../node/core-service-impl'; describe('core-service-impl', () => { @@ -22,9 +27,18 @@ describe('core-service-impl', () => { protocolLabel: 'serial port', properties, } as const; - const actual = new CoreServiceImpl()['createPort'](port); + const resolve = (toResolve: PortIdentifier): Port | undefined => { + if (portIdentifierEquals(toResolve, port)) { + return port; + } + return undefined; + }; + const actual = new CoreServiceImpl()['createPort']( + { protocol: port.protocol, address: port.address }, + resolve + ); expect(actual).to.be.not.undefined; - const expected = new Port() + const expected = new RpcPort() .setAddress(port.address) .setHardwareId(port.hardwareId) .setLabel(port.addressLabel) @@ -33,7 +47,7 @@ describe('core-service-impl', () => { Object.entries(properties).forEach(([key, value]) => expected.getPropertiesMap().set(key, value) ); - expect((actual).toObject(false)).to.be.deep.equal( + expect((actual).toObject(false)).to.be.deep.equal( expected.toObject(false) ); }); diff --git a/arduino-ide-extension/src/test/node/library-service-impl.slow-test.ts b/arduino-ide-extension/src/test/node/library-service-impl.slow-test.ts index 694b86d96..6473660da 100644 --- a/arduino-ide-extension/src/test/node/library-service-impl.slow-test.ts +++ b/arduino-ide-extension/src/test/node/library-service-impl.slow-test.ts @@ -2,7 +2,7 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { Container } from '@theia/core/shared/inversify'; import { expect } from 'chai'; import { LibrarySearch, LibraryService } from '../../common/protocol'; -import { createBaseContainer, startDaemon } from './test-bindings'; +import { createBaseContainer, startDaemon } from './node-test-bindings'; describe('library-service-impl', () => { let libraryService: LibraryService; diff --git a/arduino-ide-extension/src/test/node/test-bindings.ts b/arduino-ide-extension/src/test/node/node-test-bindings.ts similarity index 78% rename from arduino-ide-extension/src/test/node/test-bindings.ts rename to arduino-ide-extension/src/test/node/node-test-bindings.ts index 30d8513a9..2b4c651f7 100644 --- a/arduino-ide-extension/src/test/node/test-bindings.ts +++ b/arduino-ide-extension/src/test/node/node-test-bindings.ts @@ -1,18 +1,9 @@ -import { - CommandContribution, - CommandRegistry, - CommandService, -} from '@theia/core/lib/common/command'; -import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider'; import { Disposable, DisposableCollection, } from '@theia/core/lib/common/disposable'; import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables'; -import { ILogger, Loggable } from '@theia/core/lib/common/logger'; -import { LogLevel } from '@theia/core/lib/common/logger-protocol'; import { waitForEvent } from '@theia/core/lib/common/promise-util'; -import { MockLogger } from '@theia/core/lib/common/test/mock-logger'; import URI from '@theia/core/lib/common/uri'; import { FileUri } from '@theia/core/lib/node/file-uri'; import { ProcessUtils } from '@theia/core/lib/node/process-utils'; @@ -23,19 +14,18 @@ import { interfaces, } from '@theia/core/shared/inversify'; import deepmerge from 'deepmerge'; -import { promises as fs, mkdirSync } from 'node:fs'; import { dump as dumpYaml } from 'js-yaml'; +import { mkdirSync, promises as fs } from 'node:fs'; import { join } from 'node:path'; import { path as tempPath, track } from 'temp'; import { ArduinoDaemon, - AttachedBoardsChangeEvent, - AvailablePorts, BoardsPackage, BoardsService, ConfigService, ConfigState, CoreService, + DetectedPorts, IndexUpdateDidCompleteParams, IndexUpdateDidFailParams, IndexUpdateParams, @@ -52,7 +42,7 @@ import { import { ArduinoDaemonImpl } from '../../node/arduino-daemon-impl'; import { BoardDiscovery } from '../../node/board-discovery'; import { BoardsServiceImpl } from '../../node/boards-service-impl'; -import { CLI_CONFIG, CliConfig, DefaultCliConfig } from '../../node/cli-config'; +import { CliConfig, CLI_CONFIG, DefaultCliConfig } from '../../node/cli-config'; import { ConfigServiceImpl } from '../../node/config-service-impl'; import { CoreClientProvider } from '../../node/core-client-provider'; import { CoreServiceImpl } from '../../node/core-service-impl'; @@ -70,87 +60,10 @@ import { ConfigDirUriProvider, EnvVariablesServer, } from '../../node/theia/env-variables/env-variables-server'; +import { bindCommon } from '../common/common-test-bindings'; const tracked = track(); -@injectable() -class ConsoleLogger extends MockLogger { - override log( - logLevel: number, - arg2: string | Loggable | Error, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...params: any[] - ): Promise { - if (arg2 instanceof Error) { - return this.error(String(arg2), params); - } - switch (logLevel) { - case LogLevel.INFO: - return this.info(arg2, params); - case LogLevel.WARN: - return this.warn(arg2, params); - case LogLevel.TRACE: - return this.trace(arg2, params); - case LogLevel.ERROR: - return this.error(arg2, params); - case LogLevel.FATAL: - return this.fatal(arg2, params); - default: - return this.info(arg2, params); - } - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - override async info(arg: string | Loggable, ...params: any[]): Promise { - if (params.length) { - console.info(arg, ...params); - } else { - console.info(arg); - } - } - - override async trace( - arg: string | Loggable, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...params: any[] - ): Promise { - if (params.length) { - console.trace(arg, ...params); - } else { - console.trace(arg); - } - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - override async warn(arg: string | Loggable, ...params: any[]): Promise { - if (params.length) { - console.warn(arg, ...params); - } else { - console.warn(arg); - } - } - - override async error( - arg: string | Loggable, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...params: any[] - ): Promise { - if (params.length) { - console.error(arg, ...params); - } else { - console.error(arg); - } - } - - override async fatal( - arg: string | Loggable, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...params: any[] - ): Promise { - return this.error(arg, params); - } -} - @injectable() class SilentArduinoDaemon extends ArduinoDaemonImpl { protected override onData(): void { @@ -160,7 +73,7 @@ class SilentArduinoDaemon extends ArduinoDaemonImpl { @injectable() class TestBoardDiscovery extends BoardDiscovery { - mutableAvailablePorts: AvailablePorts = {}; + mutableDetectedPorts: DetectedPorts = {}; override async start(): Promise { // NOOP @@ -168,8 +81,8 @@ class TestBoardDiscovery extends BoardDiscovery { override async stop(): Promise { // NOOP } - override get availablePorts(): AvailablePorts { - return this.mutableAvailablePorts; + override get detectedPorts(): DetectedPorts { + return this.mutableDetectedPorts; } } @@ -221,7 +134,7 @@ class TestNotificationServiceServer implements NotificationServiceServer { notifyLibraryDidUninstall(event: { item: LibraryPackage }): void { this.events.push(`notifyLibraryDidUninstall:${JSON.stringify(event)}`); } - notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void { + notifyDetectedPortsDidChange(event: { detectedPorts: DetectedPorts }): void { this.events.push(`notifyAttachedBoardsDidChange:${JSON.stringify(event)}`); } notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void { @@ -309,6 +222,7 @@ export async function createBaseContainer( } const container = new Container({ defaultScope: 'Singleton' }); const module = new ContainerModule((bind, unbind, isBound, rebind) => { + bindCommon(bind); bind(CoreClientProvider).toSelf().inSingletonScope(); bind(CoreServiceImpl).toSelf().inSingletonScope(); bind(CoreService).toService(CoreServiceImpl); @@ -336,15 +250,10 @@ export async function createBaseContainer( bind(SilentArduinoDaemon).toSelf().inSingletonScope(); bind(ArduinoDaemon).toService(SilentArduinoDaemon); bind(ArduinoDaemonImpl).toService(SilentArduinoDaemon); - bind(ConsoleLogger).toSelf().inSingletonScope(); - bind(ILogger).toService(ConsoleLogger); bind(TestNotificationServiceServer).toSelf().inSingletonScope(); bind(NotificationServiceServer).toService(TestNotificationServiceServer); bind(ConfigServiceImpl).toSelf().inSingletonScope(); bind(ConfigService).toService(ConfigServiceImpl); - bind(CommandRegistry).toSelf().inSingletonScope(); - bind(CommandService).toService(CommandRegistry); - bindContributionProvider(bind, CommandContribution); bind(TestBoardDiscovery).toSelf().inSingletonScope(); bind(BoardDiscovery).toService(TestBoardDiscovery); bind(IsTempSketch).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/test/node/sketches-service-impl.slow-test.ts b/arduino-ide-extension/src/test/node/sketches-service-impl.slow-test.ts index 908bc827d..082a7474d 100644 --- a/arduino-ide-extension/src/test/node/sketches-service-impl.slow-test.ts +++ b/arduino-ide-extension/src/test/node/sketches-service-impl.slow-test.ts @@ -12,7 +12,7 @@ import { sync as rimrafSync } from 'rimraf'; import { Sketch, SketchesService } from '../../common/protocol'; import { SketchesServiceImpl } from '../../node/sketches-service-impl'; import { ErrnoException } from '../../node/utils/errors'; -import { createBaseContainer, startDaemon } from './test-bindings'; +import { createBaseContainer, startDaemon } from './node-test-bindings'; const testTimeout = 10_000; diff --git a/i18n/en.json b/i18n/en.json index 9a5b38eda..4fa5c1cff 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -18,24 +18,21 @@ "configDialog1": "Select both a Board and a Port if you want to upload a sketch.", "configDialog2": "If you only select a Board you will be able to compile, but not to upload your sketch.", "couldNotFindPreviouslySelected": "Could not find previously selected board '{0}' in installed platform '{1}'. Please manually reselect the board you want to use. Do you want to reselect it now?", - "disconnected": "Disconnected", + "editBoardsConfig": "Edit Board and Port...", "getBoardInfo": "Get Board Info", "inSketchbook": " (in Sketchbook)", "installNow": "The \"{0} {1}\" core has to be installed for the currently selected \"{2}\" board. Do you want to install it now?", "noBoardsFound": "No boards found for \"{0}\"", - "noFQBN": "The FQBN is not available for the selected board \"{0}\". Do you have the corresponding core installed?", "noNativeSerialPort": "Native serial port, can't obtain info.", "noPortsDiscovered": "No ports discovered", - "noPortsSelected": "No ports selected for board: '{0}'.", "nonSerialPort": "Non-serial port, can't obtain info.", - "noneSelected": "No boards selected.", "openBoardsConfig": "Select other board and port…", "pleasePickBoard": "Please pick a board connected to the port you have selected.", "port": "Port{0}", - "portLabel": "Port: {0}", "ports": "ports", "programmer": "Programmer", "reselectLater": "Reselect later", + "revertBoardsConfig": "Use '{0}' discovered on '{1}'", "searchBoard": "Search board", "selectBoard": "Select Board", "selectPortForInfo": "Please select a port to obtain board info.", @@ -44,6 +41,7 @@ "succesfullyInstalledPlatform": "Successfully installed platform {0}:{1}", "succesfullyUninstalledPlatform": "Successfully uninstalled platform {0}:{1}", "typeOfPorts": "{0} ports", + "unconfirmedBoard": "Unconfirmed board", "unknownBoard": "Unknown board" }, "boardsManager": "Boards Manager", @@ -215,6 +213,11 @@ "optimizeForDebugging": "Optimize for Debugging", "sketchIsNotCompiled": "Sketch '{0}' must be verified before starting a debug session. Please verify the sketch and start debugging again. Do you want to verify the sketch now?" }, + "developer": { + "clearBoardList": "Clear the Board List History", + "clearBoardsConfig": "Clear the Board and Port Selection", + "dumpBoardList": "Dump the Board List" + }, "dialog": { "dontAskAgain": "Don't ask again" },