Skip to content

Commit 633346a

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
feat: new window inherits the custom board options
A new startup task ensures setting any custom board menu selection in a new sketch window. Closes #2271 Signed-off-by: Akos Kitta <[email protected]>
1 parent 0f83a48 commit 633346a

File tree

6 files changed

+700
-60
lines changed

6 files changed

+700
-60
lines changed

Diff for: arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
454454
// To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board.
455455
bind(BoardsDataStore).toSelf().inSingletonScope();
456456
bind(FrontendApplicationContribution).toService(BoardsDataStore);
457+
bind(CommandContribution).toService(BoardsDataStore);
458+
bind(StartupTaskProvider).toService(BoardsDataStore); // to inherit the boards config options, programmer, etc in a new window
459+
457460
// Logger for the Arduino daemon
458461
bind(ILogger)
459462
.toDynamicValue((ctx) => {

Diff for: arduino-ide-extension/src/browser/boards/boards-data-store.ts

+185-52
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
2+
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
23
import { StorageService } from '@theia/core/lib/browser/storage-service';
4+
import type {
5+
Command,
6+
CommandContribution,
7+
CommandRegistry,
8+
} from '@theia/core/lib/common/command';
39
import { DisposableCollection } from '@theia/core/lib/common/disposable';
410
import { Emitter, Event } from '@theia/core/lib/common/event';
511
import { ILogger } from '@theia/core/lib/common/logger';
6-
import { deepClone } from '@theia/core/lib/common/objects';
12+
import { deepClone, deepFreeze } from '@theia/core/lib/common/objects';
713
import { inject, injectable, named } from '@theia/core/shared/inversify';
814
import {
915
BoardDetails,
1016
BoardsService,
1117
ConfigOption,
1218
Programmer,
19+
isBoardIdentifierChangeEvent,
1320
} from '../../common/protocol';
1421
import { notEmpty } from '../../common/utils';
22+
import type {
23+
StartupTask,
24+
StartupTaskProvider,
25+
} from '../../electron-common/startup-task';
1526
import { NotificationCenter } from '../notification-center';
27+
import { BoardsServiceProvider } from './boards-service-provider';
1628

1729
@injectable()
18-
export class BoardsDataStore implements FrontendApplicationContribution {
30+
export class BoardsDataStore
31+
implements
32+
FrontendApplicationContribution,
33+
StartupTaskProvider,
34+
CommandContribution
35+
{
1936
@inject(ILogger)
2037
@named('store')
2138
private readonly logger: ILogger;
@@ -28,44 +45,110 @@ export class BoardsDataStore implements FrontendApplicationContribution {
2845
// In other words, store the data (such as the board configs) per sketch, not per IDE2 installation. https://github.com/arduino/arduino-ide/issues/2240
2946
@inject(StorageService)
3047
private readonly storageService: StorageService;
48+
@inject(BoardsServiceProvider)
49+
private readonly boardsServiceProvider: BoardsServiceProvider;
50+
@inject(FrontendApplicationStateService)
51+
private readonly appStateService: FrontendApplicationStateService;
3152

32-
private readonly onChangedEmitter = new Emitter<string[]>();
33-
private readonly toDispose = new DisposableCollection(this.onChangedEmitter);
53+
private readonly onDidChangeEmitter =
54+
new Emitter<BoardsDataStoreChangeEvent>();
55+
private readonly toDispose = new DisposableCollection(
56+
this.onDidChangeEmitter
57+
);
58+
private _selectedBoardData: BoardsDataStoreChange | undefined;
3459

3560
onStart(): void {
36-
this.toDispose.push(
61+
this.toDispose.pushAll([
62+
this.boardsServiceProvider.onBoardsConfigDidChange((event) => {
63+
if (isBoardIdentifierChangeEvent(event)) {
64+
this.updateSelectedBoardData(event.selectedBoard?.fqbn);
65+
}
66+
}),
3767
this.notificationCenter.onPlatformDidInstall(async ({ item }) => {
38-
const dataDidChangePerFqbn: string[] = [];
39-
for (const fqbn of item.boards
68+
const boardsWithFqbn = item.boards
4069
.map(({ fqbn }) => fqbn)
41-
.filter(notEmpty)
42-
.filter((fqbn) => !!fqbn)) {
70+
.filter(notEmpty);
71+
const changes: BoardsDataStoreChange[] = [];
72+
for (const fqbn of boardsWithFqbn) {
4373
const key = this.getStorageKey(fqbn);
44-
let data = await this.storageService.getData<ConfigOption[]>(key);
45-
if (!data || !data.length) {
46-
const details = await this.getBoardDetailsSafe(fqbn);
47-
if (details) {
48-
data = details.configOptions;
49-
if (data.length) {
50-
await this.storageService.setData(key, data);
51-
dataDidChangePerFqbn.push(fqbn);
52-
}
53-
}
74+
const storedData =
75+
await this.storageService.getData<BoardsDataStore.Data>(key);
76+
if (!storedData) {
77+
// if not previously value is available for the board, do not update the cache
78+
continue;
79+
}
80+
const details = await this.loadBoardDetails(fqbn);
81+
if (details) {
82+
const data = createDataStoreEntry(details);
83+
await this.storageService.setData(key, data);
84+
changes.push({ fqbn, data });
5485
}
5586
}
56-
if (dataDidChangePerFqbn.length) {
57-
this.fireChanged(...dataDidChangePerFqbn);
87+
if (changes.length) {
88+
this.fireChanged(...changes);
5889
}
59-
})
90+
}),
91+
]);
92+
93+
Promise.all([
94+
this.boardsServiceProvider.ready,
95+
this.appStateService.reachedState('ready'),
96+
]).then(() =>
97+
this.updateSelectedBoardData(
98+
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn
99+
)
60100
);
61101
}
62102

103+
private async getSelectedBoardData(
104+
fqbn: string | undefined
105+
): Promise<BoardsDataStoreChange | undefined> {
106+
if (!fqbn) {
107+
return undefined;
108+
} else {
109+
const data = await this.getData(fqbn);
110+
if (data === BoardsDataStore.Data.EMPTY) {
111+
return undefined;
112+
}
113+
return { fqbn, data };
114+
}
115+
}
116+
117+
private async updateSelectedBoardData(
118+
fqbn: string | undefined
119+
): Promise<void> {
120+
this._selectedBoardData = await this.getSelectedBoardData(fqbn);
121+
}
122+
63123
onStop(): void {
64124
this.toDispose.dispose();
65125
}
66126

67-
get onChanged(): Event<string[]> {
68-
return this.onChangedEmitter.event;
127+
registerCommands(registry: CommandRegistry): void {
128+
registry.registerCommand(USE_INHERITED_DATA, {
129+
execute: async (arg: unknown) => {
130+
if (isBoardsDataStoreChange(arg)) {
131+
await this.setData(arg);
132+
this.fireChanged(arg);
133+
}
134+
},
135+
});
136+
}
137+
138+
tasks(): StartupTask[] {
139+
if (!this._selectedBoardData) {
140+
return [];
141+
}
142+
return [
143+
{
144+
command: USE_INHERITED_DATA.id,
145+
args: [this._selectedBoardData],
146+
},
147+
];
148+
}
149+
150+
get onDidChange(): Event<BoardsDataStoreChangeEvent> {
151+
return this.onDidChangeEmitter.event;
69152
}
70153

71154
async appendConfigToFqbn(
@@ -84,22 +167,19 @@ export class BoardsDataStore implements FrontendApplicationContribution {
84167
}
85168

86169
const key = this.getStorageKey(fqbn);
87-
let data = await this.storageService.getData<
170+
const storedData = await this.storageService.getData<
88171
BoardsDataStore.Data | undefined
89172
>(key, undefined);
90-
if (BoardsDataStore.Data.is(data)) {
91-
return data;
173+
if (BoardsDataStore.Data.is(storedData)) {
174+
return storedData;
92175
}
93176

94177
const boardDetails = await this.getBoardDetailsSafe(fqbn);
95178
if (!boardDetails) {
96179
return BoardsDataStore.Data.EMPTY;
97180
}
98181

99-
data = {
100-
configOptions: boardDetails.configOptions,
101-
programmers: boardDetails.programmers,
102-
};
182+
const data = createDataStoreEntry(boardDetails);
103183
await this.storageService.setData(key, data);
104184
return data;
105185
}
@@ -111,17 +191,15 @@ export class BoardsDataStore implements FrontendApplicationContribution {
111191
fqbn: string;
112192
selectedProgrammer: Programmer;
113193
}): Promise<boolean> {
114-
const data = deepClone(await this.getData(fqbn));
115-
const { programmers } = data;
194+
const storedData = deepClone(await this.getData(fqbn));
195+
const { programmers } = storedData;
116196
if (!programmers.find((p) => Programmer.equals(selectedProgrammer, p))) {
117197
return false;
118198
}
119199

120-
await this.setData({
121-
fqbn,
122-
data: { ...data, selectedProgrammer },
123-
});
124-
this.fireChanged(fqbn);
200+
const data = { ...storedData, selectedProgrammer };
201+
await this.setData({ fqbn, data });
202+
this.fireChanged({ fqbn, data });
125203
return true;
126204
}
127205

@@ -153,17 +231,12 @@ export class BoardsDataStore implements FrontendApplicationContribution {
153231
return false;
154232
}
155233
await this.setData({ fqbn, data });
156-
this.fireChanged(fqbn);
234+
this.fireChanged({ fqbn, data });
157235
return true;
158236
}
159237

160-
protected async setData({
161-
fqbn,
162-
data,
163-
}: {
164-
fqbn: string;
165-
data: BoardsDataStore.Data;
166-
}): Promise<void> {
238+
protected async setData(change: BoardsDataStoreChange): Promise<void> {
239+
const { fqbn, data } = change;
167240
const key = this.getStorageKey(fqbn);
168241
return this.storageService.setData(key, data);
169242
}
@@ -176,7 +249,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
176249
fqbn: string
177250
): Promise<BoardDetails | undefined> {
178251
try {
179-
const details = this.boardsService.getBoardDetails({ fqbn });
252+
const details = await this.boardsService.getBoardDetails({ fqbn });
180253
return details;
181254
} catch (err) {
182255
if (
@@ -197,8 +270,8 @@ export class BoardsDataStore implements FrontendApplicationContribution {
197270
}
198271
}
199272

200-
protected fireChanged(...fqbn: string[]): void {
201-
this.onChangedEmitter.fire(fqbn);
273+
protected fireChanged(...changes: BoardsDataStoreChange[]): void {
274+
this.onDidChangeEmitter.fire({ changes });
202275
}
203276
}
204277

@@ -209,11 +282,13 @@ export namespace BoardsDataStore {
209282
readonly selectedProgrammer?: Programmer;
210283
}
211284
export namespace Data {
212-
export const EMPTY: Data = {
285+
export const EMPTY: Data = deepFreeze({
213286
configOptions: [],
214287
programmers: [],
215-
};
216-
export function is(arg: any): arg is Data {
288+
defaultProgrammerId: undefined,
289+
});
290+
291+
export function is(arg: unknown): arg is Data {
217292
return (
218293
!!arg &&
219294
'configOptions' in arg &&
@@ -224,3 +299,61 @@ export namespace BoardsDataStore {
224299
}
225300
}
226301
}
302+
303+
export function isEmptyData(data: BoardsDataStore.Data): boolean {
304+
return (
305+
Boolean(!data.configOptions.length) &&
306+
Boolean(!data.programmers.length) &&
307+
Boolean(!data.selectedProgrammer)
308+
);
309+
}
310+
311+
export function findDefaultProgrammer(
312+
programmers: readonly Programmer[],
313+
defaultProgrammerId: string | undefined | BoardsDataStore.Data
314+
): Programmer | undefined {
315+
if (!defaultProgrammerId) {
316+
return undefined;
317+
}
318+
const id =
319+
typeof defaultProgrammerId === 'string'
320+
? defaultProgrammerId
321+
: defaultProgrammerId.defaultProgrammerId;
322+
return programmers.find((p) => p.id === id);
323+
}
324+
function createDataStoreEntry(details: BoardDetails): BoardsDataStore.Data {
325+
const configOptions = details.configOptions.slice();
326+
const programmers = details.programmers.slice();
327+
const selectedProgrammer = findDefaultProgrammer(
328+
programmers,
329+
details.defaultProgrammerId
330+
);
331+
return {
332+
configOptions,
333+
programmers,
334+
defaultProgrammerId: details.defaultProgrammerId,
335+
selectedProgrammer,
336+
};
337+
}
338+
339+
export interface BoardsDataStoreChange {
340+
readonly fqbn: string;
341+
readonly data: BoardsDataStore.Data;
342+
}
343+
344+
function isBoardsDataStoreChange(arg: unknown): arg is BoardsDataStoreChange {
345+
return (
346+
typeof arg === 'object' &&
347+
arg !== null &&
348+
typeof (<BoardsDataStoreChange>arg).fqbn === 'string' &&
349+
BoardsDataStore.Data.is((<BoardsDataStoreChange>arg).data)
350+
);
351+
}
352+
353+
export interface BoardsDataStoreChangeEvent {
354+
readonly changes: readonly BoardsDataStoreChange[];
355+
}
356+
357+
const USE_INHERITED_DATA: Command = {
358+
id: 'arduino-use-inherited-boards-data',
359+
};

Diff for: arduino-ide-extension/src/browser/contributions/boards-data-menu-updater.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class BoardsDataMenuUpdater extends Contribution {
3535
private readonly toDisposeOnBoardChange = new DisposableCollection();
3636

3737
override onStart(): void {
38-
this.boardsDataStore.onChanged(() =>
38+
this.boardsDataStore.onDidChange(() =>
3939
this.updateMenuActions(
4040
this.boardsServiceProvider.boardsConfig.selectedBoard
4141
)

Diff for: arduino-ide-extension/src/browser/contributions/ino-language.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,21 @@ export class InoLanguage extends SketchContribution {
9090
this.notificationCenter.onPlatformDidInstall(() => forceRestart()),
9191
this.notificationCenter.onPlatformDidUninstall(() => forceRestart()),
9292
this.notificationCenter.onDidReinitialize(() => forceRestart()),
93-
this.boardDataStore.onChanged((dataChangePerFqbn) => {
93+
this.boardDataStore.onDidChange((event) => {
9494
if (this.languageServerFqbn) {
9595
const sanitizedFqbn = sanitizeFqbn(this.languageServerFqbn);
9696
if (!sanitizeFqbn) {
9797
throw new Error(
9898
`Failed to sanitize the FQBN of the running language server. FQBN with the board settings was: ${this.languageServerFqbn}`
9999
);
100100
}
101-
const matchingFqbn = dataChangePerFqbn.find(
102-
(fqbn) => sanitizedFqbn === fqbn
101+
const matchingChange = event.changes.find(
102+
(change) => change.fqbn === sanitizedFqbn
103103
);
104104
const { boardsConfig } = this.boardsServiceProvider;
105105
if (
106-
matchingFqbn &&
107-
boardsConfig.selectedBoard?.fqbn === matchingFqbn
106+
matchingChange &&
107+
boardsConfig.selectedBoard?.fqbn === matchingChange.fqbn
108108
) {
109109
start(boardsConfig.selectedBoard);
110110
}

0 commit comments

Comments
 (0)