Skip to content

Commit 74bfdc4

Browse files
authored
Rework listing of discovered ports (#614)
* 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
1 parent 20f7712 commit 74bfdc4

30 files changed

+3031
-413
lines changed

arduino-ide-extension/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@
149149
],
150150
"arduino": {
151151
"cli": {
152-
"version": "0.19.1"
152+
"version": "0.20.0"
153153
},
154154
"fwuploader": {
155155
"version": "2.0.0"

arduino-ide-extension/src/browser/boards/boards-config.tsx

+64-5
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ import {
1010
BoardWithPackage,
1111
} from '../../common/protocol/boards-service';
1212
import { NotificationCenter } from '../notification-center';
13-
import { BoardsServiceProvider } from './boards-service-provider';
13+
import {
14+
AvailableBoard,
15+
BoardsServiceProvider,
16+
} from './boards-service-provider';
1417
import { nls } from '@theia/core/lib/browser/nls';
18+
import { naturalCompare } from '../../common/utils';
1519

1620
export namespace BoardsConfig {
1721
export interface Config {
@@ -184,11 +188,50 @@ export class BoardsConfig extends React.Component<
184188
.filter(notEmpty);
185189
}
186190

191+
protected get availableBoards(): AvailableBoard[] {
192+
return this.props.boardsServiceProvider.availableBoards;
193+
}
194+
187195
protected queryPorts = async (
188196
availablePorts: MaybePromise<Port[]> = this.availablePorts
189197
) => {
190-
const ports = await availablePorts;
191-
return { knownPorts: ports.sort(Port.compare) };
198+
// Available ports must be sorted in this order:
199+
// 1. Serial with recognized boards
200+
// 2. Serial with guessed boards
201+
// 3. Serial with incomplete boards
202+
// 4. Network with recognized boards
203+
// 5. Other protocols with recognized boards
204+
const ports = (await availablePorts).sort((left: Port, right: Port) => {
205+
if (left.protocol === 'serial' && right.protocol !== 'serial') {
206+
return -1;
207+
} else if (left.protocol !== 'serial' && right.protocol === 'serial') {
208+
return 1;
209+
} else if (left.protocol === 'network' && right.protocol !== 'network') {
210+
return -1;
211+
} else if (left.protocol !== 'network' && right.protocol === 'network') {
212+
return 1;
213+
} else if (left.protocol === right.protocol) {
214+
// We show ports, including those that have guessed
215+
// or unrecognized boards, so we must sort those too.
216+
const leftBoard = this.availableBoards.find((board) =>
217+
Port.sameAs(board.port, left)
218+
);
219+
const rightBoard = this.availableBoards.find((board) =>
220+
Port.sameAs(board.port, right)
221+
);
222+
if (leftBoard && !rightBoard) {
223+
return -1;
224+
} else if (!leftBoard && rightBoard) {
225+
return 1;
226+
} else if (leftBoard?.state! < rightBoard?.state!) {
227+
return -1;
228+
} else if (leftBoard?.state! > rightBoard?.state!) {
229+
return 1;
230+
}
231+
}
232+
return naturalCompare(left.address, right.address);
233+
});
234+
return { knownPorts: ports };
192235
};
193236

194237
protected toggleFilterPorts = () => {
@@ -281,8 +324,24 @@ export class BoardsConfig extends React.Component<
281324
}
282325

283326
protected renderPorts(): React.ReactNode {
284-
const filter = this.state.showAllPorts ? () => true : Port.isBoardPort;
285-
const ports = this.state.knownPorts.filter(filter);
327+
let ports = [] as Port[];
328+
if (this.state.showAllPorts) {
329+
ports = this.state.knownPorts;
330+
} else {
331+
ports = this.state.knownPorts.filter((port) => {
332+
if (port.protocol === 'serial') {
333+
return true;
334+
}
335+
// All other ports with different protocol are
336+
// only shown if there is a recognized board
337+
// connected
338+
for (const board of this.availableBoards) {
339+
if (board.port?.address === port.address) {
340+
return true;
341+
}
342+
}
343+
});
344+
}
286345
return !ports.length ? (
287346
<div className="loading noselect">No ports discovered</div>
288347
) : (

arduino-ide-extension/src/browser/boards/boards-service-provider.ts

+90-91
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
4242
protected readonly onAvailableBoardsChangedEmitter = new Emitter<
4343
AvailableBoard[]
4444
>();
45+
protected readonly onAvailablePortsChangedEmitter = new Emitter<Port[]>();
4546

4647
/**
4748
* Used for the auto-reconnecting. Sometimes, the attached board gets disconnected after uploading something to it.
@@ -67,8 +68,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
6768
* This event is also emitted when the board package for the currently selected board was uninstalled.
6869
*/
6970
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
70-
readonly onAvailableBoardsChanged =
71-
this.onAvailableBoardsChangedEmitter.event;
71+
readonly onAvailableBoardsChanged = this.onAvailableBoardsChangedEmitter.event;
72+
readonly onAvailablePortsChanged = this.onAvailablePortsChangedEmitter.event;
7273

7374
onStart(): void {
7475
this.notificationCenter.onAttachedBoardsChanged(
@@ -88,6 +89,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
8889
]).then(([attachedBoards, availablePorts]) => {
8990
this._attachedBoards = attachedBoards;
9091
this._availablePorts = availablePorts;
92+
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
9193
this.reconcileAvailableBoards().then(() => this.tryReconnect());
9294
});
9395
}
@@ -102,6 +104,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
102104
}
103105
this._attachedBoards = event.newState.boards;
104106
this._availablePorts = event.newState.ports;
107+
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
105108
this.reconcileAvailableBoards().then(() => this.tryReconnect());
106109
}
107110

@@ -180,8 +183,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
180183
const selectedAvailableBoard = AvailableBoard.is(selectedBoard)
181184
? selectedBoard
182185
: this._availableBoards.find((availableBoard) =>
183-
Board.sameAs(availableBoard, selectedBoard)
184-
);
186+
Board.sameAs(availableBoard, selectedBoard)
187+
);
185188
if (
186189
selectedAvailableBoard &&
187190
selectedAvailableBoard.selected &&
@@ -358,14 +361,14 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
358361
const timeoutTask =
359362
!!timeout && timeout > 0
360363
? new Promise<void>((_, reject) =>
361-
setTimeout(
362-
() => reject(new Error(`Timeout after ${timeout} ms.`)),
363-
timeout
364-
)
364+
setTimeout(
365+
() => reject(new Error(`Timeout after ${timeout} ms.`)),
366+
timeout
365367
)
368+
)
366369
: new Promise<void>(() => {
367-
/* never */
368-
});
370+
/* never */
371+
});
369372
const waitUntilTask = new Promise<void>((resolve) => {
370373
let candidate = find(what, this.availableBoards);
371374
if (candidate) {
@@ -384,7 +387,6 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
384387
}
385388

386389
protected async reconcileAvailableBoards(): Promise<void> {
387-
const attachedBoards = this._attachedBoards;
388390
const availablePorts = this._availablePorts;
389391
// Unset the port on the user's config, if it is not available anymore.
390392
if (
@@ -402,51 +404,64 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
402404
const boardsConfig = this.boardsConfig;
403405
const currentAvailableBoards = this._availableBoards;
404406
const availableBoards: AvailableBoard[] = [];
405-
const availableBoardPorts = availablePorts.filter(Port.isBoardPort);
406-
const attachedSerialBoards = attachedBoards.filter(({ port }) => !!port);
407+
const attachedBoards = this._attachedBoards.filter(({ port }) => !!port);
408+
const availableBoardPorts = availablePorts.filter((port) => {
409+
if (port.protocol === "serial") {
410+
// We always show all serial ports, even if there
411+
// is no recognized board connected to it
412+
return true;
413+
}
414+
415+
// All other ports with different protocol are
416+
// only shown if there is a recognized board
417+
// connected
418+
for (const board of attachedBoards) {
419+
if (board.port?.address === port.address) {
420+
return true;
421+
}
422+
}
423+
return false;
424+
});
407425

408426
for (const boardPort of availableBoardPorts) {
409-
let state = AvailableBoard.State.incomplete; // Initial pessimism.
410-
let board = attachedSerialBoards.find(({ port }) =>
411-
Port.sameAs(boardPort, port)
412-
);
427+
let board = attachedBoards.find(({ port }) => Port.sameAs(boardPort, port));
428+
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(boardPort);
429+
430+
let availableBoard = {} as AvailableBoard;
413431
if (board) {
414-
state = AvailableBoard.State.recognized;
415-
} else {
432+
availableBoard = {
433+
...board,
434+
state: AvailableBoard.State.recognized,
435+
selected: BoardsConfig.Config.sameAs(boardsConfig, board),
436+
port: boardPort,
437+
};
438+
} else if (lastSelectedBoard) {
416439
// If the selected board is not recognized because it is a 3rd party board: https://github.com/arduino/arduino-cli/issues/623
417440
// 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
418-
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
419-
boardPort
420-
);
421-
if (lastSelectedBoard) {
422-
board = {
423-
...lastSelectedBoard,
424-
port: boardPort,
425-
};
426-
state = AvailableBoard.State.guessed;
427-
}
428-
}
429-
if (!board) {
430-
availableBoards.push({
431-
name: nls.localize('arduino/common/unknown', 'Unknown'),
441+
availableBoard = {
442+
...lastSelectedBoard,
443+
state: AvailableBoard.State.guessed,
444+
selected: BoardsConfig.Config.sameAs(boardsConfig, lastSelectedBoard),
432445
port: boardPort,
433-
state,
434-
});
446+
};
435447
} else {
436-
const selected = BoardsConfig.Config.sameAs(boardsConfig, board);
437-
availableBoards.push({
438-
...board,
439-
state,
440-
selected,
448+
availableBoard = {
449+
name: nls.localize('arduino/common/unknown', 'Unknown'),
441450
port: boardPort,
442-
});
451+
state: AvailableBoard.State.incomplete,
452+
};
443453
}
454+
availableBoards.push(availableBoard);
444455
}
445456

446-
if (
447-
boardsConfig.selectedBoard &&
448-
!availableBoards.some(({ selected }) => selected)
449-
) {
457+
if (boardsConfig.selectedBoard && !availableBoards.some(({ selected }) => selected)) {
458+
// If the selected board has the same port of an unknown board
459+
// that is already in availableBoards we might get a duplicate port.
460+
// So we remove the one already in the array and add the selected one.
461+
const found = availableBoards.findIndex(board => board.port?.address === boardsConfig.selectedPort?.address);
462+
if (found >= 0) {
463+
availableBoards.splice(found, 1);
464+
}
450465
availableBoards.push({
451466
...boardsConfig.selectedBoard,
452467
port: boardsConfig.selectedPort,
@@ -455,28 +470,20 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
455470
});
456471
}
457472

458-
const sortedAvailableBoards = availableBoards.sort(AvailableBoard.compare);
459-
let hasChanged =
460-
sortedAvailableBoards.length !== currentAvailableBoards.length;
461-
for (let i = 0; !hasChanged && i < sortedAvailableBoards.length; i++) {
462-
hasChanged =
463-
AvailableBoard.compare(
464-
sortedAvailableBoards[i],
465-
currentAvailableBoards[i]
466-
) !== 0;
473+
availableBoards.sort(AvailableBoard.compare);
474+
475+
let hasChanged = availableBoards.length !== currentAvailableBoards.length;
476+
for (let i = 0; !hasChanged && i < availableBoards.length; i++) {
477+
const [left, right] = [availableBoards[i], currentAvailableBoards[i]];
478+
hasChanged = !!AvailableBoard.compare(left, right) || left.selected !== right.selected;
467479
}
468480
if (hasChanged) {
469-
this._availableBoards = sortedAvailableBoards;
481+
this._availableBoards = availableBoards;
470482
this.onAvailableBoardsChangedEmitter.fire(this._availableBoards);
471483
}
472484
}
473485

474-
protected async getLastSelectedBoardOnPort(
475-
port: Port | string | undefined
476-
): Promise<Board | undefined> {
477-
if (!port) {
478-
return undefined;
479-
}
486+
protected async getLastSelectedBoardOnPort(port: Port): Promise<Board | undefined> {
480487
const key = this.getLastSelectedBoardOnPortKey(port);
481488
return this.getData<Board>(key);
482489
}
@@ -497,11 +504,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
497504
]);
498505
}
499506

500-
protected getLastSelectedBoardOnPortKey(port: Port | string): string {
501-
// TODO: we lose the port's `protocol` info (`serial`, `network`, etc.) here if the `port` is a `string`.
502-
return `last-selected-board-on-port:${
503-
typeof port === 'string' ? port : Port.toString(port)
504-
}`;
507+
protected getLastSelectedBoardOnPortKey(port: Port): string {
508+
return `last-selected-board-on-port:${Port.toString(port)}`;
505509
}
506510

507511
protected async loadState(): Promise<void> {
@@ -585,35 +589,30 @@ export namespace AvailableBoard {
585589
return !!board.port;
586590
}
587591

592+
// Available boards must be sorted in this order:
593+
// 1. Serial with recognized boards
594+
// 2. Serial with guessed boards
595+
// 3. Serial with incomplete boards
596+
// 4. Network with recognized boards
597+
// 5. Other protocols with recognized boards
588598
export const compare = (left: AvailableBoard, right: AvailableBoard) => {
589-
if (left.selected && !right.selected) {
599+
if (left.port?.protocol === "serial" && right.port?.protocol !== "serial") {
590600
return -1;
591-
}
592-
if (right.selected && !left.selected) {
601+
} else if (left.port?.protocol !== "serial" && right.port?.protocol === "serial") {
593602
return 1;
594-
}
595-
let result = naturalCompare(left.name, right.name);
596-
if (result !== 0) {
597-
return result;
598-
}
599-
if (left.fqbn && right.fqbn) {
600-
result = naturalCompare(left.fqbn, right.fqbn);
601-
if (result !== 0) {
602-
return result;
603-
}
604-
}
605-
if (left.port && right.port) {
606-
result = Port.compare(left.port, right.port);
607-
if (result !== 0) {
608-
return result;
609-
}
610-
}
611-
if (!!left.selected && !right.selected) {
603+
} else if (left.port?.protocol === "network" && right.port?.protocol !== "network") {
612604
return -1;
613-
}
614-
if (!!right.selected && !left.selected) {
605+
} else if (left.port?.protocol !== "network" && right.port?.protocol === "network") {
615606
return 1;
607+
} else if (left.port?.protocol === right.port?.protocol) {
608+
// We show all ports, including those that have guessed
609+
// or unrecognized boards, so we must sort those too.
610+
if (left.state < right.state) {
611+
return -1;
612+
} else if (left.state > right.state) {
613+
return 1;
614+
}
616615
}
617-
return left.state - right.state;
618-
};
616+
return naturalCompare(left.port?.address!, right.port?.address!);
617+
}
619618
}

0 commit comments

Comments
 (0)