Skip to content

Commit 258f174

Browse files
committed
feat: set the Arduino context as workspaceState
Set the VS Code `workspaceState` on fqbn, port, and sketch path changes. VS Code extensions can read this cached state. Signed-off-by: dankeboy36 <[email protected]>
1 parent e47fb2e commit 258f174

20 files changed

+781
-38
lines changed

arduino-ide-extension/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
"temp-dir": "^2.0.0",
106106
"tree-kill": "^1.2.1",
107107
"util": "^0.12.5",
108+
"vscode-arduino-api": "^0.1.1-alpha.4",
108109
"which": "^1.3.1"
109110
},
110111
"devDependencies": {
@@ -173,7 +174,7 @@
173174
],
174175
"arduino": {
175176
"cli": {
176-
"version": "0.32.2"
177+
"version": "0.33.0-rc1"
177178
},
178179
"fwuploader": {
179180
"version": "2.2.2"

arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ import { FileResourceResolver as TheiaFileResourceResolver } from '@theia/filesy
354354
import { StylingParticipant } from '@theia/core/lib/browser/styling-service';
355355
import { MonacoEditorMenuContribution } from './theia/monaco/monaco-menu';
356356
import { MonacoEditorMenuContribution as TheiaMonacoEditorMenuContribution } from '@theia/monaco/lib/browser/monaco-menu';
357+
import { UpdateArduinoContext } from './contributions/update-arduino-context';
357358

358359
// Hack to fix copy/cut/paste issue after electron version update in Theia.
359360
// https://github.com/eclipse-theia/theia/issues/12487
@@ -747,6 +748,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
747748
Contribution.configure(bind, Account);
748749
Contribution.configure(bind, CloudSketchbookContribution);
749750
Contribution.configure(bind, CreateCloudCopy);
751+
Contribution.configure(bind, UpdateArduinoContext);
750752

751753
bindContributionProvider(bind, StartupTaskProvider);
752754
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { DisposableCollection } from '@theia/core/lib/common/disposable';
2+
import URI from '@theia/core/lib/common/uri';
3+
import { inject, injectable } from '@theia/core/shared/inversify';
4+
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
5+
import type { ArduinoState } from 'vscode-arduino-api';
6+
import {
7+
BoardsService,
8+
CompileSummary,
9+
Port,
10+
isCompileSummary,
11+
} from '../../common/protocol';
12+
import {
13+
toApiBoardDetails,
14+
toApiCompileSummary,
15+
toApiPort,
16+
} from '../../common/protocol/arduino-context-mapper';
17+
import type { BoardsConfig } from '../boards/boards-config';
18+
import { BoardsDataStore } from '../boards/boards-data-store';
19+
import { BoardsServiceProvider } from '../boards/boards-service-provider';
20+
import { CurrentSketch } from '../sketches-service-client-impl';
21+
import { SketchContribution } from './contribution';
22+
23+
interface UpdateWorkspaceStateParams<T extends ArduinoState> {
24+
readonly key: keyof T;
25+
readonly value: T[keyof T];
26+
}
27+
28+
/**
29+
* Contribution for setting the VS Code [`workspaceState`](https://code.visualstudio.com/api/references/vscode-api#workspace) on Arduino IDE context changes, such as FQBN, selected port, and sketch path changes via commands.
30+
* See [`vscode-arduino-api`](https://www.npmjs.com/package/vscode-arduino-api) for more details.
31+
*/
32+
@injectable()
33+
export class UpdateArduinoContext extends SketchContribution {
34+
@inject(BoardsService)
35+
private readonly boardsService: BoardsService;
36+
@inject(BoardsServiceProvider)
37+
private readonly boardsServiceProvider: BoardsServiceProvider;
38+
@inject(BoardsDataStore)
39+
private readonly boardsDataStore: BoardsDataStore;
40+
@inject(HostedPluginSupport)
41+
private readonly hostedPluginSupport: HostedPluginSupport;
42+
43+
private readonly toDispose = new DisposableCollection();
44+
45+
override onStart(): void {
46+
this.toDispose.pushAll([
47+
this.boardsServiceProvider.onBoardsConfigChanged((config) =>
48+
this.updateBoardsConfig(config)
49+
),
50+
this.sketchServiceClient.onCurrentSketchDidChange((sketch) =>
51+
this.updateSketchPath(sketch)
52+
),
53+
this.configService.onDidChangeDataDirUri((dataDirUri) =>
54+
this.updateDataDirPath(dataDirUri)
55+
),
56+
this.configService.onDidChangeSketchDirUri((userDirUri) =>
57+
this.updateUserDirPath(userDirUri)
58+
),
59+
this.commandService.onDidExecuteCommand(({ commandId, args }) => {
60+
if (
61+
commandId === 'arduino.languageserver.notifyBuildDidComplete' &&
62+
isCompileSummary(args[0])
63+
) {
64+
this.updateCompileSummary(args[0]);
65+
}
66+
}),
67+
this.boardsDataStore.onChanged((fqbn) => {
68+
const selectedFqbn =
69+
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn;
70+
if (selectedFqbn && fqbn.includes(selectedFqbn)) {
71+
this.updateBoardDetails(selectedFqbn);
72+
}
73+
}),
74+
]);
75+
}
76+
77+
override onReady(): void {
78+
this.boardsServiceProvider.reconciled.then(() => {
79+
this.updateBoardsConfig(this.boardsServiceProvider.boardsConfig);
80+
});
81+
this.updateSketchPath(this.sketchServiceClient.tryGetCurrentSketch());
82+
this.updateUserDirPath(this.configService.tryGetSketchDirUri());
83+
this.updateDataDirPath(this.configService.tryGetDataDirUri());
84+
}
85+
86+
onStop(): void {
87+
this.toDispose.dispose();
88+
}
89+
90+
private async updateSketchPath(
91+
sketch: CurrentSketch | undefined
92+
): Promise<void> {
93+
const sketchPath = CurrentSketch.isValid(sketch)
94+
? new URI(sketch.uri).path.fsPath()
95+
: undefined;
96+
return this.updateWorkspaceState({ key: 'sketchPath', value: sketchPath });
97+
}
98+
99+
private async updateCompileSummary(
100+
compileSummary: CompileSummary
101+
): Promise<void> {
102+
const apiCompileSummary = toApiCompileSummary(compileSummary);
103+
return this.updateWorkspaceState({
104+
key: 'compileSummary',
105+
value: apiCompileSummary,
106+
});
107+
}
108+
109+
private async updateBoardsConfig(
110+
boardsConfig: BoardsConfig.Config
111+
): Promise<void> {
112+
const fqbn = boardsConfig.selectedBoard?.fqbn;
113+
const port = boardsConfig.selectedPort;
114+
await this.updateFqbn(fqbn);
115+
await this.updateBoardDetails(fqbn);
116+
await this.updatePort(port);
117+
}
118+
119+
private async updateFqbn(fqbn: string | undefined): Promise<void> {
120+
await this.updateWorkspaceState({ key: 'fqbn', value: fqbn });
121+
}
122+
123+
private async updateBoardDetails(fqbn: string | undefined): Promise<void> {
124+
const unset = () =>
125+
this.updateWorkspaceState({ key: 'boardDetails', value: undefined });
126+
if (!fqbn) {
127+
return unset();
128+
}
129+
const [details, persistedData] = await Promise.all([
130+
this.boardsService.getBoardDetails({ fqbn }),
131+
this.boardsDataStore.getData(fqbn),
132+
]);
133+
if (!details) {
134+
return unset();
135+
}
136+
const apiBoardDetails = toApiBoardDetails({
137+
...details,
138+
configOptions:
139+
BoardsDataStore.Data.EMPTY === persistedData
140+
? details.configOptions
141+
: persistedData.configOptions.slice(),
142+
});
143+
return this.updateWorkspaceState({
144+
key: 'boardDetails',
145+
value: apiBoardDetails,
146+
});
147+
}
148+
149+
private async updatePort(port: Port | undefined): Promise<void> {
150+
const apiPort = port && toApiPort(port);
151+
return this.updateWorkspaceState({ key: 'port', value: apiPort });
152+
}
153+
154+
private async updateUserDirPath(userDirUri: URI | undefined): Promise<void> {
155+
const userDirPath = userDirUri?.path.fsPath();
156+
return this.updateWorkspaceState({
157+
key: 'userDirPath',
158+
value: userDirPath,
159+
});
160+
}
161+
162+
private async updateDataDirPath(dataDirUri: URI | undefined): Promise<void> {
163+
const dataDirPath = dataDirUri?.path.fsPath();
164+
return this.updateWorkspaceState({
165+
key: 'dataDirPath',
166+
value: dataDirPath,
167+
});
168+
}
169+
170+
private async updateWorkspaceState<T extends ArduinoState>(
171+
params: UpdateWorkspaceStateParams<T>
172+
): Promise<void> {
173+
await this.hostedPluginSupport.didStart;
174+
return this.commandService.executeCommand(
175+
'vscodeArduinoAPI.updateState',
176+
params
177+
);
178+
}
179+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import type {
2+
Port as APIPort,
3+
BoardDetails as ApiBoardDetails,
4+
BuildProperties as ApiBuildProperties,
5+
CompileSummary as ApiCompileSummary,
6+
ConfigOption as ApiConfigOption,
7+
ConfigValue as ApiConfigValue,
8+
Tool as ApiTool,
9+
} from 'vscode-arduino-api';
10+
import type {
11+
BoardDetails,
12+
CompileSummary,
13+
ConfigOption,
14+
ConfigValue,
15+
Port,
16+
Tool,
17+
} from '../protocol';
18+
19+
export function toApiCompileSummary(
20+
compileSummary: CompileSummary
21+
): ApiCompileSummary {
22+
const {
23+
buildPath,
24+
buildProperties,
25+
boardPlatform,
26+
buildPlatform,
27+
executableSectionsSize,
28+
usedLibraries,
29+
} = compileSummary;
30+
return {
31+
buildPath,
32+
buildProperties: toApiBuildProperties(buildProperties),
33+
executableSectionsSize: executableSectionsSize,
34+
boardPlatform,
35+
buildPlatform,
36+
usedLibraries,
37+
};
38+
}
39+
40+
export function toApiPort(port: Port): APIPort | undefined {
41+
const {
42+
hardwareId = '',
43+
properties = {},
44+
address,
45+
protocol,
46+
protocolLabel,
47+
addressLabel: label,
48+
} = port;
49+
return {
50+
label,
51+
address,
52+
hardwareId,
53+
properties,
54+
protocol,
55+
protocolLabel,
56+
};
57+
}
58+
59+
export function toApiBoardDetails(boardDetails: BoardDetails): ApiBoardDetails {
60+
const { fqbn, programmers, configOptions, requiredTools } = boardDetails;
61+
return {
62+
buildProperties: toApiBuildProperties(boardDetails.buildProperties),
63+
configOptions: configOptions.map(toApiConfigOption),
64+
fqbn,
65+
programmers,
66+
toolsDependencies: requiredTools.map(toApiTool),
67+
};
68+
}
69+
70+
function toApiConfigOption(configOption: ConfigOption): ApiConfigOption {
71+
const { label, values, option } = configOption;
72+
return {
73+
optionLabel: label,
74+
option,
75+
values: values.map(toApiConfigValue),
76+
};
77+
}
78+
79+
function toApiConfigValue(configValue: ConfigValue): ApiConfigValue {
80+
const { label, selected, value } = configValue;
81+
return {
82+
selected,
83+
value,
84+
valueLabel: label,
85+
};
86+
}
87+
88+
function toApiTool(toolDependency: Tool): ApiTool {
89+
const { name, packager, version } = toolDependency;
90+
return {
91+
name,
92+
packager,
93+
version,
94+
};
95+
}
96+
97+
const propertySep = '=';
98+
99+
function parseProperty(
100+
property: string
101+
): [key: string, value: string] | undefined {
102+
const segments = property.split(propertySep);
103+
if (segments.length < 2) {
104+
console.warn(`Could not parse build property: ${property}.`);
105+
return undefined;
106+
}
107+
108+
const [key, ...rest] = segments;
109+
if (!key) {
110+
console.warn(`Could not determine property key from raw: ${property}.`);
111+
return undefined;
112+
}
113+
const value = rest.join(propertySep);
114+
return [key, value];
115+
}
116+
117+
export function toApiBuildProperties(properties: string[]): ApiBuildProperties {
118+
return properties.reduce((acc, curr) => {
119+
const entry = parseProperty(curr);
120+
if (entry) {
121+
const [key, value] = entry;
122+
acc[key] = value;
123+
}
124+
return acc;
125+
}, <Record<string, string>>{});
126+
}

arduino-ide-extension/src/common/protocol/boards-service.ts

+1
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ export interface BoardDetails {
441441
readonly debuggingSupported: boolean;
442442
readonly VID: string;
443443
readonly PID: string;
444+
readonly buildProperties: string[];
444445
}
445446

446447
export interface Tool {

0 commit comments

Comments
 (0)