Skip to content

Commit a0a3bf4

Browse files
author
Akos Kitta
committed
fix: propagate monitor errors to the frontend
- Handle when the board's platform is not installed (Closes #1974) - UX: Smoother monitor widget reset (Closes #1985) - Fixed monitor <input> readOnly state (Closes #1984) - Set monitor widget header color (Ref #682) Closes #1508 Signed-off-by: Akos Kitta <[email protected]>
1 parent 6e72be1 commit a0a3bf4

15 files changed

+637
-333
lines changed

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

+1-9
Original file line numberDiff line numberDiff line change
@@ -496,15 +496,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
496496
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
497497
bind(WidgetFactory).toDynamicValue((context) => ({
498498
id: MonitorWidget.ID,
499-
createWidget: () => {
500-
return new MonitorWidget(
501-
context.container.get<MonitorModel>(MonitorModel),
502-
context.container.get<MonitorManagerProxyClient>(
503-
MonitorManagerProxyClient
504-
),
505-
context.container.get<BoardsServiceProvider>(BoardsServiceProvider)
506-
);
507-
},
499+
createWidget: () => context.container.get(MonitorWidget),
508500
}));
509501

510502
bind(MonitorManagerProxyFactory).toFactory(

Diff for: arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts

+30-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import {
2+
ApplicationError,
23
CommandRegistry,
34
Disposable,
45
Emitter,
56
MessageService,
67
nls,
78
} from '@theia/core';
9+
import { Deferred } from '@theia/core/lib/common/promise-util';
810
import { inject, injectable } from '@theia/core/shared/inversify';
911
import { Board, Port } from '../common/protocol';
1012
import {
@@ -37,7 +39,7 @@ export class MonitorManagerProxyClientImpl
3739
readonly onMonitorSettingsDidChange =
3840
this.onMonitorSettingsDidChangeEmitter.event;
3941

40-
protected readonly onMonitorShouldResetEmitter = new Emitter();
42+
protected readonly onMonitorShouldResetEmitter = new Emitter<void>();
4143
readonly onMonitorShouldReset = this.onMonitorShouldResetEmitter.event;
4244

4345
// WebSocket used to handle pluggable monitor communication between
@@ -71,9 +73,11 @@ export class MonitorManagerProxyClientImpl
7173
* @param addressPort port of the WebSocket
7274
*/
7375
async connect(addressPort: number): Promise<void> {
74-
if (!!this.webSocket) {
75-
if (this.wsPort === addressPort) return;
76-
else this.disconnect();
76+
if (this.webSocket) {
77+
if (this.wsPort === addressPort) {
78+
return;
79+
}
80+
this.disconnect();
7781
}
7882
try {
7983
this.webSocket = new WebSocket(`ws://localhost:${addressPort}`);
@@ -87,6 +91,9 @@ export class MonitorManagerProxyClientImpl
8791
return;
8892
}
8993

94+
const opened = new Deferred<void>();
95+
this.webSocket.onopen = () => opened.resolve();
96+
this.webSocket.onerror = () => opened.reject();
9097
this.webSocket.onmessage = (message) => {
9198
const parsedMessage = JSON.parse(message.data);
9299
if (Array.isArray(parsedMessage))
@@ -99,19 +106,26 @@ export class MonitorManagerProxyClientImpl
99106
}
100107
};
101108
this.wsPort = addressPort;
109+
return opened.promise;
102110
}
103111

104112
/**
105113
* Disconnects the WebSocket if connected.
106114
*/
107115
disconnect(): void {
108-
if (!this.webSocket) return;
116+
if (!this.webSocket) {
117+
return;
118+
}
109119
this.onBoardsConfigChanged?.dispose();
110120
this.onBoardsConfigChanged = undefined;
111121
try {
112-
this.webSocket?.close();
122+
this.webSocket.close();
113123
this.webSocket = undefined;
114-
} catch {
124+
} catch (err) {
125+
console.error(
126+
'Could not close the websocket connection for the monitor.',
127+
err
128+
);
115129
this.messageService.error(
116130
nls.localize(
117131
'arduino/monitor/unableToCloseWebSocket',
@@ -126,6 +140,7 @@ export class MonitorManagerProxyClientImpl
126140
}
127141

128142
async startMonitor(settings?: PluggableMonitorSettings): Promise<void> {
143+
await this.boardsServiceProvider.reconciled;
129144
this.lastConnectedBoard = {
130145
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
131146
selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort,
@@ -150,7 +165,7 @@ export class MonitorManagerProxyClientImpl
150165
? Port.keyOf(this.lastConnectedBoard.selectedPort)
151166
: undefined)
152167
) {
153-
this.onMonitorShouldResetEmitter.fire(null);
168+
this.onMonitorShouldResetEmitter.fire();
154169
this.lastConnectedBoard = {
155170
selectedBoard: selectedBoard,
156171
selectedPort: selectedPort,
@@ -167,7 +182,13 @@ export class MonitorManagerProxyClientImpl
167182
const { selectedBoard, selectedPort } =
168183
this.boardsServiceProvider.boardsConfig;
169184
if (!selectedBoard || !selectedBoard.fqbn || !selectedPort) return;
170-
await this.server().startMonitor(selectedBoard, selectedPort, settings);
185+
try {
186+
await this.server().startMonitor(selectedBoard, selectedPort, settings);
187+
} catch (err) {
188+
this.messageService.error(
189+
ApplicationError.is(err) ? err.message : String(err)
190+
);
191+
}
171192
}
172193

173194
getCurrentSettings(board: Board, port: Port): Promise<MonitorSettings> {

Diff for: arduino-ide-extension/src/browser/monitor-model.ts

+37-47
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ import {
44
LocalStorageService,
55
} from '@theia/core/lib/browser';
66
import { inject, injectable } from '@theia/core/shared/inversify';
7-
import { MonitorManagerProxyClient } from '../common/protocol';
7+
import {
8+
isMonitorConnected,
9+
MonitorConnectionStatus,
10+
monitorConnectionStatusEquals,
11+
MonitorEOL,
12+
MonitorManagerProxyClient,
13+
MonitorState,
14+
} from '../common/protocol';
815
import { isNullOrUndefined } from '../common/utils';
916
import { MonitorSettings } from '../node/monitor-settings/monitor-settings-provider';
1017

@@ -19,48 +26,48 @@ export class MonitorModel implements FrontendApplicationContribution {
1926
protected readonly monitorManagerProxy: MonitorManagerProxyClient;
2027

2128
protected readonly onChangeEmitter: Emitter<
22-
MonitorModel.State.Change<keyof MonitorModel.State>
29+
MonitorState.Change<keyof MonitorState>
2330
>;
2431

2532
protected _autoscroll: boolean;
2633
protected _timestamp: boolean;
27-
protected _lineEnding: MonitorModel.EOL;
34+
protected _lineEnding: MonitorEOL;
2835
protected _interpolate: boolean;
2936
protected _darkTheme: boolean;
3037
protected _wsPort: number;
3138
protected _serialPort: string;
32-
protected _connected: boolean;
39+
protected _connectionStatus: MonitorConnectionStatus;
3340

3441
constructor() {
3542
this._autoscroll = true;
3643
this._timestamp = false;
3744
this._interpolate = false;
38-
this._lineEnding = MonitorModel.EOL.DEFAULT;
45+
this._lineEnding = MonitorEOL.DEFAULT;
3946
this._darkTheme = false;
4047
this._wsPort = 0;
4148
this._serialPort = '';
42-
this._connected = true;
49+
this._connectionStatus = 'not-connected';
4350

4451
this.onChangeEmitter = new Emitter<
45-
MonitorModel.State.Change<keyof MonitorModel.State>
52+
MonitorState.Change<keyof MonitorState>
4653
>();
4754
}
4855

4956
onStart(): void {
5057
this.localStorageService
51-
.getData<MonitorModel.State>(MonitorModel.STORAGE_ID)
58+
.getData<MonitorState>(MonitorModel.STORAGE_ID)
5259
.then(this.restoreState.bind(this));
5360

5461
this.monitorManagerProxy.onMonitorSettingsDidChange(
5562
this.onMonitorSettingsDidChange.bind(this)
5663
);
5764
}
5865

59-
get onChange(): Event<MonitorModel.State.Change<keyof MonitorModel.State>> {
66+
get onChange(): Event<MonitorState.Change<keyof MonitorState>> {
6067
return this.onChangeEmitter.event;
6168
}
6269

63-
protected restoreState(state: MonitorModel.State): void {
70+
protected restoreState(state: MonitorState): void {
6471
if (!state) {
6572
return;
6673
}
@@ -125,11 +132,11 @@ export class MonitorModel implements FrontendApplicationContribution {
125132
this.timestamp = !this._timestamp;
126133
}
127134

128-
get lineEnding(): MonitorModel.EOL {
135+
get lineEnding(): MonitorEOL {
129136
return this._lineEnding;
130137
}
131138

132-
set lineEnding(lineEnding: MonitorModel.EOL) {
139+
set lineEnding(lineEnding: MonitorEOL) {
133140
if (lineEnding === this._lineEnding) return;
134141
this._lineEnding = lineEnding;
135142
this.monitorManagerProxy.changeSettings({
@@ -211,19 +218,26 @@ export class MonitorModel implements FrontendApplicationContribution {
211218
);
212219
}
213220

214-
get connected(): boolean {
215-
return this._connected;
221+
get connectionStatus(): MonitorConnectionStatus {
222+
return this._connectionStatus;
216223
}
217224

218-
set connected(connected: boolean) {
219-
if (connected === this._connected) return;
220-
this._connected = connected;
225+
set connectionStatus(connectionStatus: MonitorConnectionStatus) {
226+
if (
227+
monitorConnectionStatusEquals(connectionStatus, this.connectionStatus)
228+
) {
229+
return;
230+
}
231+
this._connectionStatus = connectionStatus;
221232
this.monitorManagerProxy.changeSettings({
222-
monitorUISettings: { connected },
233+
monitorUISettings: {
234+
connectionStatus,
235+
connected: isMonitorConnected(connectionStatus),
236+
},
223237
});
224238
this.onChangeEmitter.fire({
225-
property: 'connected',
226-
value: this._connected,
239+
property: 'connectionStatus',
240+
value: this._connectionStatus,
227241
});
228242
}
229243

@@ -238,7 +252,7 @@ export class MonitorModel implements FrontendApplicationContribution {
238252
darkTheme,
239253
wsPort,
240254
serialPort,
241-
connected,
255+
connectionStatus,
242256
} = monitorUISettings;
243257

244258
if (!isNullOrUndefined(autoscroll)) this.autoscroll = autoscroll;
@@ -248,31 +262,7 @@ export class MonitorModel implements FrontendApplicationContribution {
248262
if (!isNullOrUndefined(darkTheme)) this.darkTheme = darkTheme;
249263
if (!isNullOrUndefined(wsPort)) this.wsPort = wsPort;
250264
if (!isNullOrUndefined(serialPort)) this.serialPort = serialPort;
251-
if (!isNullOrUndefined(connected)) this.connected = connected;
265+
if (!isNullOrUndefined(connectionStatus))
266+
this.connectionStatus = connectionStatus;
252267
};
253268
}
254-
255-
// TODO: Move this to /common
256-
export namespace MonitorModel {
257-
export interface State {
258-
autoscroll: boolean;
259-
timestamp: boolean;
260-
lineEnding: EOL;
261-
interpolate: boolean;
262-
darkTheme: boolean;
263-
wsPort: number;
264-
serialPort: string;
265-
connected: boolean;
266-
}
267-
export namespace State {
268-
export interface Change<K extends keyof State> {
269-
readonly property: K;
270-
readonly value: State[K];
271-
}
272-
}
273-
274-
export type EOL = '' | '\n' | '\r' | '\r\n';
275-
export namespace EOL {
276-
export const DEFAULT: EOL = '\n';
277-
}
278-
}

Diff for: arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { ArduinoToolbar } from '../../toolbar/arduino-toolbar';
1111
import { ArduinoMenus } from '../../menu/arduino-menus';
1212
import { nls } from '@theia/core/lib/common';
13+
import { Event } from '@theia/core/lib/common/event';
1314
import { MonitorModel } from '../../monitor-model';
1415
import { MonitorManagerProxyClient } from '../../../common/protocol';
1516

@@ -84,13 +85,13 @@ export class MonitorViewContribution
8485
id: 'monitor-autoscroll',
8586
render: () => this.renderAutoScrollButton(),
8687
isVisible: (widget) => widget instanceof MonitorWidget,
87-
onDidChange: this.model.onChange as any, // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
88+
onDidChange: this.model.onChange as Event<unknown> as Event<void>,
8889
});
8990
registry.registerItem({
9091
id: 'monitor-timestamp',
9192
render: () => this.renderTimestampButton(),
9293
isVisible: (widget) => widget instanceof MonitorWidget,
93-
onDidChange: this.model.onChange as any, // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
94+
onDidChange: this.model.onChange as Event<unknown> as Event<void>,
9495
});
9596
registry.registerItem({
9697
id: SerialMonitor.Commands.CLEAR_OUTPUT.id,
@@ -143,8 +144,7 @@ export class MonitorViewContribution
143144
protected async reset(): Promise<void> {
144145
const widget = this.tryGetWidget();
145146
if (widget) {
146-
widget.dispose();
147-
await this.openView({ activate: true, reveal: true });
147+
widget.reset();
148148
}
149149
}
150150

0 commit comments

Comments
 (0)