Skip to content

Commit 5ec1915

Browse files
fix(plugin): decouple state update from the LS (#2643)
* fix(plugin): decouple state update from the LS To enhance the reliability of Arduino IDE extensions, the update process for `ArduinoState` has been modified to ensure independence from the language server's availability. This change addresses issues caused by `compileSummary` being `undefined` due to potential startup failures of the Arduino Language Server, as noted in dankeboy36/esp-exception-decoder#28 (comment). The `compile` command now resolves with a `CompileSummary` rather than `void`, facilitating a more reliable way for extensions to access necessary data. Furthermore, the command has been adjusted to allow resolution with `undefined` when the compiled data is partial. By transitioning to direct usage of the resolved compile value for state updates, the reliance on executed commands for extensions is eliminated. This update also moves the VSIX command execution to the frontend without altering existing IDE behavior. Closes #2642 Signed-off-by: dankeboy36 <[email protected]> * fix: install missing libx11-dev and libxkbfile-dev Signed-off-by: dankeboy36 <[email protected]> * fix: pick better GH step name Signed-off-by: dankeboy36 <[email protected]> * fix: install the required dependencies on Linux Signed-off-by: dankeboy36 <[email protected]> * fix(revert): do not manually install deps on Linux Signed-off-by: dankeboy36 <[email protected]> * chore: pin `ubuntu-22.04` for linux actions * fix: restore accidentally removed dispose on finally Signed-off-by: dankeboy36 <[email protected]> * fix(test): align mock naming 💄 Signed-off-by: dankeboy36 <[email protected]> * fix: let the ino contribution notify the LS + event emitter dispatches the new state. Signed-off-by: dankeboy36 <[email protected]> * fix(test): emit the new compiler summary state Signed-off-by: dankeboy36 <[email protected]> * chore(revert): unpin linux version, use latest revert of b11bde1 Signed-off-by: dankeboy36 <[email protected]> --------- Signed-off-by: dankeboy36 <[email protected]> Co-authored-by: Giacomo Cusinato <[email protected]>
1 parent 6d96e22 commit 5ec1915

File tree

8 files changed

+132
-111
lines changed

8 files changed

+132
-111
lines changed

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ import { OpenSketch } from './contributions/open-sketch';
131131
import { Close } from './contributions/close';
132132
import { SaveAsSketch } from './contributions/save-as-sketch';
133133
import { SaveSketch } from './contributions/save-sketch';
134-
import { VerifySketch } from './contributions/verify-sketch';
134+
import {
135+
CompileSummaryProvider,
136+
VerifySketch,
137+
} from './contributions/verify-sketch';
135138
import { UploadSketch } from './contributions/upload-sketch';
136139
import { CommonFrontendContribution } from './theia/core/common-frontend-contribution';
137140
import { EditContributions } from './contributions/edit-contributions';
@@ -788,6 +791,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
788791
Contribution.configure(bind, BoardsDataMenuUpdater);
789792
Contribution.configure(bind, AutoSelectProgrammer);
790793

794+
bind(CompileSummaryProvider).toService(VerifySketch);
795+
791796
bindContributionProvider(bind, StartupTaskProvider);
792797
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
793798

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

+39
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ArduinoDaemon,
99
BoardIdentifier,
1010
BoardsService,
11+
CompileSummary,
1112
ExecutableService,
1213
isBoardIdentifierChangeEvent,
1314
sanitizeFqbn,
@@ -23,6 +24,7 @@ import { HostedPluginEvents } from '../hosted/hosted-plugin-events';
2324
import { NotificationCenter } from '../notification-center';
2425
import { CurrentSketch } from '../sketches-service-client-impl';
2526
import { SketchContribution, URI } from './contribution';
27+
import { CompileSummaryProvider } from './verify-sketch';
2628

2729
interface DaemonAddress {
2830
/**
@@ -107,6 +109,8 @@ export class InoLanguage extends SketchContribution {
107109
private readonly notificationCenter: NotificationCenter;
108110
@inject(BoardsDataStore)
109111
private readonly boardDataStore: BoardsDataStore;
112+
@inject(CompileSummaryProvider)
113+
private readonly compileSummaryProvider: CompileSummaryProvider;
110114

111115
private readonly toDispose = new DisposableCollection();
112116
private readonly languageServerStartMutex = new Mutex();
@@ -173,6 +177,13 @@ export class InoLanguage extends SketchContribution {
173177
}
174178
}
175179
}),
180+
this.compileSummaryProvider.onDidChangeCompileSummary(
181+
(compileSummary) => {
182+
if (compileSummary) {
183+
this.fireBuildDidComplete(compileSummary);
184+
}
185+
}
186+
),
176187
]);
177188
Promise.all([
178189
this.boardsServiceProvider.ready,
@@ -317,4 +328,32 @@ export class InoLanguage extends SketchContribution {
317328
params
318329
);
319330
}
331+
332+
// Execute the a command contributed by the Arduino Tools VSIX to send the `ino/buildDidComplete` notification to the language server
333+
private async fireBuildDidComplete(
334+
compileSummary: CompileSummary
335+
): Promise<void> {
336+
const params = {
337+
...compileSummary,
338+
};
339+
console.info(
340+
`Executing 'arduino.languageserver.notifyBuildDidComplete' with ${JSON.stringify(
341+
params.buildOutputUri
342+
)}`
343+
);
344+
345+
try {
346+
await this.commandService.executeCommand(
347+
'arduino.languageserver.notifyBuildDidComplete',
348+
params
349+
);
350+
} catch (err) {
351+
console.error(
352+
`Unexpected error when firing event on build did complete. ${JSON.stringify(
353+
params.buildOutputUri
354+
)}`,
355+
err
356+
);
357+
}
358+
}
320359
}

arduino-ide-extension/src/browser/contributions/update-arduino-state.ts

+15-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import { DisposableCollection } from '@theia/core/lib/common/disposable';
22
import URI from '@theia/core/lib/common/uri';
33
import { inject, injectable } from '@theia/core/shared/inversify';
4-
import { HostedPluginSupport } from '../hosted/hosted-plugin-support';
54
import type { ArduinoState } from 'vscode-arduino-api';
65
import {
6+
BoardsConfig,
77
BoardsService,
88
CompileSummary,
9-
isCompileSummary,
10-
BoardsConfig,
119
PortIdentifier,
1210
resolveDetectedPort,
1311
} from '../../common/protocol';
@@ -18,8 +16,10 @@ import {
1816
} from '../../common/protocol/arduino-context-mapper';
1917
import { BoardsDataStore } from '../boards/boards-data-store';
2018
import { BoardsServiceProvider } from '../boards/boards-service-provider';
19+
import { HostedPluginSupport } from '../hosted/hosted-plugin-support';
2120
import { CurrentSketch } from '../sketches-service-client-impl';
2221
import { SketchContribution } from './contribution';
22+
import { CompileSummaryProvider } from './verify-sketch';
2323

2424
/**
2525
* (non-API) exported for tests
@@ -43,6 +43,8 @@ export class UpdateArduinoState extends SketchContribution {
4343
private readonly boardsDataStore: BoardsDataStore;
4444
@inject(HostedPluginSupport)
4545
private readonly hostedPluginSupport: HostedPluginSupport;
46+
@inject(CompileSummaryProvider)
47+
private readonly compileSummaryProvider: CompileSummaryProvider;
4648

4749
private readonly toDispose = new DisposableCollection();
4850

@@ -60,14 +62,13 @@ export class UpdateArduinoState extends SketchContribution {
6062
this.configService.onDidChangeSketchDirUri((userDirUri) =>
6163
this.updateUserDirPath(userDirUri)
6264
),
63-
this.commandService.onDidExecuteCommand(({ commandId, args }) => {
64-
if (
65-
commandId === 'arduino.languageserver.notifyBuildDidComplete' &&
66-
isCompileSummary(args[0])
67-
) {
68-
this.updateCompileSummary(args[0]);
65+
this.compileSummaryProvider.onDidChangeCompileSummary(
66+
(compilerSummary) => {
67+
if (compilerSummary) {
68+
this.updateCompileSummary(compilerSummary);
69+
}
6970
}
70-
}),
71+
),
7172
this.boardsDataStore.onDidChange((event) => {
7273
const selectedFqbn =
7374
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn;
@@ -88,6 +89,10 @@ export class UpdateArduinoState extends SketchContribution {
8889
this.updateSketchPath(this.sketchServiceClient.tryGetCurrentSketch());
8990
this.updateUserDirPath(this.configService.tryGetSketchDirUri());
9091
this.updateDataDirPath(this.configService.tryGetDataDirUri());
92+
const { compileSummary } = this.compileSummaryProvider;
93+
if (compileSummary) {
94+
this.updateCompileSummary(compileSummary);
95+
}
9196
}
9297

9398
onStop(): void {

arduino-ide-extension/src/browser/contributions/verify-sketch.ts

+35-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Emitter } from '@theia/core/lib/common/event';
1+
import { Emitter, Event } from '@theia/core/lib/common/event';
22
import { nls } from '@theia/core/lib/common/nls';
33
import { inject, injectable } from '@theia/core/shared/inversify';
4-
import type { CoreService } from '../../common/protocol';
4+
import type { CompileSummary, CoreService } from '../../common/protocol';
55
import { ArduinoMenus } from '../menu/arduino-menus';
66
import { CurrentSketch } from '../sketches-service-client-impl';
77
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
@@ -15,6 +15,12 @@ import {
1515
} from './contribution';
1616
import { CoreErrorHandler } from './core-error-handler';
1717

18+
export const CompileSummaryProvider = Symbol('CompileSummaryProvider');
19+
export interface CompileSummaryProvider {
20+
readonly compileSummary: CompileSummary | undefined;
21+
readonly onDidChangeCompileSummary: Event<CompileSummary | undefined>;
22+
}
23+
1824
export type VerifySketchMode =
1925
/**
2026
* When the user explicitly triggers the verify command from the primary UI: menu, toolbar, or keybinding. The UI shows the output, updates the toolbar items state, etc.
@@ -46,13 +52,20 @@ export interface VerifySketchParams {
4652
type VerifyProgress = 'idle' | VerifySketchMode;
4753

4854
@injectable()
49-
export class VerifySketch extends CoreServiceContribution {
55+
export class VerifySketch
56+
extends CoreServiceContribution
57+
implements CompileSummaryProvider
58+
{
5059
@inject(CoreErrorHandler)
5160
private readonly coreErrorHandler: CoreErrorHandler;
5261

5362
private readonly onDidChangeEmitter = new Emitter<void>();
5463
private readonly onDidChange = this.onDidChangeEmitter.event;
64+
private readonly onDidChangeCompileSummaryEmitter = new Emitter<
65+
CompileSummary | undefined
66+
>();
5567
private verifyProgress: VerifyProgress = 'idle';
68+
private _compileSummary: CompileSummary | undefined;
5669

5770
override registerCommands(registry: CommandRegistry): void {
5871
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
@@ -117,6 +130,21 @@ export class VerifySketch extends CoreServiceContribution {
117130
super.handleError(error);
118131
}
119132

133+
get compileSummary(): CompileSummary | undefined {
134+
return this._compileSummary;
135+
}
136+
137+
private updateCompileSummary(
138+
compileSummary: CompileSummary | undefined
139+
): void {
140+
this._compileSummary = compileSummary;
141+
this.onDidChangeCompileSummaryEmitter.fire(this._compileSummary);
142+
}
143+
144+
get onDidChangeCompileSummary(): Event<CompileSummary | undefined> {
145+
return this.onDidChangeCompileSummaryEmitter.event;
146+
}
147+
120148
private async verifySketch(
121149
params?: VerifySketchParams
122150
): Promise<CoreService.Options.Compile | undefined> {
@@ -141,7 +169,7 @@ export class VerifySketch extends CoreServiceContribution {
141169
return options;
142170
}
143171

144-
await this.doWithProgress({
172+
const compileSummary = await this.doWithProgress({
145173
progressText: nls.localize(
146174
'arduino/sketch/compile',
147175
'Compiling sketch...'
@@ -160,6 +188,9 @@ export class VerifySketch extends CoreServiceContribution {
160188
nls.localize('arduino/sketch/doneCompiling', 'Done compiling.'),
161189
{ timeout: 3000 }
162190
);
191+
192+
this.updateCompileSummary(compileSummary);
193+
163194
// Returns with the used options for the compilation
164195
// so that follow-up tasks (such as upload) can reuse the compiled code.
165196
// Note that the `fqbn` is already decorated with the board settings, if any.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export interface CoreService {
171171
compile(
172172
options: CoreService.Options.Compile,
173173
cancellationToken?: CancellationToken
174-
): Promise<void>;
174+
): Promise<CompileSummary | undefined>;
175175
upload(
176176
options: CoreService.Options.Upload,
177177
cancellationToken?: CancellationToken

arduino-ide-extension/src/node/core-service-impl.ts

+14-41
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { type ClientReadableStream } from '@grpc/grpc-js';
22
import { ApplicationError } from '@theia/core/lib/common/application-error';
33
import type { CancellationToken } from '@theia/core/lib/common/cancellation';
4-
import { CommandService } from '@theia/core/lib/common/command';
54
import {
65
Disposable,
76
DisposableCollection,
@@ -69,15 +68,13 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
6968
private readonly responseService: ResponseService;
7069
@inject(MonitorManager)
7170
private readonly monitorManager: MonitorManager;
72-
@inject(CommandService)
73-
private readonly commandService: CommandService;
7471
@inject(BoardDiscovery)
7572
private readonly boardDiscovery: BoardDiscovery;
7673

7774
async compile(
7875
options: CoreService.Options.Compile,
7976
cancellationToken?: CancellationToken
80-
): Promise<void> {
77+
): Promise<CompileSummary | undefined> {
8178
const coreClient = await this.coreClient;
8279
const { client, instance } = coreClient;
8380
const request = this.compileRequest(options, instance);
@@ -91,7 +88,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
9188
);
9289
const toDisposeOnFinally = new DisposableCollection(handler);
9390

94-
return new Promise<void>((resolve, reject) => {
91+
return new Promise<CompileSummary | undefined>((resolve, reject) => {
9592
let hasRetried = false;
9693

9794
const handleUnexpectedError = (error: Error) => {
@@ -164,50 +161,26 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
164161
call
165162
.on('data', handler.onData)
166163
.on('error', handleError)
167-
.on('end', resolve);
164+
.on('end', () => {
165+
if (isCompileSummary(compileSummary)) {
166+
resolve(compileSummary);
167+
} else {
168+
console.error(
169+
`Have not received the full compile summary from the CLI while running the compilation. ${JSON.stringify(
170+
compileSummary
171+
)}`
172+
);
173+
resolve(undefined);
174+
}
175+
});
168176
};
169177

170178
startCompileStream();
171179
}).finally(() => {
172180
toDisposeOnFinally.dispose();
173-
if (!isCompileSummary(compileSummary)) {
174-
if (cancellationToken && cancellationToken.isCancellationRequested) {
175-
// NOOP
176-
return;
177-
}
178-
console.error(
179-
`Have not received the full compile summary from the CLI while running the compilation. ${JSON.stringify(
180-
compileSummary
181-
)}`
182-
);
183-
} else {
184-
this.fireBuildDidComplete(compileSummary);
185-
}
186181
});
187182
}
188183

189-
// This executes on the frontend, the VS Code extension receives it, and sends an `ino/buildDidComplete` notification to the language server.
190-
private fireBuildDidComplete(compileSummary: CompileSummary): void {
191-
const params = {
192-
...compileSummary,
193-
};
194-
console.info(
195-
`Executing 'arduino.languageserver.notifyBuildDidComplete' with ${JSON.stringify(
196-
params.buildOutputUri
197-
)}`
198-
);
199-
this.commandService
200-
.executeCommand('arduino.languageserver.notifyBuildDidComplete', params)
201-
.catch((err) =>
202-
console.error(
203-
`Unexpected error when firing event on build did complete. ${JSON.stringify(
204-
params.buildOutputUri
205-
)}`,
206-
err
207-
)
208-
);
209-
}
210-
211184
private compileRequest(
212185
options: CoreService.Options.Compile & {
213186
exportBinaries?: boolean;

0 commit comments

Comments
 (0)