mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-05-02 11:17:20 +00:00

* Removed Protocol type * Reworked function that groups ports by protocol * Remove useless protocol check in Port sameAs function * Reworked port selection menu ordering Now ports are shown in this order: 1. Serial with recognized boards 2. Serial with unrecognized boards 3. Network with recognized boards 4. Network with unrecognized boards 5. Other protocols with recognized boards 6. Other protocols with unrecognized boards * Fix ports shown multiple times in menu * Reworked board selection dropdown ordering Ordering is now: 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 * Localize some strings * Fix bug selecting board in boards selector dropdown * Reworked board selection dialog ordering * Fix Tools > Port menu not refreshing * Move Select other board button to bottom of Board selector dropdown and change its style * Updated arduino-cli to 0.20.0 and generated protocol files
364 lines
12 KiB
TypeScript
364 lines
12 KiB
TypeScript
import { inject, injectable } from 'inversify';
|
|
import { remote } from 'electron';
|
|
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
|
import {
|
|
DisposableCollection,
|
|
Disposable,
|
|
} from '@theia/core/lib/common/disposable';
|
|
import { firstToUpperCase } from '../../common/utils';
|
|
import { BoardsConfig } from '../boards/boards-config';
|
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
|
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,
|
|
} from '../../common/protocol';
|
|
import { SketchContribution, Command, CommandRegistry } from './contribution';
|
|
import { nls } from '@theia/core/lib/browser/nls';
|
|
|
|
@injectable()
|
|
export class BoardSelection extends SketchContribution {
|
|
@inject(CommandRegistry)
|
|
protected readonly commandRegistry: CommandRegistry;
|
|
|
|
@inject(MainMenuManager)
|
|
protected readonly mainMenuManager: MainMenuManager;
|
|
|
|
@inject(MenuModelRegistry)
|
|
protected readonly menuModelRegistry: MenuModelRegistry;
|
|
|
|
@inject(NotificationCenter)
|
|
protected readonly notificationCenter: NotificationCenter;
|
|
|
|
@inject(BoardsService)
|
|
protected readonly boardsService: BoardsService;
|
|
|
|
@inject(BoardsServiceProvider)
|
|
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
|
|
|
protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection();
|
|
|
|
registerCommands(registry: CommandRegistry): void {
|
|
registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, {
|
|
execute: async () => {
|
|
const { selectedBoard, selectedPort } =
|
|
this.boardsServiceProvider.boardsConfig;
|
|
if (!selectedBoard) {
|
|
this.messageService.info(
|
|
nls.localize(
|
|
'arduino/board/selectBoardForInfo',
|
|
'Please select a board to obtain board info.'
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
if (!selectedBoard.fqbn) {
|
|
this.messageService.info(
|
|
nls.localize(
|
|
'arduino/board/platformMissing',
|
|
"The platform for the selected '{0}' board is not installed.",
|
|
selectedBoard.name
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
if (!selectedPort) {
|
|
this.messageService.info(
|
|
nls.localize(
|
|
'arduino/board/selectPortForInfo',
|
|
'Please select a port to obtain board info.'
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
const boardDetails = await this.boardsService.getBoardDetails({
|
|
fqbn: selectedBoard.fqbn,
|
|
});
|
|
if (boardDetails) {
|
|
const { VID, PID } = boardDetails;
|
|
const detail = `BN: ${selectedBoard.name}
|
|
VID: ${VID}
|
|
PID: ${PID}`;
|
|
await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
|
message: nls.localize('arduino/board/boardInfo', 'Board Info'),
|
|
title: nls.localize('arduino/board/boardInfo', 'Board Info'),
|
|
type: 'info',
|
|
detail,
|
|
buttons: [nls.localize('vscode/issueMainService/ok', 'OK')],
|
|
});
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
onStart(): void {
|
|
this.updateMenus();
|
|
this.notificationCenter.onPlatformInstalled(this.updateMenus.bind(this));
|
|
this.notificationCenter.onPlatformUninstalled(this.updateMenus.bind(this));
|
|
this.boardsServiceProvider.onBoardsConfigChanged(
|
|
this.updateMenus.bind(this)
|
|
);
|
|
this.boardsServiceProvider.onAvailableBoardsChanged(
|
|
this.updateMenus.bind(this)
|
|
);
|
|
this.boardsServiceProvider.onAvailablePortsChanged(
|
|
this.updateMenus.bind(this)
|
|
);
|
|
}
|
|
|
|
protected async updateMenus(): Promise<void> {
|
|
const [installedBoards, availablePorts, config] = await Promise.all([
|
|
this.installedBoards(),
|
|
this.boardsService.getState(),
|
|
this.boardsServiceProvider.boardsConfig,
|
|
]);
|
|
this.rebuildMenus(installedBoards, availablePorts, config);
|
|
}
|
|
|
|
protected rebuildMenus(
|
|
installedBoards: InstalledBoardWithPackage[],
|
|
availablePorts: AvailablePorts,
|
|
config: BoardsConfig.Config
|
|
): void {
|
|
this.toDisposeBeforeMenuRebuild.dispose();
|
|
|
|
// Boards submenu
|
|
const boardsSubmenuPath = [
|
|
...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP,
|
|
'1_boards',
|
|
];
|
|
const boardsSubmenuLabel = config.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(
|
|
boardsSubmenuPath,
|
|
nls.localize(
|
|
'arduino/board/board',
|
|
'Board{0}',
|
|
!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''
|
|
),
|
|
{ order: '100' }
|
|
);
|
|
this.toDisposeBeforeMenuRebuild.push(
|
|
Disposable.create(() =>
|
|
unregisterSubmenu(boardsSubmenuPath, this.menuModelRegistry)
|
|
)
|
|
);
|
|
|
|
// Ports submenu
|
|
const portsSubmenuPath = [
|
|
...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP,
|
|
'2_ports',
|
|
];
|
|
const portsSubmenuLabel = config.selectedPort?.address;
|
|
this.menuModelRegistry.registerSubmenu(
|
|
portsSubmenuPath,
|
|
nls.localize(
|
|
'arduino/board/port',
|
|
'Port{0}',
|
|
portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''
|
|
),
|
|
{ order: '101' }
|
|
);
|
|
this.toDisposeBeforeMenuRebuild.push(
|
|
Disposable.create(() =>
|
|
unregisterSubmenu(portsSubmenuPath, this.menuModelRegistry)
|
|
)
|
|
);
|
|
|
|
const getBoardInfo = {
|
|
commandId: BoardSelection.Commands.GET_BOARD_INFO.id,
|
|
label: nls.localize('arduino/board/getBoardInfo', 'Get Board Info'),
|
|
order: '103',
|
|
};
|
|
this.menuModelRegistry.registerMenuAction(
|
|
ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP,
|
|
getBoardInfo
|
|
);
|
|
this.toDisposeBeforeMenuRebuild.push(
|
|
Disposable.create(() =>
|
|
this.menuModelRegistry.unregisterMenuAction(getBoardInfo)
|
|
)
|
|
);
|
|
|
|
const boardsManagerGroup = [...boardsSubmenuPath, '0_manager'];
|
|
const boardsPackagesGroup = [...boardsSubmenuPath, '1_packages'];
|
|
|
|
this.menuModelRegistry.registerMenuAction(boardsManagerGroup, {
|
|
commandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
|
|
label: `${BoardsListWidget.WIDGET_LABEL}...`,
|
|
});
|
|
|
|
// Installed boards
|
|
for (const board of installedBoards) {
|
|
const { packageId, packageName, fqbn, name, manuallyInstalled } = 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(),
|
|
});
|
|
|
|
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,
|
|
};
|
|
}
|
|
},
|
|
isToggled: () =>
|
|
fqbn === this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn,
|
|
};
|
|
|
|
// Board menu
|
|
const menuAction = { commandId: id, label: name };
|
|
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
|
|
const registerPorts = (
|
|
protocol: string,
|
|
protocolOrder: number,
|
|
ports: AvailablePorts
|
|
) => {
|
|
const addresses = Object.keys(ports);
|
|
if (!addresses.length) {
|
|
return;
|
|
}
|
|
|
|
// Register placeholder for protocol
|
|
const menuPath = [
|
|
...portsSubmenuPath,
|
|
`${protocolOrder.toString()}_${protocol}`,
|
|
];
|
|
const placeholder = new PlaceholderMenuNode(
|
|
menuPath,
|
|
`${firstToUpperCase(protocol)} ports`,
|
|
{ order: protocolOrder.toString() }
|
|
);
|
|
this.menuModelRegistry.registerMenuNode(menuPath, placeholder);
|
|
this.toDisposeBeforeMenuRebuild.push(
|
|
Disposable.create(() =>
|
|
this.menuModelRegistry.unregisterMenuNode(placeholder.id)
|
|
)
|
|
);
|
|
|
|
// First we show addresses with recognized boards connected,
|
|
// then all the rest.
|
|
const sortedAddresses = Object.keys(ports);
|
|
sortedAddresses.sort((left: string, right: string): number => {
|
|
const [, leftBoards] = ports[left];
|
|
const [, rightBoards] = ports[right];
|
|
return rightBoards.length - leftBoards.length;
|
|
});
|
|
|
|
for (let i = 0; i < sortedAddresses.length; i++) {
|
|
const address = sortedAddresses[i];
|
|
const [port, boards] = ports[address];
|
|
let label = `${address}`;
|
|
if (boards.length) {
|
|
const boardsList = boards.map((board) => board.name).join(', ');
|
|
label = `${label} (${boardsList})`;
|
|
}
|
|
const id = `arduino-select-port--${address}`;
|
|
const command = { id };
|
|
const handler = {
|
|
execute: () => {
|
|
if (
|
|
!Port.equals(
|
|
port,
|
|
this.boardsServiceProvider.boardsConfig.selectedPort
|
|
)
|
|
) {
|
|
this.boardsServiceProvider.boardsConfig = {
|
|
selectedBoard:
|
|
this.boardsServiceProvider.boardsConfig.selectedBoard,
|
|
selectedPort: port,
|
|
};
|
|
}
|
|
},
|
|
isToggled: () =>
|
|
Port.equals(
|
|
port,
|
|
this.boardsServiceProvider.boardsConfig.selectedPort
|
|
),
|
|
};
|
|
const menuAction = {
|
|
commandId: id,
|
|
label,
|
|
order: `${protocolOrder + i + 1}`,
|
|
};
|
|
this.commandRegistry.registerCommand(command, handler);
|
|
this.toDisposeBeforeMenuRebuild.push(
|
|
Disposable.create(() =>
|
|
this.commandRegistry.unregisterCommand(command)
|
|
)
|
|
);
|
|
this.menuModelRegistry.registerMenuAction(menuPath, menuAction);
|
|
}
|
|
};
|
|
|
|
const grouped = AvailablePorts.byProtocol(availablePorts);
|
|
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;
|
|
});
|
|
|
|
this.mainMenuManager.update();
|
|
}
|
|
|
|
protected async installedBoards(): Promise<InstalledBoardWithPackage[]> {
|
|
const allBoards = await this.boardsService.searchBoards({});
|
|
return allBoards.filter(InstalledBoardWithPackage.is);
|
|
}
|
|
}
|
|
export namespace BoardSelection {
|
|
export namespace Commands {
|
|
export const GET_BOARD_INFO: Command = { id: 'arduino-get-board-info' };
|
|
}
|
|
}
|