mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-24 11:46:32 +00:00
[experimental]: Introduced the Boards Control.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
12f2aa35ff
commit
d54a69935e
@ -36,9 +36,9 @@
|
||||
"@theia/workspace": "next",
|
||||
"@types/dateformat": "^3.0.1",
|
||||
"@types/deepmerge": "^2.2.0",
|
||||
"@types/js-yaml": "^3.12.2",
|
||||
"@types/glob": "^5.0.35",
|
||||
"@types/google-protobuf": "^3.7.1",
|
||||
"@types/js-yaml": "^3.12.2",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/ps-tree": "^1.1.0",
|
||||
"@types/react-select": "^3.0.0",
|
||||
@ -47,6 +47,7 @@
|
||||
"css-element-queries": "^1.2.0",
|
||||
"dateformat": "^3.0.3",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fuzzy": "^0.1.3",
|
||||
"glob": "^7.1.6",
|
||||
"google-protobuf": "^3.11.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@ -105,6 +106,9 @@
|
||||
{
|
||||
"frontend": "lib/browser/menu/browser-arduino-menu-module",
|
||||
"frontendElectron": "lib/electron-browser/menu/electron-arduino-menu-module"
|
||||
},
|
||||
{
|
||||
"frontend": "lib/browser/boards/quick-open/boards-quick-open-module"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { QuickOpenItem, QuickOpenModel } from '@theia/core/lib/common/quick-open-model';
|
||||
import { QuickOpenService, QuickOpenOptions } from '@theia/core/lib/browser/quick-open/quick-open-service';
|
||||
import { BoardsService, BoardsServiceClient } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class BoardsConfigQuickOpenService {
|
||||
|
||||
@inject(QuickOpenService)
|
||||
protected readonly quickOpenService: QuickOpenService;
|
||||
|
||||
@inject(BoardsService)
|
||||
protected readonly boardsService: BoardsService;
|
||||
|
||||
@inject(BoardsServiceClient)
|
||||
protected readonly boardsServiceClient: BoardsServiceClient;
|
||||
|
||||
async selectBoard(): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
protected open(items: QuickOpenItem | QuickOpenItem[], placeholder: string): void {
|
||||
this.quickOpenService.open(this.getModel(Array.isArray(items) ? items : [items]), this.getOptions(placeholder));
|
||||
}
|
||||
|
||||
protected getOptions(placeholder: string, fuzzyMatchLabel: boolean = true, onClose: (canceled: boolean) => void = () => { }): QuickOpenOptions {
|
||||
return QuickOpenOptions.resolve({
|
||||
placeholder,
|
||||
fuzzyMatchLabel,
|
||||
fuzzySort: false,
|
||||
onClose
|
||||
});
|
||||
}
|
||||
|
||||
protected getModel(items: QuickOpenItem | QuickOpenItem[]): QuickOpenModel {
|
||||
return {
|
||||
onType(_: string, acceptor: (items: QuickOpenItem[]) => void): void {
|
||||
acceptor(Array.isArray(items) ? items : [items]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -45,24 +45,25 @@ export class BoardsDetailsMenuUpdater implements FrontendApplicationContribution
|
||||
const boardsConfigMenuPath = [...ArduinoMenus.TOOLS, 'z_boardsConfig']; // `z_` is for ordering.
|
||||
for (const { label, option, values } of configOptions.sort(ConfigOption.LABEL_COMPARATOR)) {
|
||||
const menuPath = [...boardsConfigMenuPath, `${option}`];
|
||||
const commands = new Map<string, Disposable>()
|
||||
const commands = new Map<string, Disposable & { label: string }>()
|
||||
for (const value of values) {
|
||||
const id = `${fqbn}-${option}--${value.value}`;
|
||||
const command = { id, label: value.label };
|
||||
const command = { id };
|
||||
const selectedValue = value.value;
|
||||
const handler = {
|
||||
execute: () => this.boardsConfigStore.setSelected({ fqbn, option, selectedValue }),
|
||||
isToggled: () => value.selected
|
||||
};
|
||||
commands.set(id, this.commandRegistry.registerCommand(command, handler));
|
||||
commands.set(id, Object.assign(this.commandRegistry.registerCommand(command, handler), { label: value.label }));
|
||||
}
|
||||
this.menuRegistry.registerSubmenu(menuPath, label);
|
||||
this.toDisposeOnBoardChange.pushAll([
|
||||
...commands.values(),
|
||||
Disposable.create(() => this.unregisterSubmenu(menuPath)), // We cannot dispose submenu entries: https://github.com/eclipse-theia/theia/issues/7299
|
||||
...Array.from(commands.keys()).map((commandId, index) => {
|
||||
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: String(index) })
|
||||
return Disposable.create(() => this.menuRegistry.unregisterMenuAction(commandId))
|
||||
const { label } = commands.get(commandId)!;
|
||||
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: String(index), label });
|
||||
return Disposable.create(() => this.menuRegistry.unregisterMenuAction(commandId));
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
|
||||
import { RecursiveRequired } from '../../common/types';
|
||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, Board, Port, BoardUninstalledEvent } from '../../common/protocol';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
import { naturalCompare } from '../../common/utils';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApplicationContribution {
|
||||
@ -262,10 +263,10 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
});
|
||||
}
|
||||
|
||||
const sortedAvailableBoards = availableBoards.sort(AvailableBoard.COMPARATOR);
|
||||
const sortedAvailableBoards = availableBoards.sort(AvailableBoard.compare);
|
||||
let hasChanged = sortedAvailableBoards.length !== currentAvailableBoards.length;
|
||||
for (let i = 0; !hasChanged && i < sortedAvailableBoards.length; i++) {
|
||||
hasChanged = AvailableBoard.COMPARATOR(sortedAvailableBoards[i], currentAvailableBoards[i]) !== 0;
|
||||
hasChanged = AvailableBoard.compare(sortedAvailableBoards[i], currentAvailableBoards[i]) !== 0;
|
||||
}
|
||||
if (hasChanged) {
|
||||
this._availableBoards = sortedAvailableBoards;
|
||||
@ -312,9 +313,10 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of a ready-to-use board, configured by the user. Not all of the available boards are
|
||||
* 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`.
|
||||
* 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;
|
||||
@ -339,17 +341,27 @@ export namespace AvailableBoard {
|
||||
'incomplete'
|
||||
}
|
||||
|
||||
export function isWithPort(board: AvailableBoard): board is AvailableBoard & { port: Port } {
|
||||
export function is(board: any): board is AvailableBoard {
|
||||
return Board.is(board) && 'state' in board;
|
||||
}
|
||||
|
||||
export function hasPort(board: AvailableBoard): board is AvailableBoard & { port: Port } {
|
||||
return !!board.port;
|
||||
}
|
||||
|
||||
export const COMPARATOR = (left: AvailableBoard, right: AvailableBoard) => {
|
||||
let result = left.name.localeCompare(right.name);
|
||||
export const compare = (left: AvailableBoard, right: AvailableBoard) => {
|
||||
if (left.selected && !right.selected) {
|
||||
return -1;
|
||||
}
|
||||
if (right.selected && !left.selected) {
|
||||
return 1;
|
||||
}
|
||||
let result = naturalCompare(left.name, right.name);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
if (left.fqbn && right.fqbn) {
|
||||
result = left.name.localeCompare(right.name);
|
||||
result = naturalCompare(left.fqbn, right.fqbn);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
</div>
|
||||
<BoardsDropDown
|
||||
coords={coords}
|
||||
items={availableBoards.filter(AvailableBoard.isWithPort).map(board => ({
|
||||
items={availableBoards.filter(AvailableBoard.hasPort).map(board => ({
|
||||
...board,
|
||||
onClick: () => {
|
||||
if (board.state === AvailableBoard.State.incomplete) {
|
||||
|
@ -0,0 +1,16 @@
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { CommandContribution } from '@theia/core/lib/common/command';
|
||||
import { QuickOpenContribution } from '@theia/core/lib/browser/quick-open';
|
||||
import { KeybindingContribution } from '@theia/core/lib/browser/keybinding';
|
||||
import { BoardsQuickOpenService } from './boards-quick-open-service';
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
bind(BoardsQuickOpenService).toSelf().inSingletonScope();
|
||||
bind(CommandContribution).toService(BoardsQuickOpenService);
|
||||
bind(KeybindingContribution).toService(BoardsQuickOpenService);
|
||||
bind(QuickOpenContribution).toService(BoardsQuickOpenService);
|
||||
bind(ILogger).toDynamicValue(({ container }) => container.get<ILogger>(ILogger).child('boards-quick-open'))
|
||||
.inSingletonScope()
|
||||
.whenTargetNamed('boards-quick-open');
|
||||
});
|
@ -0,0 +1,309 @@
|
||||
import * as fuzzy from 'fuzzy';
|
||||
import { inject, injectable, postConstruct, named } from 'inversify';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common/command';
|
||||
import { KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import { QuickOpenItem, QuickOpenModel, QuickOpenMode, QuickOpenGroupItem } from '@theia/core/lib/common/quick-open-model';
|
||||
import {
|
||||
QuickOpenService,
|
||||
QuickOpenHandler,
|
||||
QuickOpenOptions,
|
||||
QuickOpenItemOptions,
|
||||
QuickOpenContribution,
|
||||
QuickOpenActionProvider,
|
||||
QuickOpenHandlerRegistry,
|
||||
QuickOpenGroupItemOptions
|
||||
} from '@theia/core/lib/browser/quick-open';
|
||||
import { naturalCompare } from '../../../common/utils';
|
||||
import { BoardsService, Port, Board, ConfigOption, ConfigValue } from '../../../common/protocol';
|
||||
import { CoreServiceClientImpl } from '../../core-service-client-impl';
|
||||
import { BoardsConfigStore } from '../boards-config-store';
|
||||
import { BoardsServiceClientImpl, AvailableBoard } from '../boards-service-client-impl';
|
||||
|
||||
@injectable()
|
||||
export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenModel, QuickOpenHandler, CommandContribution, KeybindingContribution, Command {
|
||||
|
||||
readonly id = 'arduino-boards-quick-open';
|
||||
readonly prefix = '|';
|
||||
readonly description = 'Configure Available Boards';
|
||||
readonly label: 'Configure Available Boards';
|
||||
|
||||
@inject(ILogger)
|
||||
@named('boards-quick-open')
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
@inject(QuickOpenService)
|
||||
protected readonly quickOpenService: QuickOpenService;
|
||||
|
||||
@inject(BoardsService)
|
||||
protected readonly boardsService: BoardsService;
|
||||
|
||||
@inject(BoardsServiceClientImpl)
|
||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||
|
||||
@inject(BoardsConfigStore)
|
||||
protected readonly configStore: BoardsConfigStore;
|
||||
|
||||
@inject(CoreServiceClientImpl)
|
||||
protected coreServiceClient: CoreServiceClientImpl;
|
||||
|
||||
protected isOpen: boolean = false;
|
||||
protected currentQuery: string = '';
|
||||
// Attached boards plus the user's config.
|
||||
protected availableBoards: AvailableBoard[] = [];
|
||||
// Only for the `selected` one from the `availableBoards`. Note: the `port` of the `selected` is optional.
|
||||
protected boardConfigs: ConfigOption[] = [];
|
||||
protected allBoards: Board.Detailed[] = []
|
||||
protected selectedBoard?: (AvailableBoard & { port: Port });
|
||||
|
||||
// `init` name is used by the `QuickOpenHandler`.
|
||||
@postConstruct()
|
||||
protected postConstruct(): void {
|
||||
this.coreServiceClient.onIndexUpdated(() => this.update(this.availableBoards));
|
||||
this.boardsServiceClient.onAvailableBoardsChanged(availableBoards => this.update(availableBoards));
|
||||
this.update(this.boardsServiceClient.availableBoards);
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(this, { execute: () => this.open() });
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({ command: this.id, keybinding: 'ctrlCmd+k ctrlCmd+b' });
|
||||
}
|
||||
|
||||
registerQuickOpenHandlers(registry: QuickOpenHandlerRegistry): void {
|
||||
registry.registerHandler(this);
|
||||
}
|
||||
|
||||
getModel(): QuickOpenModel {
|
||||
return this;
|
||||
}
|
||||
|
||||
getOptions(): QuickOpenOptions {
|
||||
let placeholder = '';
|
||||
if (!this.selectedBoard) {
|
||||
placeholder += 'No board selected.';
|
||||
}
|
||||
placeholder += 'Type to filter boards';
|
||||
if (this.boardConfigs.length) {
|
||||
placeholder += ' or use the ↓↑ keys to adjust the board settings...';
|
||||
} else {
|
||||
placeholder += '...';
|
||||
}
|
||||
return {
|
||||
placeholder,
|
||||
fuzzyMatchLabel: true,
|
||||
onClose: () => this.isOpen = false
|
||||
};
|
||||
}
|
||||
|
||||
open(): void {
|
||||
this.isOpen = true;
|
||||
this.quickOpenService.open(this, this.getOptions());
|
||||
}
|
||||
|
||||
onType(
|
||||
lookFor: string,
|
||||
acceptor: (items: QuickOpenItem<QuickOpenItemOptions>[], actionProvider?: QuickOpenActionProvider) => void): void {
|
||||
|
||||
this.currentQuery = lookFor;
|
||||
const fuzzyFilter = this.fuzzyFilter(lookFor);
|
||||
const availableBoards = this.availableBoards.filter(AvailableBoard.hasPort).filter(({ name }) => fuzzyFilter(name));
|
||||
const toAccept: QuickOpenItem<QuickOpenItemOptions>[] = [];
|
||||
|
||||
// Show the selected attached in a different group.
|
||||
if (this.selectedBoard && fuzzyFilter(this.selectedBoard.name)) {
|
||||
toAccept.push(this.toQuickItem(this.selectedBoard, { groupLabel: 'Selected Board' }));
|
||||
}
|
||||
|
||||
// Filter the selected from the attached ones.
|
||||
toAccept.push(...availableBoards.filter(board => board !== this.selectedBoard).map((board, i) => {
|
||||
let group: QuickOpenGroupItemOptions | undefined = undefined;
|
||||
if (i === 0) {
|
||||
// If no `selectedBoard`, then this item is the top one, no borders required.
|
||||
group = { groupLabel: 'Attached Boards', showBorder: !!this.selectedBoard };
|
||||
}
|
||||
return this.toQuickItem(board, group);
|
||||
}));
|
||||
|
||||
// Show the config only if the `input` is empty.
|
||||
if (!lookFor.trim().length) {
|
||||
toAccept.push(...this.boardConfigs.map((config, i) => {
|
||||
let group: QuickOpenGroupItemOptions | undefined = undefined;
|
||||
if (i === 0) {
|
||||
group = { groupLabel: 'Board Settings', showBorder: true };
|
||||
}
|
||||
return this.toQuickItem(config, group);
|
||||
}));
|
||||
} else {
|
||||
toAccept.push(...this.allBoards.filter(({ name }) => fuzzyFilter(name)).map((board, i) => {
|
||||
let group: QuickOpenGroupItemOptions | undefined = undefined;
|
||||
if (i === 0) {
|
||||
group = { groupLabel: 'Boards', showBorder: true };
|
||||
}
|
||||
return this.toQuickItem(board, group);
|
||||
}));
|
||||
}
|
||||
|
||||
acceptor(toAccept);
|
||||
}
|
||||
|
||||
private fuzzyFilter(lookFor: string): (inputString: string) => boolean {
|
||||
const shouldFilter = !!lookFor.trim().length;
|
||||
return (inputString: string) => shouldFilter ? fuzzy.test(lookFor.toLocaleLowerCase(), inputString.toLocaleLowerCase()) : true;
|
||||
}
|
||||
|
||||
protected async update(availableBoards: AvailableBoard[]): Promise<void> {
|
||||
// `selectedBoard` is not an attached board, we need to show the board settings for it (TODO: clarify!)
|
||||
const selectedBoard = availableBoards.filter(AvailableBoard.hasPort).find(({ selected }) => selected);
|
||||
const [configs, boards] = await Promise.all([
|
||||
selectedBoard && selectedBoard.fqbn ? this.configStore.getConfig(selectedBoard.fqbn) : Promise.resolve([]),
|
||||
this.boardsService.searchBoards({})
|
||||
]);
|
||||
this.allBoards = Board.decorateBoards(selectedBoard, boards)
|
||||
.filter(board => !availableBoards.some(availableBoard => Board.sameAs(availableBoard, board)));
|
||||
this.availableBoards = availableBoards;
|
||||
this.boardConfigs = configs;
|
||||
this.selectedBoard = selectedBoard;
|
||||
|
||||
if (this.isOpen) {
|
||||
// Hack, to update the state without closing and reopening the quick open widget.
|
||||
(this.quickOpenService as any).onType(this.currentQuery);
|
||||
}
|
||||
}
|
||||
|
||||
protected toQuickItem(item: BoardsQuickOpenService.Item, group?: QuickOpenGroupItemOptions): QuickOpenItem<QuickOpenItemOptions> {
|
||||
let options: QuickOpenItemOptions;
|
||||
if (AvailableBoard.is(item)) {
|
||||
const description = `on ${Port.toString(item.port)}`
|
||||
options = {
|
||||
label: `${item.name}`,
|
||||
description,
|
||||
descriptionHighlights: [
|
||||
{
|
||||
start: 0,
|
||||
end: description.length
|
||||
}
|
||||
],
|
||||
run: this.toRun(() => this.boardsServiceClient.boardsConfig = ({ selectedBoard: item, selectedPort: item.port }))
|
||||
};
|
||||
} else if (ConfigOption.is(item)) {
|
||||
const selected = item.values.find(({ selected }) => selected);
|
||||
const value = selected ? selected.label : 'Not set';
|
||||
const label = `${item.label}: ${value}`;
|
||||
options = {
|
||||
label,
|
||||
// Intended to match the value part of a board setting.
|
||||
// NOTE: this does not work, as `fuzzyMatchLabel: true` is set. Manual highlighting is ignored, apparently.
|
||||
labelHighlights: [
|
||||
{
|
||||
start: label.length - value.length,
|
||||
end: label.length
|
||||
}
|
||||
],
|
||||
run: (mode) => {
|
||||
if (mode === QuickOpenMode.OPEN) {
|
||||
this.setConfig(item);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
if (!selected) {
|
||||
options.description = 'Not set';
|
||||
};
|
||||
} else {
|
||||
options = {
|
||||
label: `${item.name}`,
|
||||
description: `${item.missing ? '' : `[installed with '${item.packageName}']`}`,
|
||||
run: (mode) => {
|
||||
if (mode === QuickOpenMode.OPEN) {
|
||||
this.selectBoard(item);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
if (group) {
|
||||
return new QuickOpenGroupItem<QuickOpenGroupItemOptions>({ ...options, ...group });
|
||||
} else {
|
||||
return new QuickOpenItem<QuickOpenItemOptions>(options);
|
||||
}
|
||||
}
|
||||
|
||||
protected toRun(run: (() => void)): ((mode: QuickOpenMode) => boolean) {
|
||||
return (mode) => {
|
||||
if (mode !== QuickOpenMode.OPEN) {
|
||||
return false;
|
||||
}
|
||||
run();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
protected async selectBoard(board: Board): Promise<void> {
|
||||
const allPorts = this.availableBoards.filter(AvailableBoard.hasPort).map(({ port }) => port).sort(Port.compare);
|
||||
const toItem = (port: Port) => new QuickOpenItem<QuickOpenItemOptions>({
|
||||
label: Port.toString(port, { useLabel: true }),
|
||||
run: this.toRun(() => {
|
||||
this.boardsServiceClient.boardsConfig = {
|
||||
selectedBoard: board,
|
||||
selectedPort: port
|
||||
};
|
||||
})
|
||||
});
|
||||
const options = {
|
||||
placeholder: `Select a port for '${board.name}'. Press 'Enter' to confirm or 'Escape' to cancel.`,
|
||||
fuzzyMatchLabel: true
|
||||
}
|
||||
this.quickOpenService.open({
|
||||
onType: (lookFor, acceptor) => {
|
||||
const fuzzyFilter = this.fuzzyFilter(lookFor);
|
||||
acceptor(allPorts.filter(({ address }) => fuzzyFilter(address)).map(toItem));
|
||||
}
|
||||
}, options);
|
||||
}
|
||||
|
||||
protected async setConfig(config: ConfigOption): Promise<void> {
|
||||
const toItem = (value: ConfigValue) => new QuickOpenItem<QuickOpenItemOptions>({
|
||||
label: value.label,
|
||||
iconClass: value.selected ? 'fa fa-check' : '',
|
||||
run: this.toRun(() => {
|
||||
if (!this.selectedBoard) {
|
||||
this.logger.warn(`Could not alter the boards settings. No board selected. ${JSON.stringify(config)}`);
|
||||
return;
|
||||
}
|
||||
if (!this.selectedBoard.fqbn) {
|
||||
this.logger.warn(`Could not alter the boards settings. The selected board does not have a FQBN. ${JSON.stringify(this.selectedBoard)}`);
|
||||
return;
|
||||
}
|
||||
const { fqbn } = this.selectedBoard;
|
||||
this.configStore.setSelected({
|
||||
fqbn,
|
||||
option: config.option,
|
||||
selectedValue: value.value
|
||||
});
|
||||
})
|
||||
});
|
||||
const options = {
|
||||
placeholder: `Configure '${config.label}'. Press 'Enter' to confirm or 'Escape' to cancel.`,
|
||||
fuzzyMatchLabel: true
|
||||
}
|
||||
this.quickOpenService.open({
|
||||
onType: (lookFor, acceptor) => {
|
||||
const fuzzyFilter = this.fuzzyFilter(lookFor);
|
||||
acceptor(config.values
|
||||
.filter(({ label }) => fuzzyFilter(label))
|
||||
.sort((left, right) => naturalCompare(left.label, right.label))
|
||||
.map(toItem));
|
||||
}
|
||||
}, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace BoardsQuickOpenService {
|
||||
export type Item = AvailableBoard & { port: Port } | Board.Detailed | ConfigOption;
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { isWindows, isOSX } from '@theia/core/lib/common/os';
|
||||
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import { naturalCompare } from './../utils';
|
||||
import { Searchable } from './searchable';
|
||||
import { Installable } from './installable';
|
||||
import { ArduinoComponent } from './arduino-component';
|
||||
const naturalCompare: (left: string, right: string) => number = require('string-natural-compare').caseInsensitive;
|
||||
|
||||
export interface AttachedBoardsChangeEvent {
|
||||
readonly oldState: Readonly<{ boards: Board[], ports: Port[] }>;
|
||||
@ -109,7 +109,7 @@ export namespace Port {
|
||||
if (!isBoardPort(left) && isBoardPort(right)) {
|
||||
return 1;
|
||||
}
|
||||
let result = left.protocol.toLocaleLowerCase().localeCompare(right.protocol.toLocaleLowerCase());
|
||||
let result = naturalCompare(left.protocol.toLocaleLowerCase(), right.protocol.toLocaleLowerCase());
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
@ -117,7 +117,7 @@ export namespace Port {
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
return (left.label || '').localeCompare(right.label || '');
|
||||
return naturalCompare(left.label || '', right.label || '');
|
||||
}
|
||||
|
||||
export function equals(left: Port | undefined, right: Port | undefined): boolean {
|
||||
@ -214,6 +214,11 @@ export interface ConfigOption {
|
||||
}
|
||||
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.
|
||||
@ -266,7 +271,7 @@ export namespace ConfigOption {
|
||||
}
|
||||
}
|
||||
|
||||
export const LABEL_COMPARATOR = (left: ConfigOption, right: ConfigOption) => left.label.toLocaleLowerCase().localeCompare(right.label.toLocaleLowerCase());
|
||||
export const LABEL_COMPARATOR = (left: ConfigOption, right: ConfigOption) => naturalCompare(left.label.toLocaleLowerCase(), right.label.toLocaleLowerCase());
|
||||
|
||||
}
|
||||
|
||||
@ -298,9 +303,9 @@ export namespace Board {
|
||||
}
|
||||
|
||||
export function compare(left: Board, right: Board): number {
|
||||
let result = left.name.localeCompare(right.name);
|
||||
let result = naturalCompare(left.name, right.name);
|
||||
if (result === 0) {
|
||||
result = (left.fqbn || '').localeCompare(right.fqbn || '');
|
||||
result = naturalCompare(left.fqbn || '', right.fqbn || '');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -314,13 +319,14 @@ export namespace Board {
|
||||
return `${board.name}${fqbn}`;
|
||||
}
|
||||
|
||||
export type Detailed = Board & Readonly<{ selected: boolean, missing: boolean, packageName: string, details?: string }>;
|
||||
export function decorateBoards(
|
||||
selectedBoard: Board | undefined,
|
||||
searchResults: Array<Board & { packageName: string }>): Array<Board & { selected: boolean, missing: boolean, packageName: string, details?: string }> {
|
||||
boards: Array<Board & { packageName: string }>): Array<Detailed> {
|
||||
// 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<string, number>();
|
||||
for (const { name } of searchResults) {
|
||||
for (const { name } of boards) {
|
||||
const counter = distinctBoardNames.get(name) || 0;
|
||||
distinctBoardNames.set(name, counter + 1);
|
||||
}
|
||||
@ -337,7 +343,7 @@ export namespace Board {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return searchResults.map(board => ({
|
||||
return boards.map(board => ({
|
||||
...board,
|
||||
details: (distinctBoardNames.get(board.name) || 0) > 1 ? ` - ${board.packageName}` : undefined,
|
||||
selected: selected(board),
|
||||
|
@ -1,4 +1,4 @@
|
||||
const naturalCompare: (left: string, right: string) => number = require('string-natural-compare').caseInsensitive;
|
||||
import { naturalCompare } from './../utils';
|
||||
import { ArduinoComponent } from './arduino-component';
|
||||
|
||||
export interface Installable<T extends ArduinoComponent> {
|
||||
|
1
arduino-ide-extension/src/common/utils.ts
Normal file
1
arduino-ide-extension/src/common/utils.ts
Normal file
@ -0,0 +1 @@
|
||||
export const naturalCompare: (left: string, right: string) => number = require('string-natural-compare').caseInsensitive;
|
Loading…
x
Reference in New Issue
Block a user