From 2b2ea7264348621d895a7ca082e2d8d2d6f84603 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Wed, 2 Mar 2022 17:55:11 +0100 Subject: [PATCH 01/66] backend structure WIP --- .../src/node/arduino-ide-backend-module.ts | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 223b52b8b..bd2dd868d 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -40,16 +40,8 @@ import { ArduinoDaemon, ArduinoDaemonPath, } from '../common/protocol/arduino-daemon'; -import { - SerialServiceImpl, - SerialServiceName, -} from './serial/serial-service-impl'; -import { - SerialService, - SerialServicePath, - SerialServiceClient, -} from '../common/protocol/serial-service'; -import { MonitorClientProvider } from './serial/monitor-client-provider'; +import { SerialServiceName } from './serial/serial-service-impl'; + import { ConfigServiceImpl } from './config-service-impl'; import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { EnvVariablesServer } from './theia/env-variables/env-variables-server'; @@ -205,19 +197,25 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // #endregion Theia customizations + // a single MonitorManager is responsible for handling the actual connections to the pluggable monitors + bind(MonitorManager).toSelf().inSingletonScope(); + // Serial client provider per connected frontend. bind(ConnectionContainerModule).toConstantValue( ConnectionContainerModule.create(({ bind, bindBackendService }) => { - bind(MonitorClientProvider).toSelf().inSingletonScope(); - bind(SerialServiceImpl).toSelf().inSingletonScope(); - bind(SerialService).toService(SerialServiceImpl); - bindBackendService( - SerialServicePath, - SerialService, - (service, client) => { - service.setClient(client); - client.onDidCloseConnection(() => service.dispose()); - return service; + bind(MonitorManagerProxyImpl).toSelf().inSingletonScope(); + bind(MonitorManagerProxy).toService(MonitorManagerProxyImpl); + bindBackendService( + MonitorManagerProxyPath, + MonitorManagerProxy, + (monitorMgrProxy, client) => { + monitorMgrProxy.setClient(client); + // when the client close the connection, the proxy is disposed. + // when the MonitorManagerProxy is disposed, it informs the MonitorManager + // telling him that it does not need an address/board anymore. + // the MonitorManager will then dispose the actual connection if there are no proxies using it + client.onDidCloseConnection(() => monitorMgrProxy.dispose()); + return monitorMgrProxy; } ); }) From ebab0b226fea9e52d1533305797be010d17212ce Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 3 Mar 2022 10:52:27 +0100 Subject: [PATCH 02/66] Scaffold interfaces and classes for pluggable monitors --- .../browser/arduino-ide-frontend-module.ts | 6 ++++++ .../monitor-manager-proxy-client-impl.ts | 6 ++++++ .../src/common/monitor-manager-proxy.ts | 12 +++++++++++ .../src/node/arduino-ide-backend-module.ts | 3 +++ .../src/node/monitor-manager-proxy-impl.ts | 20 +++++++++++++++++++ .../src/node/monitor-manager.ts | 6 ++++++ 6 files changed, 53 insertions(+) create mode 100644 arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts create mode 100644 arduino-ide-extension/src/common/monitor-manager-proxy.ts create mode 100644 arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts create mode 100644 arduino-ide-extension/src/node/monitor-manager.ts diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 5ceae9179..c06f9f7b2 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -275,6 +275,8 @@ import { IDEUpdaterDialogWidget, } from './dialogs/ide-updater/ide-updater-dialog'; import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider'; +import { MonitorManagerProxyClient } from '../common/monitor-manager-proxy'; +import { MonitorManagerProxyClientImpl } from './monitor-manager-proxy-client-impl'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -431,6 +433,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // Serial service client to receive and delegate notifications from the backend. bind(SerialServiceClient).to(SerialServiceClientImpl).inSingletonScope(); + // Monitor manager proxy client to receive and delegate pluggable monitors + // notifications from the backend + bind(MonitorManagerProxyClient).to(MonitorManagerProxyClientImpl).inSingletonScope(); + bind(WorkspaceService).toSelf().inSingletonScope(); rebind(TheiaWorkspaceService).toService(WorkspaceService); bind(WorkspaceVariableContribution).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts new file mode 100644 index 000000000..706027839 --- /dev/null +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -0,0 +1,6 @@ +import { injectable } from "@theia/core/shared/inversify"; +import { MonitorManagerProxyClient } from "../common/monitor-manager-proxy"; + +@injectable() +export class MonitorManagerProxyClientImpl implements MonitorManagerProxyClient { +} diff --git a/arduino-ide-extension/src/common/monitor-manager-proxy.ts b/arduino-ide-extension/src/common/monitor-manager-proxy.ts new file mode 100644 index 000000000..ba08b361b --- /dev/null +++ b/arduino-ide-extension/src/common/monitor-manager-proxy.ts @@ -0,0 +1,12 @@ +import { JsonRpcServer } from "@theia/core"; + +export const MonitorManagerProxyPath = '/services/monitor-manager-proxy'; +export const MonitorManagerProxy = Symbol('MonitorManagerProxy'); +export interface MonitorManagerProxy extends JsonRpcServer { + +} + +export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); +export interface MonitorManagerProxyClient { + +} diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index bd2dd868d..1646171ae 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -86,6 +86,9 @@ import WebSocketServiceImpl from './web-socket/web-socket-service-impl'; import { WebSocketService } from './web-socket/web-socket-service'; import { ArduinoLocalizationContribution } from './arduino-localization-contribution'; import { LocalizationContribution } from '@theia/core/lib/node/i18n/localization-contribution'; +import { MonitorManagerProxyImpl } from './monitor-manager-proxy-impl'; +import { MonitorManager } from './monitor-manager'; +import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyPath } from '../common/monitor-manager-proxy'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts new file mode 100644 index 000000000..814c7bc3d --- /dev/null +++ b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts @@ -0,0 +1,20 @@ +import { inject, injectable } from "@theia/core/shared/inversify"; +import { MonitorManagerProxy, MonitorManagerProxyClient } from "../common/monitor-manager-proxy"; +import { MonitorManager } from "./monitor-manager"; + +@injectable() +export class MonitorManagerProxyImpl implements MonitorManagerProxy { + constructor( + @inject(MonitorManager) + protected readonly manager: MonitorManager, + ) { + } + + dispose(): void { + // TODO + } + + setClient(client: MonitorManagerProxyClient | undefined): void { + // TODO + } +} \ No newline at end of file diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts new file mode 100644 index 000000000..1814c94af --- /dev/null +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -0,0 +1,6 @@ +import { injectable } from "@theia/core/shared/inversify"; + +@injectable() +export class MonitorManager { + +} \ No newline at end of file From 3133b01c4a759299ca3a667f6802a8de47078107 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 4 Mar 2022 17:57:23 +0100 Subject: [PATCH 03/66] Implement MonitorService to handle pluggable monitor lifetime --- .../src/common/protocol/index.ts | 1 + .../src/common/protocol/monitor-service.ts | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 arduino-ide-extension/src/common/protocol/monitor-service.ts diff --git a/arduino-ide-extension/src/common/protocol/index.ts b/arduino-ide-extension/src/common/protocol/index.ts index 101905752..1a9a25c28 100644 --- a/arduino-ide-extension/src/common/protocol/index.ts +++ b/arduino-ide-extension/src/common/protocol/index.ts @@ -13,3 +13,4 @@ export * from './examples-service'; export * from './executable-service'; export * from './response-service'; export * from './notification-service'; +export * from './monitor-service'; diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts new file mode 100644 index 000000000..d3d5c1edc --- /dev/null +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -0,0 +1,30 @@ +import { Event, JsonRpcServer } from "@theia/core"; +import { Board, Port } from './boards-service'; + +export const MonitorManagerProxyPath = '/services/monitor-manager-proxy'; +export const MonitorManagerProxy = Symbol('MonitorManagerProxy'); +export interface MonitorManagerProxy extends JsonRpcServer { + //set the monitor settings, which includes address, port and other monitor-specific settings + setMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise; +} + +export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); +export interface MonitorManagerProxyClient { + onWebSocketChanged: Event; + notifyWebSocketChanged(message: string): void; +} + +export interface MonitorSetting { + // The setting identifier + readonly id: string; + // A human-readable label of the setting (to be displayed on the GUI) + readonly label: string; + // The setting type (at the moment only "enum" is avaiable) + readonly type: string; + // The values allowed on "enum" types + readonly values: string[]; + // The selected value + selectedValue: string; +} + +export type MonitorSettings = Record; \ No newline at end of file From 750796d3a02479a55f68732f72d567b80ab7d433 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 4 Mar 2022 17:59:27 +0100 Subject: [PATCH 04/66] Rename WebSocketService to WebSocketProvider and uninjected it --- .../src/node/arduino-ide-backend-module.ts | 11 +++++------ .../src/node/serial/serial-service-impl.ts | 4 ++-- ...et-service-impl.ts => web-socket-provider-impl.ts} | 4 ++-- .../{web-socket-service.ts => web-socket-provider.ts} | 4 ++-- .../src/test/node/serial-service-impl.test.ts | 6 +++--- 5 files changed, 14 insertions(+), 15 deletions(-) rename arduino-ide-extension/src/node/web-socket/{web-socket-service-impl.ts => web-socket-provider-impl.ts} (91%) rename arduino-ide-extension/src/node/web-socket/{web-socket-service.ts => web-socket-provider.ts} (74%) diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 1646171ae..2e5e4b17c 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -82,13 +82,15 @@ import { } from '../common/protocol/authentication-service'; import { ArduinoFirmwareUploaderImpl } from './arduino-firmware-uploader-impl'; import { PlotterBackendContribution } from './plotter/plotter-backend-contribution'; -import WebSocketServiceImpl from './web-socket/web-socket-service-impl'; -import { WebSocketService } from './web-socket/web-socket-service'; import { ArduinoLocalizationContribution } from './arduino-localization-contribution'; import { LocalizationContribution } from '@theia/core/lib/node/i18n/localization-contribution'; import { MonitorManagerProxyImpl } from './monitor-manager-proxy-impl'; import { MonitorManager } from './monitor-manager'; -import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyPath } from '../common/monitor-manager-proxy'; +import { + MonitorManagerProxy, + MonitorManagerProxyClient, + MonitorManagerProxyPath, +} from '../common/monitor-manager-proxy'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -172,9 +174,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { }) ); - // Shared WebSocketService for the backend. This will manage all websocket conenctions - bind(WebSocketService).to(WebSocketServiceImpl).inSingletonScope(); - // Shared Arduino core client provider service for the backend. bind(CoreClientProvider).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/node/serial/serial-service-impl.ts b/arduino-ide-extension/src/node/serial/serial-service-impl.ts index db094d31e..5b6475c34 100644 --- a/arduino-ide-extension/src/node/serial/serial-service-impl.ts +++ b/arduino-ide-extension/src/node/serial/serial-service-impl.ts @@ -17,7 +17,7 @@ import { } from '../cli-protocol/cc/arduino/cli/monitor/v1/monitor_pb'; import { MonitorClientProvider } from './monitor-client-provider'; import { Board } from '../../common/protocol/boards-service'; -import { WebSocketService } from '../web-socket/web-socket-service'; +import { WebSocketProvider } from '../web-socket/web-socket-provider'; import { SerialPlotter } from '../../browser/serial/plotter/protocol'; import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol'; @@ -86,7 +86,7 @@ export class SerialServiceImpl implements SerialService { @inject(MonitorClientProvider) protected readonly serialClientProvider: MonitorClientProvider, - @inject(WebSocketService) + @inject(WebSocketProvider) protected readonly webSocketService: WebSocketService ) { } diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts b/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts similarity index 91% rename from arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts rename to arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts index 869c2cf8d..81d258a0b 100644 --- a/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts +++ b/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts @@ -1,10 +1,10 @@ import { Emitter } from '@theia/core'; import { injectable } from 'inversify'; import * as WebSocket from 'ws'; -import { WebSocketService } from './web-socket-service'; +import { WebSocketProvider } from './web-socket-provider'; @injectable() -export default class WebSocketServiceImpl implements WebSocketService { +export default class WebSocketProviderImpl implements WebSocketProvider { protected wsClients: WebSocket[]; protected server: WebSocket.Server; diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-service.ts b/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts similarity index 74% rename from arduino-ide-extension/src/node/web-socket/web-socket-service.ts rename to arduino-ide-extension/src/node/web-socket/web-socket-provider.ts index c793a07c4..6aa102040 100644 --- a/arduino-ide-extension/src/node/web-socket/web-socket-service.ts +++ b/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts @@ -1,8 +1,8 @@ import { Event } from '@theia/core/lib/common/event'; import * as WebSocket from 'ws'; -export const WebSocketService = Symbol('WebSocketService'); -export interface WebSocketService { +export const WebSocketProvider = Symbol('WebSocketProvider'); +export interface WebSocketProvider { getAddress(): WebSocket.AddressInfo; sendMessage(message: string): void; onMessageReceived: Event; diff --git a/arduino-ide-extension/src/test/node/serial-service-impl.test.ts b/arduino-ide-extension/src/test/node/serial-service-impl.test.ts index 141c240a3..db77a8b87 100644 --- a/arduino-ide-extension/src/test/node/serial-service-impl.test.ts +++ b/arduino-ide-extension/src/test/node/serial-service-impl.test.ts @@ -7,7 +7,7 @@ use(sinonChai); import { ILogger } from '@theia/core/lib/common/logger'; import { MonitorClientProvider } from '../../node/serial/monitor-client-provider'; -import { WebSocketService } from '../../node/web-socket/web-socket-service'; +import { WebSocketProvider } from '../../node/web-socket/web-socket-provider'; import { MonitorServiceClient } from '../../node/cli-protocol/cc/arduino/cli/monitor/v1/monitor_grpc_pb'; import { Status } from '../../common/protocol'; @@ -16,7 +16,7 @@ describe('SerialServiceImpl', () => { let logger: IMock; let serialClientProvider: IMock; - let webSocketService: IMock; + let webSocketService: IMock; beforeEach(() => { logger = Mock.ofType(); @@ -25,7 +25,7 @@ describe('SerialServiceImpl', () => { logger.setup((b) => b.error(It.isAnyString())); serialClientProvider = Mock.ofType(); - webSocketService = Mock.ofType(); + webSocketService = Mock.ofType(); subject = new SerialServiceImpl( logger.object, From 116b3d598417d15120d5fb055d3cddc775579688 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 4 Mar 2022 18:00:34 +0100 Subject: [PATCH 05/66] Moved some interfaces --- .../src/node/arduino-ide-backend-module.ts | 6 +- .../src/node/monitor-service.ts | 327 ++++++++++++++++++ 2 files changed, 328 insertions(+), 5 deletions(-) create mode 100644 arduino-ide-extension/src/node/monitor-service.ts diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 2e5e4b17c..6b3f585c8 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -86,11 +86,7 @@ import { ArduinoLocalizationContribution } from './arduino-localization-contribu import { LocalizationContribution } from '@theia/core/lib/node/i18n/localization-contribution'; import { MonitorManagerProxyImpl } from './monitor-manager-proxy-impl'; import { MonitorManager } from './monitor-manager'; -import { - MonitorManagerProxy, - MonitorManagerProxyClient, - MonitorManagerProxyPath, -} from '../common/monitor-manager-proxy'; +import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyPath } from '../common/protocol/monitor-service'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts new file mode 100644 index 000000000..cbb408f0f --- /dev/null +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -0,0 +1,327 @@ +import { ClientDuplexStream } from "@grpc/grpc-js"; +import { Disposable, Emitter, ILogger } from "@theia/core"; +import { inject, named } from "@theia/core/shared/inversify"; +import { Board, Port, Status, MonitorSettings } from "../common/protocol"; +import { MonitorPortConfiguration, MonitorPortSetting, MonitorRequest, MonitorResponse } from "./cli-protocol/cc/arduino/cli/commands/v1/monitor_pb"; +import { CoreClientAware } from "./core-client-provider"; +import { WebSocketProvider } from "./web-socket/web-socket-provider"; +import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb' +import WebSocketProviderImpl from "./web-socket/web-socket-provider-impl"; + +export class MonitorService extends CoreClientAware implements Disposable { + // Bidirectional gRPC stream used to receive and send data from the running + // pluggable monitor managed by the Arduino CLI. + protected duplex: ClientDuplexStream | null; + + // Settings used by the currently running pluggable monitor. + // They can be freely modified while running. + protected settings: MonitorSettings; + + // List of messages received from the running pluggable monitor. + // These are flushed from time to time to the frontend. + protected messages: string[] = []; + + // Handles messages received from the frontend via websocket. + protected onMessageReceived?: Disposable; + + // Sends messages to the frontend from time to time. + protected flushMessagesInterval?: NodeJS.Timeout; + + // Triggered each time the number of clients connected + // to the this service WebSocket changes. + protected onWSClientsNumberChanged?: Disposable; + + // Used to notify that the monitor is being disposed + protected readonly onDisposeEmitter = new Emitter(); + readonly onDispose = this.onDisposeEmitter.event; + + protected readonly webSocketProvider: WebSocketProvider = new WebSocketProviderImpl(); + + constructor( + @inject(ILogger) + @named("monitor-service") + protected readonly logger: ILogger, + + private readonly board: Board, + private readonly port: Port, + ) { + super(); + + this.onWSClientsNumberChanged = this.webSocketProvider.onClientsNumberChanged(async (clients: number) => { + if (clients === 0) { + // There are no more clients that want to receive + // data from this monitor, we can freely close + // and dispose it. + this.dispose(); + } + }); + } + + getWebsocketAddress(): number { + return this.webSocketProvider.getAddress().port; + } + + dispose(): void { + this.stop(); + this.onDisposeEmitter.fire(); + } + + /** + * isStarted is used to know if the currently running pluggable monitor is started. + * @returns true if pluggable monitor communication duplex is open, + * false in all other cases. + */ + isStarted(): boolean { + return !!this.duplex; + } + + /** + * Start and connects a monitor using currently set board and port. + * If a monitor is already started or board fqbn, port address and/or protocol + * are missing nothing happens. + * @returns a status to verify connection has been established. + */ + async start(): Promise { + if (this.duplex) { + return Status.ALREADY_CONNECTED; + } + + if (!this.board?.fqbn || !this.port?.address || !this.port?.protocol) { + return Status.CONFIG_MISSING + } + + this.logger.info("starting monitor"); + const coreClient = await this.coreClient(); + const { client, instance } = coreClient; + + this.duplex = client.monitor() + this.duplex + .on('close', () => { + this.logger.info(`monitor to ${this.port?.address} using ${this.port?.protocol} closed by client`) + }) + .on('end', () => { + this.logger.info(`monitor to ${this.port?.address} using ${this.port?.protocol} closed by server`) + }) + .on('error', (err: Error) => { + this.logger.error(err); + // TODO + // this.theiaFEClient?.notifyError() + }) + .on('data', ((res: MonitorResponse) => { + if (res.getError()) { + // TODO: Maybe disconnect + this.logger.error(res.getError()); + return; + } + const data = res.getRxData() + const message = + typeof data === 'string' ? data : new TextDecoder('utf8').decode(data); + this.messages.push(...splitLines(message)) + }).bind(this)); + + const req = new MonitorRequest(); + req.setInstance(instance); + if (this.board?.fqbn) { + req.setFqbn(this.board.fqbn) + } + if (this.port?.address && this.port?.protocol) { + const port = new gRPCPort() + port.setAddress(this.port.address); + port.setProtocol(this.port.protocol); + req.setPort(port); + } + const config = new MonitorPortConfiguration(); + for (const id in this.settings) { + const s = new MonitorPortSetting(); + s.setSettingId(id); + s.setValue(this.settings[id].selectedValue); + config.addSettings(s); + } + req.setPortConfiguration(config) + + const connect = new Promise(resolve => { + if (this.duplex?.write(req)) { + this.startMessagesHandlers(); + this.logger.info(`started monitor to ${this.port?.address} using ${this.port?.protocol}`) + resolve(Status.OK); + } + this.logger.warn(`failed starting monitor to ${this.port?.address} using ${this.port?.protocol}`) + resolve(Status.NOT_CONNECTED); + }); + + const connectTimeout = new Promise(resolve => { + setTimeout(async () => { + this.logger.warn(`timeout starting monitor to ${this.port?.address} using ${this.port?.protocol}`) + resolve(Status.NOT_CONNECTED); + }, 1000); + }); + // Try opening a monitor connection with a timeout + return await Promise.race([ + connect, + connectTimeout, + ]) + } + + /** + * Pauses the currently running monitor, it still closes the gRPC connection + * with the underlying monitor process but it doesn't stop the message handlers + * currently running. + * This is mainly used to handle upload when to the board/port combination + * the monitor is listening to. + * @returns + */ + async pause(): Promise { + return new Promise(resolve => { + if (!this.duplex) { + this.logger.warn(`monitor to ${this.port?.address} using ${this.port?.protocol} already stopped`) + return resolve(); + } + // It's enough to close the connection with the client + // to stop the monitor process + this.duplex.cancel(); + this.duplex = null; + this.logger.info(`stopped monitor to ${this.port?.address} using ${this.port?.protocol}`) + resolve(); + }) + } + + /** + * Stop the monitor currently running + */ + async stop(): Promise { + return this.pause().finally( + this.stopMessagesHandlers + ); + } + + /** + * Send a message to the running monitor, a well behaved monitor + * will then send that message to the board. + * We MUST NEVER send a message that wasn't a user's input to the board. + * @param message string sent to running monitor + * @returns a status to verify message has been sent. + */ + async send(message: string): Promise { + if (!this.duplex) { + return Status.NOT_CONNECTED; + } + const coreClient = await this.coreClient(); + const { instance } = coreClient; + + const req = new MonitorRequest(); + req.setInstance(instance); + req.setTxData(new TextEncoder().encode(message)); + return new Promise(resolve => { + if (this.duplex) { + this.duplex?.write(req, () => { + resolve(Status.OK); + }); + return; + } + this.stop().then(() => resolve(Status.NOT_CONNECTED)); + }) + } + + /** + * Set monitor settings, if there is a running monitor they'll be sent + * to it, otherwise they'll be used when starting one. + * Only values in settings parameter will be change, other values won't + * be changed in any way. + * @param settings map of monitor settings to change + * @returns a status to verify settings have been sent. + */ + async changeSettings(settings: MonitorSettings): Promise { + const config = new MonitorPortConfiguration(); + for (const id in settings) { + const s = new MonitorPortSetting(); + s.setSettingId(id); + s.setValue(settings[id].selectedValue); + config.addSettings(s); + this.settings[id] = settings[id]; + } + + if (!this.duplex) { + return Status.NOT_CONNECTED; + } + const coreClient = await this.coreClient(); + const { instance } = coreClient; + + const req = new MonitorRequest(); + req.setInstance(instance); + req.setPortConfiguration(config) + this.duplex.write(req); + return Status.OK + } + + /** + * Starts the necessary handlers to send and receive + * messages to and from the frontend and the running monitor + */ + private startMessagesHandlers(): void { + if (!this.flushMessagesInterval) { + const flushMessagesToFrontend = () => { + if (this.messages.length) { + this.webSocketProvider.sendMessage(JSON.stringify(this.messages)); + this.messages = []; + } + }; + this.flushMessagesInterval = setInterval(flushMessagesToFrontend, 32); + } + + if (!this.onMessageReceived) { + this.onMessageReceived = this.webSocketProvider.onMessageReceived( + (msg: string) => { + const message: SerialPlotter.Protocol.Message = JSON.parse(msg); + + switch (message.command) { + case SerialPlotter.Protocol.Command.PLOTTER_SEND_MESSAGE: + this.send(message.data); + break; + + case SerialPlotter.Protocol.Command.PLOTTER_SET_BAUDRATE: + this.theiaFEClient?.notifyBaudRateChanged( + parseInt(message.data, 10) as SerialConfig.BaudRate + ); + break; + + case SerialPlotter.Protocol.Command.PLOTTER_SET_LINE_ENDING: + this.theiaFEClient?.notifyLineEndingChanged(message.data); + break; + + case SerialPlotter.Protocol.Command.PLOTTER_SET_INTERPOLATE: + this.theiaFEClient?.notifyInterpolateChanged(message.data); + break; + + default: + break; + } + } + ) + } + } + + /** + * Stops the necessary handlers to send and receive messages to + * and from the frontend and the running monitor + */ + private stopMessagesHandlers(): void { + if (this.flushMessagesInterval) { + clearInterval(this.flushMessagesInterval); + this.flushMessagesInterval = undefined; + } + if (this.onMessageReceived) { + this.onMessageReceived.dispose(); + this.onMessageReceived = undefined; + } + } + +} + +/** + * Splits a string into an array without removing newline char. + * @param s string to split into lines + * @returns an lines array + */ +function splitLines(s: string): string[] { + return s.split(/(?<=\n)/); +} From 2c95e7f0330c9c636cf02515d892aaa6c7438f04 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 4 Mar 2022 18:01:34 +0100 Subject: [PATCH 06/66] Changed upload settings --- .../browser/contributions/burn-bootloader.ts | 8 ++++- .../browser/contributions/upload-sketch.ts | 9 +++-- .../src/common/protocol/core-service.ts | 10 +++--- .../src/node/core-service-impl.ts | 35 +++++++++---------- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts index 75aaef8fa..96953db7b 100644 --- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -60,9 +60,15 @@ export class BurnBootloader extends SketchContribution { this.preferences.get('arduino.upload.verify'), this.preferences.get('arduino.upload.verbose'), ]); + + const board = { + ...boardsConfig.selectedBoard, + name: boardsConfig.selectedBoard?.name || '', + fqbn, + } this.outputChannelManager.getChannel('Arduino').clear(); await this.coreService.burnBootloader({ - fqbn, + board, programmer, port, verify, diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index df196cb7f..b86949fd3 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -226,6 +226,11 @@ export class UploadSketch extends SketchContribution { this.sourceOverride(), ]); + const board = { + ...boardsConfig.selectedBoard, + name: boardsConfig.selectedBoard?.name || '', + fqbn, + } let options: CoreService.Upload.Options | undefined = undefined; const sketchUri = sketch.uri; const optimizeForDebug = this.editorMode.compileForDebug; @@ -247,7 +252,7 @@ export class UploadSketch extends SketchContribution { const programmer = selectedProgrammer; options = { sketchUri, - fqbn, + board, optimizeForDebug, programmer, port, @@ -259,7 +264,7 @@ export class UploadSketch extends SketchContribution { } else { options = { sketchUri, - fqbn, + board, optimizeForDebug, port, verbose, diff --git a/arduino-ide-extension/src/common/protocol/core-service.ts b/arduino-ide-extension/src/common/protocol/core-service.ts index f8216f504..15aa85bb0 100644 --- a/arduino-ide-extension/src/common/protocol/core-service.ts +++ b/arduino-ide-extension/src/common/protocol/core-service.ts @@ -1,5 +1,5 @@ import { BoardUserField } from '.'; -import { Port } from '../../common/protocol/boards-service'; +import { Board, Port } from '../../common/protocol/boards-service'; import { Programmer } from './boards-service'; export const CompilerWarningLiterals = [ @@ -33,7 +33,7 @@ export namespace CoreService { * `file` URI to the sketch folder. */ readonly sketchUri: string; - readonly fqbn?: string | undefined; + readonly board?: Board; readonly optimizeForDebug: boolean; readonly verbose: boolean; readonly sourceOverride: Record; @@ -42,7 +42,7 @@ export namespace CoreService { export namespace Upload { export interface Options extends Compile.Options { - readonly port?: Port | undefined; + readonly port?: Port; readonly programmer?: Programmer | undefined; readonly verify: boolean; readonly userFields: BoardUserField[]; @@ -51,8 +51,8 @@ export namespace CoreService { export namespace Bootloader { export interface Options { - readonly fqbn?: string | undefined; - readonly port?: Port | undefined; + readonly board?: Board; + readonly port?: Port; readonly programmer?: Programmer | undefined; readonly verbose: boolean; readonly verify: boolean; diff --git a/arduino-ide-extension/src/node/core-service-impl.ts b/arduino-ide-extension/src/node/core-service-impl.ts index 85a5af6a1..44cfcd636 100644 --- a/arduino-ide-extension/src/node/core-service-impl.ts +++ b/arduino-ide-extension/src/node/core-service-impl.ts @@ -24,7 +24,7 @@ import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands import { firstToUpperCase, firstToLowerCase } from '../common/utils'; import { Port } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb'; import { nls } from '@theia/core'; -import { SerialService } from './../common/protocol/serial-service'; +import { MonitorManager } from './monitor-manager'; @injectable() export class CoreServiceImpl extends CoreClientAware implements CoreService { @@ -34,8 +34,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { @inject(NotificationServiceServer) protected readonly notificationService: NotificationServiceServer; - @inject(SerialService) - protected readonly serialService: SerialService; + @inject(MonitorManager) + protected readonly monitorManager: MonitorManager; protected uploading = false; @@ -45,7 +45,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { compilerWarnings?: CompilerWarnings; } ): Promise { - const { sketchUri, fqbn, compilerWarnings } = options; + const { sketchUri, board, compilerWarnings } = options; const sketchPath = FileUri.fsPath(sketchUri); await this.coreClientProvider.initialized; @@ -55,8 +55,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { const compileReq = new CompileRequest(); compileReq.setInstance(instance); compileReq.setSketchPath(sketchPath); - if (fqbn) { - compileReq.setFqbn(fqbn); + if (board?.fqbn) { + compileReq.setFqbn(board.fqbn); } if (compilerWarnings) { compileReq.setWarnings(compilerWarnings.toLowerCase()); @@ -139,11 +139,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { await this.compile(Object.assign(options, { exportBinaries: false })); this.uploading = true; - this.serialService.uploadInProgress = true; + const { sketchUri, board, port, programmer } = options; + await this.monitorManager.notifyUploadStarted(board, port); - await this.serialService.disconnect(); - - const { sketchUri, fqbn, port, programmer } = options; const sketchPath = FileUri.fsPath(sketchUri); await this.coreClientProvider.initialized; @@ -153,8 +151,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { const req = requestProvider(); req.setInstance(instance); req.setSketchPath(sketchPath); - if (fqbn) { - req.setFqbn(fqbn); + if (board?.fqbn) { + req.setFqbn(board.fqbn); } const p = new Port(); if (port) { @@ -209,23 +207,22 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { throw new Error(errorMessage); } finally { this.uploading = false; - this.serialService.uploadInProgress = false; + this.monitorManager.notifyUploadFinished(board, port); } } async burnBootloader(options: CoreService.Bootloader.Options): Promise { this.uploading = true; - this.serialService.uploadInProgress = true; - await this.serialService.disconnect(); + const { board, port, programmer } = options; + await this.monitorManager.notifyUploadStarted(board, port); await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { client, instance } = coreClient; - const { fqbn, port, programmer } = options; const burnReq = new BurnBootloaderRequest(); burnReq.setInstance(instance); - if (fqbn) { - burnReq.setFqbn(fqbn); + if (board?.fqbn) { + burnReq.setFqbn(board.fqbn); } const p = new Port(); if (port) { @@ -267,7 +264,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { throw new Error(errorMessage); } finally { this.uploading = false; - this.serialService.uploadInProgress = false; + await this.monitorManager.notifyUploadFinished(board, port); } } From 480492a7c8ad150d7be10be757d69741b913a240 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 4 Mar 2022 18:03:28 +0100 Subject: [PATCH 07/66] Enhance MonitorManager APIs --- .../monitor-manager-proxy-client-impl.ts | 7 + .../src/common/monitor-manager-proxy.ts | 12 -- .../src/node/monitor-manager-proxy-impl.ts | 45 ++++- .../src/node/monitor-manager.ts | 189 +++++++++++++++++- 4 files changed, 235 insertions(+), 18 deletions(-) delete mode 100644 arduino-ide-extension/src/common/monitor-manager-proxy.ts diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 706027839..45956ce51 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -1,6 +1,13 @@ +import { Emitter } from "@theia/core"; import { injectable } from "@theia/core/shared/inversify"; import { MonitorManagerProxyClient } from "../common/monitor-manager-proxy"; @injectable() export class MonitorManagerProxyClientImpl implements MonitorManagerProxyClient { + protected readonly onWebSocketChangedEmitter = new Emitter(); + readonly onWebSocketChanged = this.onWebSocketChangedEmitter.event; + + notifyWebSocketChanged(message: number): void { + this.onWebSocketChangedEmitter.fire(message); + } } diff --git a/arduino-ide-extension/src/common/monitor-manager-proxy.ts b/arduino-ide-extension/src/common/monitor-manager-proxy.ts deleted file mode 100644 index ba08b361b..000000000 --- a/arduino-ide-extension/src/common/monitor-manager-proxy.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { JsonRpcServer } from "@theia/core"; - -export const MonitorManagerProxyPath = '/services/monitor-manager-proxy'; -export const MonitorManagerProxy = Symbol('MonitorManagerProxy'); -export interface MonitorManagerProxy extends JsonRpcServer { - -} - -export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); -export interface MonitorManagerProxyClient { - -} diff --git a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts index 814c7bc3d..d582ea09a 100644 --- a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts +++ b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts @@ -1,20 +1,57 @@ -import { inject, injectable } from "@theia/core/shared/inversify"; -import { MonitorManagerProxy, MonitorManagerProxyClient } from "../common/monitor-manager-proxy"; +import { Emitter, ILogger } from "@theia/core"; +import { inject, injectable, named } from "@theia/core/shared/inversify"; +import { Disposable } from "@theia/core/shared/vscode-languageserver-protocol"; +import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorSettings, Status } from "../common/protocol"; +import { Board, Port } from "../common/protocol"; import { MonitorManager } from "./monitor-manager"; @injectable() export class MonitorManagerProxyImpl implements MonitorManagerProxy { + protected client: MonitorManagerProxyClient; + + protected selectedBoard: Board | null; + protected selectedPort: Port | null; + constructor( + @inject(ILogger) + @named("monitor-manager-proxy") + protected readonly logger: ILogger, + @inject(MonitorManager) protected readonly manager: MonitorManager, ) { } dispose(): void { - // TODO + // NOOP + } + + + // setMonitorConfig is called by the FE when trying to establish a monitor connection to a board or when changing some + // settings (such as the baudrate, when available) + async setMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise { + + // check if it's a different connection or a change in the settings + if (board === this.selectedBoard && port === this.selectedPort) { + + // TODO: update the settings + return; + } + + const startStatus: Status = await this.manager.startMonitor(board, port); + + if (startStatus === Status.ALREADY_CONNECTED || startStatus === Status.OK) { + this.client.notifyWebSocketChanged(this.manager.getWebsocketAddress(board, port)); + } + } setClient(client: MonitorManagerProxyClient | undefined): void { - // TODO + if (!client) { + return; + } + this.client = client; + } + } \ No newline at end of file diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 1814c94af..62434bdcd 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -1,6 +1,191 @@ -import { injectable } from "@theia/core/shared/inversify"; +import { Emitter, ILogger } from "@theia/core"; +import { inject, injectable, named } from "@theia/core/shared/inversify"; +import { Board, Port, Status, MonitorSetting, MonitorSettings } from "../common/protocol"; +import { EnumerateMonitorPortSettingsRequest, EnumerateMonitorPortSettingsResponse } from "./cli-protocol/cc/arduino/cli/commands/v1/monitor_pb"; +import { CoreClientAware } from "./core-client-provider"; +import { MonitorService } from "./monitor-service"; + +type MonitorID = string; @injectable() -export class MonitorManager { +export class MonitorManager extends CoreClientAware { + // Map of monitor services that manage the running pluggable monitors. + // Each service handles the lifetime of one, and only one, monitor. + // If either the board or port managed changes a new service must + // be started. + private monitorServices = new Map(); + + // Used to notify a monitor service that an upload process started + // to the board/port combination it manages + protected readonly onUploadStartedEmitter = new Emitter<{ board: Board, port: Port }>(); + readonly onUploadStarted = this.onUploadStartedEmitter.event; + + // Used to notify a monitor service that an upload process finished + // to the board/port combination it manages + + + + constructor( + @inject(ILogger) + @named('monitor-manager') + protected readonly logger: ILogger, + ) { + super(); + } + + /** + * Returns the possible configurations used to connect a monitor + * to the board specified by fqbn using the specified protocol + * @param protocol the protocol of the monitor we want get settings for + * @param fqbn the fqbn of the board we want to monitor + * @returns a map of all the settings supported by the monitor + */ + async portMonitorSettings(protocol: string, fqbn: string): Promise { + const coreClient = await this.coreClient(); + const { client, instance } = coreClient; + const req = new EnumerateMonitorPortSettingsRequest(); + req.setInstance(instance); + req.setPortProtocol(protocol); + req.setFqbn(fqbn); + + const res = await new Promise((resolve, reject) => { + client.enumerateMonitorPortSettings(req, (err, resp) => { + if (!!err) { + reject(err) + } + resolve(resp) + }) + }) + + let settings: MonitorSettings = {}; + for (const iterator of res.getSettingsList()) { + settings[iterator.getSettingId()] = { + 'id': iterator.getSettingId(), + 'label': iterator.getLabel(), + 'type': iterator.getType(), + 'values': iterator.getEnumValuesList(), + 'selectedValue': iterator.getValue(), + } + } + return settings; + } + + /** + * + * @param board + * @param port + */ + async startMonitor(board: Board, port: Port): Promise { + const monitorID = this.monitorID(board, port); + let monitor = this.monitorServices.get(monitorID); + if (!monitor) { + monitor = this.createMonitor(board, port) + } + return await monitor.start(); + // TODO: I need to return the address here right? + } + + async stopMonitor(board: Board, port: Port): Promise { + const monitorID = this.monitorID(board, port); + + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + // There's no monitor to stop, bail + return; + } + return await monitor.stop(); + } + + getWebsocketAddress(board: Board, port: Port): number { + const monitorID = this.monitorID(board, port); + + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + return -1; + } + return monitor.getWebsocketAddress(); + } + + /** + * Notifies the monitor service of that board/port combination + * that an upload process started on that exact board/port combination. + * This must be done so that we can stop the monitor for the time being + * until the upload process finished. + * @param board + * @param port + */ + async notifyUploadStarted(board?: Board, port?: Port): Promise { + if (!board || !port) { + // We have no way of knowing which monitor + // to retrieve if we don't have this information. + return; + } + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + // There's no monitor running there, bail + return; + } + return await monitor.pause(); + } + + /** + * Notifies the monitor service of that board/port combination + * that an upload process started on that exact board/port combination. + * @param board + * @param port + * @returns + */ + async notifyUploadFinished(board?: Board, port?: Port): Promise { + if (!board || !port) { + // We have no way of knowing which monitor + // to retrieve if we don't have this information. + return Status.NOT_CONNECTED; + } + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + // There's no monitor running there, bail + return Status.NOT_CONNECTED; + } + return await monitor.start(); + } + + /** + * + * @param board + * @param port + * @param settings map of monitor settings to change + */ + changeMonitorSettings(board: Board, port: Port, settings: Record) { + const monitorID = this.monitorID(board, port); + let monitor = this.monitorServices.get(monitorID); + if (!monitor) { + monitor = this.createMonitor(board, port) + monitor.changeSettings(settings); + } + } + + private createMonitor(board: Board, port: Port): MonitorService { + const monitorID = this.monitorID(board, port); + const monitor = new MonitorService( + this.logger, + board, + port + ); + monitor.onDispose((() => { + this.monitorServices.delete(monitorID); + }).bind(this)); + return monitor + } + /** + * Utility function to create a unique ID for a monitor service. + * @param board + * @param port + * @returns a unique monitor ID + */ + private monitorID(board: Board, port: Port): MonitorID { + return `${board.fqbn}-${port.address}-${port.protocol}`; + } } \ No newline at end of file From c5695d3a76d1efb4b2ad3fd52cb9d5e11f4c6b3f Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Mon, 7 Mar 2022 10:29:08 +0100 Subject: [PATCH 08/66] Fixed WebSocketChange event signature --- .../src/browser/monitor-manager-proxy-client-impl.ts | 2 +- arduino-ide-extension/src/common/protocol/monitor-service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 45956ce51..76425a4e7 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -1,6 +1,6 @@ import { Emitter } from "@theia/core"; import { injectable } from "@theia/core/shared/inversify"; -import { MonitorManagerProxyClient } from "../common/monitor-manager-proxy"; +import { MonitorManagerProxyClient } from "../common/protocol/monitor-service"; @injectable() export class MonitorManagerProxyClientImpl implements MonitorManagerProxyClient { diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index d3d5c1edc..0847068c3 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -10,8 +10,8 @@ export interface MonitorManagerProxy extends JsonRpcServer; - notifyWebSocketChanged(message: string): void; + onWebSocketChanged: Event; + notifyWebSocketChanged(message: number): void; } export interface MonitorSetting { From 61b8bdeec9323de581cca8f3fe0642a3d2af7214 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Mon, 7 Mar 2022 14:45:48 +0100 Subject: [PATCH 09/66] Add monitor proxy functions for the frontend --- .../src/common/protocol/monitor-service.ts | 6 +- .../src/node/monitor-manager-proxy-impl.ts | 68 ++++++++++----- .../src/node/monitor-manager.ts | 84 ++++++++++++------- .../src/node/monitor-service.ts | 2 +- 4 files changed, 109 insertions(+), 51 deletions(-) diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 0847068c3..11217ca57 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -4,8 +4,10 @@ import { Board, Port } from './boards-service'; export const MonitorManagerProxyPath = '/services/monitor-manager-proxy'; export const MonitorManagerProxy = Symbol('MonitorManagerProxy'); export interface MonitorManagerProxy extends JsonRpcServer { - //set the monitor settings, which includes address, port and other monitor-specific settings - setMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise; + startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise; + changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise; + stopMonitor(board: Board, port: Port): Promise; + getSupportedSettings(protocol: string, fqbn: string): Promise; } export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); diff --git a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts index d582ea09a..2291228eb 100644 --- a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts +++ b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts @@ -1,6 +1,5 @@ -import { Emitter, ILogger } from "@theia/core"; +import { ILogger } from "@theia/core"; import { inject, injectable, named } from "@theia/core/shared/inversify"; -import { Disposable } from "@theia/core/shared/vscode-languageserver-protocol"; import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorSettings, Status } from "../common/protocol"; import { Board, Port } from "../common/protocol"; import { MonitorManager } from "./monitor-manager"; @@ -9,9 +8,6 @@ import { MonitorManager } from "./monitor-manager"; export class MonitorManagerProxyImpl implements MonitorManagerProxy { protected client: MonitorManagerProxyClient; - protected selectedBoard: Board | null; - protected selectedPort: Port | null; - constructor( @inject(ILogger) @named("monitor-manager-proxy") @@ -26,24 +22,58 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { // NOOP } + /** + * Start a pluggable monitor and/or change its settings. + * If settings are defined they'll be set before starting the monitor, + * otherwise default ones will be used by the monitor. + * @param board board connected to port + * @param port port to monitor + * @param settings map of supported configuration by the monitor + */ + async startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise { + if (settings) { + await this.changeMonitorSettings(board, port, settings); + } + const status = await this.manager.startMonitor(board, port); + if (status === Status.ALREADY_CONNECTED || status === Status.OK) { + this.client.notifyWebSocketChanged(this.manager.getWebsocketAddressPort(board, port)); + } + } - // setMonitorConfig is called by the FE when trying to establish a monitor connection to a board or when changing some - // settings (such as the baudrate, when available) - async setMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise { - - // check if it's a different connection or a change in the settings - if (board === this.selectedBoard && port === this.selectedPort) { - - // TODO: update the settings + /** + * Changes the settings of a running pluggable monitor, if that monitor is not + * started this function is a noop. + * @param board board connected to port + * @param port port monitored + * @param settings map of supported configuration by the monitor + */ + async changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise { + if (!this.manager.isStarted(board, port)) { + // Monitor is not running, no need to change settings return; } + return this.manager.changeMonitorSettings(board, port, settings); + } - const startStatus: Status = await this.manager.startMonitor(board, port); - - if (startStatus === Status.ALREADY_CONNECTED || startStatus === Status.OK) { - this.client.notifyWebSocketChanged(this.manager.getWebsocketAddress(board, port)); - } + /** + * Stops a running pluggable monitor. + * @param board board connected to port + * @param port port monitored + */ + async stopMonitor(board: Board, port: Port): Promise { + return this.manager.stopMonitor(board, port); + } + /** + * Returns the settings supported by the pluggable monitor for the specified + * protocol, the fqbn is necessary since it's used to tell different monitors + * using the same protocol. + * @param protocol protocol of a pluggable monitor + * @param fqbn unique ID of a board + * @returns a map of MonitorSetting + */ + async getSupportedSettings(protocol: string, fqbn: string): Promise { + return this.manager.portMonitorSettings(protocol, fqbn); } setClient(client: MonitorManagerProxyClient | undefined): void { @@ -51,7 +81,5 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { return; } this.client = client; - } - } \ No newline at end of file diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 62434bdcd..a1627d3c6 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -15,16 +15,6 @@ export class MonitorManager extends CoreClientAware { // be started. private monitorServices = new Map(); - // Used to notify a monitor service that an upload process started - // to the board/port combination it manages - protected readonly onUploadStartedEmitter = new Emitter<{ board: Board, port: Port }>(); - readonly onUploadStarted = this.onUploadStartedEmitter.event; - - // Used to notify a monitor service that an upload process finished - // to the board/port combination it manages - - - constructor( @inject(ILogger) @named('monitor-manager') @@ -55,7 +45,7 @@ export class MonitorManager extends CoreClientAware { } resolve(resp) }) - }) + }); let settings: MonitorSettings = {}; for (const iterator of res.getSettingsList()) { @@ -71,9 +61,28 @@ export class MonitorManager extends CoreClientAware { } /** - * - * @param board - * @param port + * Used to know if a monitor is started + * @param board board connected to port + * @param port port to monitor + * @returns true if the monitor is currently monitoring the board/port + * combination specifed, false in all other cases. + */ + isStarted(board: Board, port: Port): boolean { + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (monitor) { + return monitor.isStarted(); + } + return false; + } + + /** + * Start a pluggable monitor that receives and sends messages + * to the specified board and port combination. + * @param board board connected to port + * @param port port to monitor + * @returns a Status object to know if the process has been + * started or if there have been errors. */ async startMonitor(board: Board, port: Port): Promise { const monitorID = this.monitorID(board, port); @@ -82,12 +91,16 @@ export class MonitorManager extends CoreClientAware { monitor = this.createMonitor(board, port) } return await monitor.start(); - // TODO: I need to return the address here right? } + /** + * Stop a pluggable monitor connected to the specified board/port + * combination. It's a noop if monitor is not running. + * @param board board connected to port + * @param port port monitored + */ async stopMonitor(board: Board, port: Port): Promise { const monitorID = this.monitorID(board, port); - const monitor = this.monitorServices.get(monitorID); if (!monitor) { // There's no monitor to stop, bail @@ -96,14 +109,20 @@ export class MonitorManager extends CoreClientAware { return await monitor.stop(); } - getWebsocketAddress(board: Board, port: Port): number { + /** + * Returns the port of the WebSocket used by the MonitorService + * that is handling the board/port combination + * @param board board connected to port + * @param port port to monitor + * @returns port of the MonitorService's WebSocket + */ + getWebsocketAddressPort(board: Board, port: Port): number { const monitorID = this.monitorID(board, port); - const monitor = this.monitorServices.get(monitorID); if (!monitor) { return -1; } - return monitor.getWebsocketAddress(); + return monitor.getWebsocketAddressPort(); } /** @@ -111,8 +130,8 @@ export class MonitorManager extends CoreClientAware { * that an upload process started on that exact board/port combination. * This must be done so that we can stop the monitor for the time being * until the upload process finished. - * @param board - * @param port + * @param board board connected to port + * @param port port to monitor */ async notifyUploadStarted(board?: Board, port?: Port): Promise { if (!board || !port) { @@ -132,9 +151,10 @@ export class MonitorManager extends CoreClientAware { /** * Notifies the monitor service of that board/port combination * that an upload process started on that exact board/port combination. - * @param board - * @param port - * @returns + * @param board board connected to port + * @param port port to monitor + * @returns a Status object to know if the process has been + * started or if there have been errors. */ async notifyUploadFinished(board?: Board, port?: Port): Promise { if (!board || !port) { @@ -152,10 +172,11 @@ export class MonitorManager extends CoreClientAware { } /** - * - * @param board - * @param port - * @param settings map of monitor settings to change + * Changes the settings of a pluggable monitor even if it's running. + * If monitor is not running they're going to be used as soon as it's started. + * @param board board connected to port + * @param port port to monitor + * @param settings monitor settings to change */ changeMonitorSettings(board: Board, port: Port, settings: Record) { const monitorID = this.monitorID(board, port); @@ -166,6 +187,13 @@ export class MonitorManager extends CoreClientAware { } } + /** + * Creates a MonitorService that handles the lifetime and the + * communication via WebSocket with the frontend. + * @param board board connected to specified port + * @param port port to monitor + * @returns a new instance of MonitorService ready to use. + */ private createMonitor(board: Board, port: Port): MonitorService { const monitorID = this.monitorID(board, port); const monitor = new MonitorService( diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index cbb408f0f..a5459cf82 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -57,7 +57,7 @@ export class MonitorService extends CoreClientAware implements Disposable { }); } - getWebsocketAddress(): number { + getWebsocketAddressPort(): number { return this.webSocketProvider.getAddress().port; } From 31b704cdb9f8450ad90ed65eafe280d638f94254 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 8 Mar 2022 17:14:31 +0100 Subject: [PATCH 10/66] Moved settings to MonitorService --- .../src/node/monitor-manager.ts | 60 ++++++------------- .../src/node/monitor-service.ts | 54 ++++++++++++++++- 2 files changed, 71 insertions(+), 43 deletions(-) diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index a1627d3c6..1f38bac8c 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -1,7 +1,6 @@ -import { Emitter, ILogger } from "@theia/core"; +import { ILogger } from "@theia/core"; import { inject, injectable, named } from "@theia/core/shared/inversify"; -import { Board, Port, Status, MonitorSetting, MonitorSettings } from "../common/protocol"; -import { EnumerateMonitorPortSettingsRequest, EnumerateMonitorPortSettingsResponse } from "./cli-protocol/cc/arduino/cli/commands/v1/monitor_pb"; +import { Board, Port, Status, MonitorSettings } from "../common/protocol"; import { CoreClientAware } from "./core-client-provider"; import { MonitorService } from "./monitor-service"; @@ -23,43 +22,6 @@ export class MonitorManager extends CoreClientAware { super(); } - /** - * Returns the possible configurations used to connect a monitor - * to the board specified by fqbn using the specified protocol - * @param protocol the protocol of the monitor we want get settings for - * @param fqbn the fqbn of the board we want to monitor - * @returns a map of all the settings supported by the monitor - */ - async portMonitorSettings(protocol: string, fqbn: string): Promise { - const coreClient = await this.coreClient(); - const { client, instance } = coreClient; - const req = new EnumerateMonitorPortSettingsRequest(); - req.setInstance(instance); - req.setPortProtocol(protocol); - req.setFqbn(fqbn); - - const res = await new Promise((resolve, reject) => { - client.enumerateMonitorPortSettings(req, (err, resp) => { - if (!!err) { - reject(err) - } - resolve(resp) - }) - }); - - let settings: MonitorSettings = {}; - for (const iterator of res.getSettingsList()) { - settings[iterator.getSettingId()] = { - 'id': iterator.getSettingId(), - 'label': iterator.getLabel(), - 'type': iterator.getType(), - 'values': iterator.getEnumValuesList(), - 'selectedValue': iterator.getValue(), - } - } - return settings; - } - /** * Used to know if a monitor is started * @param board board connected to port @@ -178,7 +140,7 @@ export class MonitorManager extends CoreClientAware { * @param port port to monitor * @param settings monitor settings to change */ - changeMonitorSettings(board: Board, port: Port, settings: Record) { + changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings) { const monitorID = this.monitorID(board, port); let monitor = this.monitorServices.get(monitorID); if (!monitor) { @@ -187,6 +149,22 @@ export class MonitorManager extends CoreClientAware { } } + /** + * Returns the settings currently used by the pluggable monitor + * that's communicating with the specified board/port combination. + * @param board board connected to port + * @param port port monitored + * @returns map of current monitor settings + */ + currentMonitorSettings(board: Board, port: Port): MonitorSettings { + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + return {}; + } + return monitor.currentSettings(); + } + /** * Creates a MonitorService that handles the lifetime and the * communication via WebSocket with the frontend. diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index a5459cf82..f843565b1 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -1,8 +1,8 @@ import { ClientDuplexStream } from "@grpc/grpc-js"; import { Disposable, Emitter, ILogger } from "@theia/core"; import { inject, named } from "@theia/core/shared/inversify"; -import { Board, Port, Status, MonitorSettings } from "../common/protocol"; -import { MonitorPortConfiguration, MonitorPortSetting, MonitorRequest, MonitorResponse } from "./cli-protocol/cc/arduino/cli/commands/v1/monitor_pb"; +import { Board, Port, Status, MonitorSettings, Monitor } from "../common/protocol"; +import { EnumerateMonitorPortSettingsRequest, EnumerateMonitorPortSettingsResponse, MonitorPortConfiguration, MonitorPortSetting, MonitorRequest, MonitorResponse } from "./cli-protocol/cc/arduino/cli/commands/v1/monitor_pb"; import { CoreClientAware } from "./core-client-provider"; import { WebSocketProvider } from "./web-socket/web-socket-provider"; import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb' @@ -55,6 +55,11 @@ export class MonitorService extends CoreClientAware implements Disposable { this.dispose(); } }); + + // Sets default settings for this monitor + this.portMonitorSettings(port.protocol, board.fqbn!).then( + settings => this.settings = settings + ); } getWebsocketAddressPort(): number { @@ -222,6 +227,51 @@ export class MonitorService extends CoreClientAware implements Disposable { }) } + /** + * + * @returns map of current monitor settings + */ + currentSettings(): MonitorSettings { + return this.settings; + } + + /** + * Returns the possible configurations used to connect a monitor + * to the board specified by fqbn using the specified protocol + * @param protocol the protocol of the monitor we want get settings for + * @param fqbn the fqbn of the board we want to monitor + * @returns a map of all the settings supported by the monitor + */ + private async portMonitorSettings(protocol: string, fqbn: string): Promise { + const coreClient = await this.coreClient(); + const { client, instance } = coreClient; + const req = new EnumerateMonitorPortSettingsRequest(); + req.setInstance(instance); + req.setPortProtocol(protocol); + req.setFqbn(fqbn); + + const res = await new Promise((resolve, reject) => { + client.enumerateMonitorPortSettings(req, (err, resp) => { + if (!!err) { + reject(err) + } + resolve(resp) + }) + }); + + let settings: MonitorSettings = {}; + for (const iterator of res.getSettingsList()) { + settings[iterator.getSettingId()] = { + 'id': iterator.getSettingId(), + 'label': iterator.getLabel(), + 'type': iterator.getType(), + 'values': iterator.getEnumValuesList(), + 'selectedValue': iterator.getValue(), + } + } + return settings; + } + /** * Set monitor settings, if there is a running monitor they'll be sent * to it, otherwise they'll be used when starting one. From 9058abb01597c963aab3897b2f963e2c936286f2 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Mar 2022 15:48:55 +0100 Subject: [PATCH 11/66] Remove several unnecessary serial monitor classes --- .../serial/serial-connection-manager.ts | 360 ---------------- .../src/browser/serial/serial-model.ts | 163 ------- .../serial/serial-service-client-impl.ts | 48 --- .../src/common/protocol/serial-service.ts | 102 ----- .../node/serial/monitor-client-provider.ts | 26 -- .../src/node/serial/serial-service-impl.ts | 397 ------------------ .../src/test/node/serial-service-impl.test.ts | 167 -------- 7 files changed, 1263 deletions(-) delete mode 100644 arduino-ide-extension/src/browser/serial/serial-connection-manager.ts delete mode 100644 arduino-ide-extension/src/browser/serial/serial-model.ts delete mode 100644 arduino-ide-extension/src/browser/serial/serial-service-client-impl.ts delete mode 100644 arduino-ide-extension/src/common/protocol/serial-service.ts delete mode 100644 arduino-ide-extension/src/node/serial/monitor-client-provider.ts delete mode 100644 arduino-ide-extension/src/node/serial/serial-service-impl.ts delete mode 100644 arduino-ide-extension/src/test/node/serial-service-impl.test.ts diff --git a/arduino-ide-extension/src/browser/serial/serial-connection-manager.ts b/arduino-ide-extension/src/browser/serial/serial-connection-manager.ts deleted file mode 100644 index e3fb2476e..000000000 --- a/arduino-ide-extension/src/browser/serial/serial-connection-manager.ts +++ /dev/null @@ -1,360 +0,0 @@ -import { injectable, inject } from 'inversify'; -import { Emitter, Event } from '@theia/core/lib/common/event'; -import { MessageService } from '@theia/core/lib/common/message-service'; -import { - SerialService, - SerialConfig, - SerialError, - Status, - SerialServiceClient, -} from '../../common/protocol/serial-service'; -import { BoardsServiceProvider } from '../boards/boards-service-provider'; -import { - Board, - BoardsService, -} from '../../common/protocol/boards-service'; -import { BoardsConfig } from '../boards/boards-config'; -import { SerialModel } from './serial-model'; -import { ThemeService } from '@theia/core/lib/browser/theming'; -import { CoreService } from '../../common/protocol'; -import { nls } from '@theia/core/lib/common/nls'; - -@injectable() -export class SerialConnectionManager { - protected config: Partial = { - board: undefined, - port: undefined, - baudRate: undefined, - }; - - protected readonly onConnectionChangedEmitter = new Emitter(); - - /** - * This emitter forwards all read events **if** the connection is established. - */ - protected readonly onReadEmitter = new Emitter<{ messages: string[] }>(); - - /** - * Array for storing previous serial errors received from the server, and based on the number of elements in this array, - * we adjust the reconnection delay. - * Super naive way: we wait `array.length * 1000` ms. Once we hit 10 errors, we do not try to reconnect and clean the array. - */ - protected serialErrors: SerialError[] = []; - protected reconnectTimeout?: number; - - /** - * When the websocket server is up on the backend, we save the port here, so that the client knows how to connect to it - * */ - protected wsPort?: number; - protected webSocket?: WebSocket; - - constructor( - @inject(SerialModel) protected readonly serialModel: SerialModel, - @inject(SerialService) protected readonly serialService: SerialService, - @inject(SerialServiceClient) - protected readonly serialServiceClient: SerialServiceClient, - @inject(BoardsService) protected readonly boardsService: BoardsService, - @inject(BoardsServiceProvider) - protected readonly boardsServiceProvider: BoardsServiceProvider, - @inject(MessageService) protected messageService: MessageService, - @inject(ThemeService) protected readonly themeService: ThemeService, - @inject(CoreService) protected readonly core: CoreService, - @inject(BoardsServiceProvider) - protected readonly boardsServiceClientImpl: BoardsServiceProvider - ) { - this.serialServiceClient.onWebSocketChanged( - this.handleWebSocketChanged.bind(this) - ); - this.serialServiceClient.onBaudRateChanged((baudRate) => { - if (this.serialModel.baudRate !== baudRate) { - this.serialModel.baudRate = baudRate; - } - }); - this.serialServiceClient.onLineEndingChanged((lineending) => { - if (this.serialModel.lineEnding !== lineending) { - this.serialModel.lineEnding = lineending; - } - }); - this.serialServiceClient.onInterpolateChanged((interpolate) => { - if (this.serialModel.interpolate !== interpolate) { - this.serialModel.interpolate = interpolate; - } - }); - - this.serialServiceClient.onError(this.handleError.bind(this)); - this.boardsServiceProvider.onBoardsConfigChanged( - this.handleBoardConfigChange.bind(this) - ); - - // Handles the `baudRate` changes by reconnecting if required. - this.serialModel.onChange(async ({ property }) => { - if ( - property === 'baudRate' && - (await this.serialService.isSerialPortOpen()) - ) { - const { boardsConfig } = this.boardsServiceProvider; - this.handleBoardConfigChange(boardsConfig); - } - - // update the current values in the backend and propagate to websocket clients - this.serialService.updateWsConfigParam({ - ...(property === 'lineEnding' && { - currentLineEnding: this.serialModel.lineEnding, - }), - ...(property === 'interpolate' && { - interpolate: this.serialModel.interpolate, - }), - }); - }); - - this.themeService.onDidColorThemeChange((theme) => { - this.serialService.updateWsConfigParam({ - darkTheme: theme.newTheme.type === 'dark', - }); - }); - } - - /** - * Updated the config in the BE passing only the properties that has changed. - * BE will create a new connection if needed. - * - * @param newConfig the porperties of the config that has changed - */ - async setConfig(newConfig: Partial): Promise { - let configHasChanged = false; - Object.keys(this.config).forEach((key: keyof SerialConfig) => { - if (newConfig[key] !== this.config[key]) { - configHasChanged = true; - this.config = { ...this.config, [key]: newConfig[key] }; - } - }); - - if (configHasChanged) { - this.serialService.updateWsConfigParam({ - currentBaudrate: this.config.baudRate, - serialPort: this.config.port?.address, - }); - - if (isSerialConfig(this.config)) { - this.serialService.setSerialConfig(this.config); - } - } - } - - getConfig(): Partial { - return this.config; - } - - getWsPort(): number | undefined { - return this.wsPort; - } - - protected handleWebSocketChanged(wsPort: number): void { - this.wsPort = wsPort; - } - - get serialConfig(): SerialConfig | undefined { - return isSerialConfig(this.config) - ? (this.config as SerialConfig) - : undefined; - } - - async isBESerialConnected(): Promise { - return await this.serialService.isSerialPortOpen(); - } - - openWSToBE(): void { - if (!isSerialConfig(this.config)) { - this.messageService.error( - `Please select a board and a port to open the serial connection.` - ); - } - - if (!this.webSocket && this.wsPort) { - try { - this.webSocket = new WebSocket(`ws://localhost:${this.wsPort}`); - this.webSocket.onmessage = (res) => { - const messages = JSON.parse(res.data); - this.onReadEmitter.fire({ messages }); - }; - } catch { - this.messageService.error(`Unable to connect to websocket`); - } - } - } - - closeWStoBE(): void { - if (this.webSocket) { - try { - this.webSocket.close(); - this.webSocket = undefined; - } catch { - this.messageService.error(`Unable to close websocket`); - } - } - } - - /** - * Handles error on the SerialServiceClient and try to reconnect, eventually - */ - async handleError(error: SerialError): Promise { - if (!(await this.serialService.isSerialPortOpen())) return; - const { code, config } = error; - const { board, port } = config; - const options = { timeout: 3000 }; - switch (code) { - case SerialError.ErrorCodes.CLIENT_CANCEL: { - console.debug( - `Serial connection was canceled by client: ${Serial.Config.toString( - this.config - )}.` - ); - break; - } - case SerialError.ErrorCodes.DEVICE_BUSY: { - this.messageService.warn( - nls.localize( - 'arduino/serial/connectionBusy', - 'Connection failed. Serial port is busy: {0}', - port.address - ), - options - ); - this.serialErrors.push(error); - break; - } - case SerialError.ErrorCodes.DEVICE_NOT_CONFIGURED: { - this.messageService.info( - nls.localize( - 'arduino/serial/disconnected', - 'Disconnected {0} from {1}.', - Board.toString(board, { - useFqbn: false, - }), - port.address - ), - options - ); - break; - } - case undefined: { - this.messageService.error( - nls.localize( - 'arduino/serial/unexpectedError', - 'Unexpected error. Reconnecting {0} on port {1}.', - Board.toString(board), - port.address - ), - options - ); - console.error(JSON.stringify(error)); - break; - } - } - - if ((await this.serialService.clientsAttached()) > 0) { - if (this.serialErrors.length >= 10) { - this.messageService.warn( - nls.localize( - 'arduino/serial/failedReconnect', - 'Failed to reconnect {0} to serial port after 10 consecutive attempts. The {1} serial port is busy.', - Board.toString(board, { - useFqbn: false, - }), - port.address - ) - ); - this.serialErrors.length = 0; - } else { - const attempts = this.serialErrors.length || 1; - if (this.reconnectTimeout !== undefined) { - // Clear the previous timer. - window.clearTimeout(this.reconnectTimeout); - } - const timeout = attempts * 1000; - this.messageService.warn( - nls.localize( - 'arduino/serial/reconnect', - 'Reconnecting {0} to {1} in {2} seconds...', - Board.toString(board, { - useFqbn: false, - }), - port.address, - attempts.toString() - ) - ); - this.reconnectTimeout = window.setTimeout( - () => this.reconnectAfterUpload(), - timeout - ); - } - } - } - - async reconnectAfterUpload(): Promise { - try { - if (isSerialConfig(this.config)) { - await this.boardsServiceClientImpl.waitUntilAvailable( - Object.assign(this.config.board, { port: this.config.port }), - 10_000 - ); - this.serialService.connectSerialIfRequired(); - } - } catch (waitError) { - this.messageService.error( - nls.localize( - 'arduino/sketch/couldNotConnectToSerial', - 'Could not reconnect to serial port. {0}', - waitError.toString() - ) - ); - } - } - - /** - * Sends the data to the connected serial port. - * The desired EOL is appended to `data`, you do not have to add it. - * It is a NOOP if connected. - */ - async send(data: string): Promise { - if (!(await this.serialService.isSerialPortOpen())) { - return Status.NOT_CONNECTED; - } - return new Promise((resolve) => { - this.serialService - .sendMessageToSerial(data + this.serialModel.lineEnding) - .then(() => resolve(Status.OK)); - }); - } - - get onConnectionChanged(): Event { - return this.onConnectionChangedEmitter.event; - } - - get onRead(): Event<{ messages: any }> { - return this.onReadEmitter.event; - } - - protected async handleBoardConfigChange( - boardsConfig: BoardsConfig.Config - ): Promise { - const { selectedBoard: board, selectedPort: port } = boardsConfig; - const { baudRate } = this.serialModel; - const newConfig: Partial = { board, port, baudRate }; - this.setConfig(newConfig); - } -} - -export namespace Serial { - export namespace Config { - export function toString(config: Partial): string { - if (!isSerialConfig(config)) return ''; - const { board, port } = config; - return `${Board.toString(board)} ${port.address}`; - } - } -} - -function isSerialConfig(config: Partial): config is SerialConfig { - return !!config.board && !!config.baudRate && !!config.port; -} diff --git a/arduino-ide-extension/src/browser/serial/serial-model.ts b/arduino-ide-extension/src/browser/serial/serial-model.ts deleted file mode 100644 index fc6e352ec..000000000 --- a/arduino-ide-extension/src/browser/serial/serial-model.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { injectable, inject } from 'inversify'; -import { Emitter, Event } from '@theia/core/lib/common/event'; -import { SerialConfig } from '../../common/protocol'; -import { - FrontendApplicationContribution, - LocalStorageService, -} from '@theia/core/lib/browser'; -import { BoardsServiceProvider } from '../boards/boards-service-provider'; - -@injectable() -export class SerialModel implements FrontendApplicationContribution { - protected static STORAGE_ID = 'arduino-serial-model'; - - @inject(LocalStorageService) - protected readonly localStorageService: LocalStorageService; - - @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; - - protected readonly onChangeEmitter: Emitter< - SerialModel.State.Change - >; - protected _autoscroll: boolean; - protected _timestamp: boolean; - protected _baudRate: SerialConfig.BaudRate; - protected _lineEnding: SerialModel.EOL; - protected _interpolate: boolean; - - constructor() { - this._autoscroll = true; - this._timestamp = false; - this._baudRate = SerialConfig.BaudRate.DEFAULT; - this._lineEnding = SerialModel.EOL.DEFAULT; - this._interpolate = false; - this.onChangeEmitter = new Emitter< - SerialModel.State.Change - >(); - } - - onStart(): void { - this.localStorageService - .getData(SerialModel.STORAGE_ID) - .then((state) => { - if (state) { - this.restoreState(state); - } - }); - } - - get onChange(): Event> { - return this.onChangeEmitter.event; - } - - get autoscroll(): boolean { - return this._autoscroll; - } - - toggleAutoscroll(): void { - this._autoscroll = !this._autoscroll; - this.storeState(); - this.storeState().then(() => - this.onChangeEmitter.fire({ - property: 'autoscroll', - value: this._autoscroll, - }) - ); - } - - get timestamp(): boolean { - return this._timestamp; - } - - toggleTimestamp(): void { - this._timestamp = !this._timestamp; - this.storeState().then(() => - this.onChangeEmitter.fire({ - property: 'timestamp', - value: this._timestamp, - }) - ); - } - - get baudRate(): SerialConfig.BaudRate { - return this._baudRate; - } - - set baudRate(baudRate: SerialConfig.BaudRate) { - this._baudRate = baudRate; - this.storeState().then(() => - this.onChangeEmitter.fire({ - property: 'baudRate', - value: this._baudRate, - }) - ); - } - - get lineEnding(): SerialModel.EOL { - return this._lineEnding; - } - - set lineEnding(lineEnding: SerialModel.EOL) { - this._lineEnding = lineEnding; - this.storeState().then(() => - this.onChangeEmitter.fire({ - property: 'lineEnding', - value: this._lineEnding, - }) - ); - } - - get interpolate(): boolean { - return this._interpolate; - } - - set interpolate(i: boolean) { - this._interpolate = i; - this.storeState().then(() => - this.onChangeEmitter.fire({ - property: 'interpolate', - value: this._interpolate, - }) - ); - } - - protected restoreState(state: SerialModel.State): void { - this._autoscroll = state.autoscroll; - this._timestamp = state.timestamp; - this._baudRate = state.baudRate; - this._lineEnding = state.lineEnding; - this._interpolate = state.interpolate; - } - - protected async storeState(): Promise { - return this.localStorageService.setData(SerialModel.STORAGE_ID, { - autoscroll: this._autoscroll, - timestamp: this._timestamp, - baudRate: this._baudRate, - lineEnding: this._lineEnding, - interpolate: this._interpolate, - }); - } -} - -export namespace SerialModel { - export interface State { - autoscroll: boolean; - timestamp: boolean; - baudRate: SerialConfig.BaudRate; - lineEnding: EOL; - interpolate: boolean; - } - export namespace State { - export interface Change { - readonly property: K; - readonly value: State[K]; - } - } - - export type EOL = '' | '\n' | '\r' | '\r\n'; - export namespace EOL { - export const DEFAULT: EOL = '\n'; - } -} diff --git a/arduino-ide-extension/src/browser/serial/serial-service-client-impl.ts b/arduino-ide-extension/src/browser/serial/serial-service-client-impl.ts deleted file mode 100644 index 5a025fcf5..000000000 --- a/arduino-ide-extension/src/browser/serial/serial-service-client-impl.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { injectable } from 'inversify'; -import { Emitter } from '@theia/core/lib/common/event'; -import { - SerialServiceClient, - SerialError, - SerialConfig, -} from '../../common/protocol/serial-service'; -import { SerialModel } from './serial-model'; - -@injectable() -export class SerialServiceClientImpl implements SerialServiceClient { - protected readonly onErrorEmitter = new Emitter(); - readonly onError = this.onErrorEmitter.event; - - protected readonly onWebSocketChangedEmitter = new Emitter(); - readonly onWebSocketChanged = this.onWebSocketChangedEmitter.event; - - protected readonly onBaudRateChangedEmitter = - new Emitter(); - readonly onBaudRateChanged = this.onBaudRateChangedEmitter.event; - - protected readonly onLineEndingChangedEmitter = - new Emitter(); - readonly onLineEndingChanged = this.onLineEndingChangedEmitter.event; - - protected readonly onInterpolateChangedEmitter = new Emitter(); - readonly onInterpolateChanged = this.onInterpolateChangedEmitter.event; - - notifyError(error: SerialError): void { - this.onErrorEmitter.fire(error); - } - - notifyWebSocketChanged(message: number): void { - this.onWebSocketChangedEmitter.fire(message); - } - - notifyBaudRateChanged(message: SerialConfig.BaudRate): void { - this.onBaudRateChangedEmitter.fire(message); - } - - notifyLineEndingChanged(message: SerialModel.EOL): void { - this.onLineEndingChangedEmitter.fire(message); - } - - notifyInterpolateChanged(message: boolean): void { - this.onInterpolateChangedEmitter.fire(message); - } -} diff --git a/arduino-ide-extension/src/common/protocol/serial-service.ts b/arduino-ide-extension/src/common/protocol/serial-service.ts deleted file mode 100644 index 0e77bb9cc..000000000 --- a/arduino-ide-extension/src/common/protocol/serial-service.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; -import { Board, Port } from './boards-service'; -import { Event } from '@theia/core/lib/common/event'; -import { SerialPlotter } from '../../browser/serial/plotter/protocol'; -import { SerialModel } from '../../browser/serial/serial-model'; - -export interface Status {} -export type OK = Status; -export interface ErrorStatus extends Status { - readonly message: string; -} -export namespace Status { - export function isOK(status: Status & { message?: string }): status is OK { - return !!status && typeof status.message !== 'string'; - } - export const OK: OK = {}; - export const NOT_CONNECTED: ErrorStatus = { message: 'Not connected.' }; - export const ALREADY_CONNECTED: ErrorStatus = { - message: 'Already connected.', - }; - export const CONFIG_MISSING: ErrorStatus = { - message: 'Serial Config missing.', - }; -} - -export const SerialServicePath = '/services/serial'; -export const SerialService = Symbol('SerialService'); -export interface SerialService extends JsonRpcServer { - clientsAttached(): Promise; - setSerialConfig(config: SerialConfig): Promise; - sendMessageToSerial(message: string): Promise; - updateWsConfigParam(config: Partial): Promise; - isSerialPortOpen(): Promise; - connectSerialIfRequired(): Promise; - disconnect(reason?: SerialError): Promise; - uploadInProgress: boolean; -} - -export interface SerialConfig { - readonly board: Board; - readonly port: Port; - /** - * Defaults to [`SERIAL`](MonitorConfig#ConnectionType#SERIAL). - */ - readonly type?: SerialConfig.ConnectionType; - /** - * Defaults to `9600`. - */ - readonly baudRate?: SerialConfig.BaudRate; -} -export namespace SerialConfig { - export const BaudRates = [ - 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, - ] as const; - export type BaudRate = typeof SerialConfig.BaudRates[number]; - export namespace BaudRate { - export const DEFAULT: BaudRate = 9600; - } - - export enum ConnectionType { - SERIAL = 0, - } -} - -export const SerialServiceClient = Symbol('SerialServiceClient'); -export interface SerialServiceClient { - onError: Event; - onWebSocketChanged: Event; - onLineEndingChanged: Event; - onBaudRateChanged: Event; - onInterpolateChanged: Event; - notifyError(event: SerialError): void; - notifyWebSocketChanged(message: number): void; - notifyLineEndingChanged(message: SerialModel.EOL): void; - notifyBaudRateChanged(message: SerialConfig.BaudRate): void; - notifyInterpolateChanged(message: boolean): void; -} - -export interface SerialError { - readonly message: string; - /** - * If no `code` is available, clients must reestablish the serial connection. - */ - readonly code: number | undefined; - readonly config: SerialConfig; -} -export namespace SerialError { - export namespace ErrorCodes { - /** - * The frontend has refreshed the browser, for instance. - */ - export const CLIENT_CANCEL = 1; - /** - * When detaching a physical device when the duplex channel is still opened. - */ - export const DEVICE_NOT_CONFIGURED = 2; - /** - * Another serial connection was opened on this port. For another electron-instance, Java IDE. - */ - export const DEVICE_BUSY = 3; - } -} diff --git a/arduino-ide-extension/src/node/serial/monitor-client-provider.ts b/arduino-ide-extension/src/node/serial/monitor-client-provider.ts deleted file mode 100644 index d73c98cfe..000000000 --- a/arduino-ide-extension/src/node/serial/monitor-client-provider.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as grpc from '@grpc/grpc-js'; -import { injectable } from 'inversify'; -import { MonitorServiceClient } from '../cli-protocol/cc/arduino/cli/monitor/v1/monitor_grpc_pb'; -import * as monitorGrpcPb from '../cli-protocol/cc/arduino/cli/monitor/v1/monitor_grpc_pb'; -import { GrpcClientProvider } from '../grpc-client-provider'; - -@injectable() -export class MonitorClientProvider extends GrpcClientProvider { - createClient(port: string | number): MonitorServiceClient { - // https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage - const MonitorServiceClient = grpc.makeClientConstructor( - // @ts-expect-error: ignore - monitorGrpcPb['cc.arduino.cli.monitor.v1.MonitorService'], - 'MonitorServiceService' - ) as any; - return new MonitorServiceClient( - `localhost:${port}`, - grpc.credentials.createInsecure(), - this.channelOptions - ); - } - - close(client: MonitorServiceClient): void { - client.close(); - } -} diff --git a/arduino-ide-extension/src/node/serial/serial-service-impl.ts b/arduino-ide-extension/src/node/serial/serial-service-impl.ts deleted file mode 100644 index 5b6475c34..000000000 --- a/arduino-ide-extension/src/node/serial/serial-service-impl.ts +++ /dev/null @@ -1,397 +0,0 @@ -import { ClientDuplexStream } from '@grpc/grpc-js'; -import { TextEncoder } from 'util'; -import { injectable, inject, named } from 'inversify'; -import { Struct } from 'google-protobuf/google/protobuf/struct_pb'; -import { ILogger } from '@theia/core/lib/common/logger'; -import { - SerialService, - SerialServiceClient, - SerialConfig, - SerialError, - Status, -} from '../../common/protocol/serial-service'; -import { - StreamingOpenRequest, - StreamingOpenResponse, - MonitorConfig as GrpcMonitorConfig, -} from '../cli-protocol/cc/arduino/cli/monitor/v1/monitor_pb'; -import { MonitorClientProvider } from './monitor-client-provider'; -import { Board } from '../../common/protocol/boards-service'; -import { WebSocketProvider } from '../web-socket/web-socket-provider'; -import { SerialPlotter } from '../../browser/serial/plotter/protocol'; -import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol'; - -export const SerialServiceName = 'serial-service'; - -interface ErrorWithCode extends Error { - readonly code: number; -} -namespace ErrorWithCode { - export function toSerialError( - error: Error, - config: SerialConfig - ): SerialError { - const { message } = error; - let code = undefined; - if (is(error)) { - // TODO: const `mapping`. Use regex for the `message`. - const mapping = new Map(); - mapping.set( - '1 CANCELLED: Cancelled on client', - SerialError.ErrorCodes.CLIENT_CANCEL - ); - mapping.set( - '2 UNKNOWN: device not configured', - SerialError.ErrorCodes.DEVICE_NOT_CONFIGURED - ); - mapping.set( - '2 UNKNOWN: error opening serial connection: Serial port busy', - SerialError.ErrorCodes.DEVICE_BUSY - ); - code = mapping.get(message); - } - return { - message, - code, - config, - }; - } - function is(error: Error & { code?: number }): error is ErrorWithCode { - return typeof error.code === 'number'; - } -} - -@injectable() -export class SerialServiceImpl implements SerialService { - protected theiaFEClient?: SerialServiceClient; - protected serialConfig?: SerialConfig; - - protected serialConnection?: { - duplex: ClientDuplexStream; - config: SerialConfig; - }; - protected messages: string[] = []; - protected onMessageReceived: Disposable | null; - protected onWSClientsNumberChanged: Disposable | null; - - protected flushMessagesInterval: NodeJS.Timeout | null; - - uploadInProgress = false; - - constructor( - @inject(ILogger) - @named(SerialServiceName) - protected readonly logger: ILogger, - - @inject(MonitorClientProvider) - protected readonly serialClientProvider: MonitorClientProvider, - - @inject(WebSocketProvider) - protected readonly webSocketService: WebSocketService - ) { } - - async isSerialPortOpen(): Promise { - return !!this.serialConnection; - } - - setClient(client: SerialServiceClient | undefined): void { - this.theiaFEClient = client; - - this.theiaFEClient?.notifyWebSocketChanged( - this.webSocketService.getAddress().port - ); - - // listen for the number of websocket clients and create or dispose the serial connection - this.onWSClientsNumberChanged = - this.webSocketService.onClientsNumberChanged(async () => { - await this.connectSerialIfRequired(); - }); - } - - public async clientsAttached(): Promise { - return this.webSocketService.getConnectedClientsNumber.bind( - this.webSocketService - )(); - } - - public async connectSerialIfRequired(): Promise { - if (this.uploadInProgress) return; - const clients = await this.clientsAttached(); - clients > 0 ? await this.connect() : await this.disconnect(); - } - - dispose(): void { - this.logger.info('>>> Disposing serial service...'); - if (this.serialConnection) { - this.disconnect(); - } - this.logger.info('<<< Disposed serial service.'); - this.theiaFEClient = undefined; - } - - async setSerialConfig(config: SerialConfig): Promise { - this.serialConfig = config; - await this.disconnect(); - await this.connectSerialIfRequired(); - } - - async updateWsConfigParam( - config: Partial - ): Promise { - const msg: SerialPlotter.Protocol.Message = { - command: SerialPlotter.Protocol.Command.MIDDLEWARE_CONFIG_CHANGED, - data: config, - }; - this.webSocketService.sendMessage(JSON.stringify(msg)); - } - - private async connect(): Promise { - if (!this.serialConfig) { - return Status.CONFIG_MISSING; - } - - this.logger.info( - `>>> Creating serial connection for ${Board.toString( - this.serialConfig.board - )} on port ${this.serialConfig.port.address}...` - ); - - if (this.serialConnection) { - return Status.ALREADY_CONNECTED; - } - const client = await this.serialClientProvider.client(); - if (!client) { - return Status.NOT_CONNECTED; - } - if (client instanceof Error) { - return { message: client.message }; - } - const duplex = client.streamingOpen(); - this.serialConnection = { duplex, config: this.serialConfig }; - - const serialConfig = this.serialConfig; - - duplex.on( - 'error', - ((error: Error) => { - const serialError = ErrorWithCode.toSerialError(error, serialConfig); - if (serialError.code !== SerialError.ErrorCodes.CLIENT_CANCEL) { - this.disconnect(serialError).then(() => { - if (this.theiaFEClient) { - this.theiaFEClient.notifyError(serialError); - } - }); - } - if (serialError.code === undefined) { - // Log the original, unexpected error. - this.logger.error(error); - } - }).bind(this) - ); - - this.updateWsConfigParam({ connected: !!this.serialConnection }); - - const flushMessagesToFrontend = () => { - if (this.messages.length) { - this.webSocketService.sendMessage(JSON.stringify(this.messages)); - this.messages = []; - } - }; - - this.onMessageReceived = this.webSocketService.onMessageReceived( - (msg: string) => { - try { - const message: SerialPlotter.Protocol.Message = JSON.parse(msg); - - switch (message.command) { - case SerialPlotter.Protocol.Command.PLOTTER_SEND_MESSAGE: - this.sendMessageToSerial(message.data); - break; - - case SerialPlotter.Protocol.Command.PLOTTER_SET_BAUDRATE: - this.theiaFEClient?.notifyBaudRateChanged( - parseInt(message.data, 10) as SerialConfig.BaudRate - ); - break; - - case SerialPlotter.Protocol.Command.PLOTTER_SET_LINE_ENDING: - this.theiaFEClient?.notifyLineEndingChanged(message.data); - break; - - case SerialPlotter.Protocol.Command.PLOTTER_SET_INTERPOLATE: - this.theiaFEClient?.notifyInterpolateChanged(message.data); - break; - - default: - break; - } - } catch (error) { } - } - ); - - // empty the queue every 32ms (~30fps) - this.flushMessagesInterval = setInterval(flushMessagesToFrontend, 32); - - duplex.on( - 'data', - ((resp: StreamingOpenResponse) => { - const raw = resp.getData(); - const message = - typeof raw === 'string' ? raw : new TextDecoder('utf8').decode(raw); - - // split the message if it contains more lines - const messages = stringToArray(message); - this.messages.push(...messages); - }).bind(this) - ); - - const { type, port } = this.serialConfig; - const req = new StreamingOpenRequest(); - const monitorConfig = new GrpcMonitorConfig(); - monitorConfig.setType(this.mapType(type)); - monitorConfig.setTarget(port.address); - if (this.serialConfig.baudRate !== undefined) { - monitorConfig.setAdditionalConfig( - Struct.fromJavaScript({ BaudRate: this.serialConfig.baudRate }) - ); - } - req.setConfig(monitorConfig); - - if (!this.serialConnection) { - return await this.disconnect(); - } - - const writeTimeout = new Promise((resolve) => { - setTimeout(async () => { - resolve(Status.NOT_CONNECTED); - }, 1000); - }); - - const writePromise = (serialConnection: any) => { - return new Promise((resolve) => { - serialConnection.duplex.write(req, () => { - const boardName = this.serialConfig?.board - ? Board.toString(this.serialConfig.board, { - useFqbn: false, - }) - : 'unknown board'; - - const portName = this.serialConfig?.port - ? this.serialConfig.port.address - : 'unknown port'; - this.logger.info( - `<<< Serial connection created for ${boardName} on port ${portName}.` - ); - resolve(Status.OK); - }); - }); - }; - - const status = await Promise.race([ - writeTimeout, - writePromise(this.serialConnection), - ]); - - if (status === Status.NOT_CONNECTED) { - this.disconnect(); - } - - return status; - } - - public async disconnect(reason?: SerialError): Promise { - return new Promise((resolve) => { - try { - if (this.onMessageReceived) { - this.onMessageReceived.dispose(); - this.onMessageReceived = null; - } - if (this.flushMessagesInterval) { - clearInterval(this.flushMessagesInterval); - this.flushMessagesInterval = null; - } - - if ( - !this.serialConnection && - reason && - reason.code === SerialError.ErrorCodes.CLIENT_CANCEL - ) { - resolve(Status.OK); - return; - } - this.logger.info('>>> Disposing serial connection...'); - if (!this.serialConnection) { - this.logger.warn('<<< Not connected. Nothing to dispose.'); - resolve(Status.NOT_CONNECTED); - return; - } - const { duplex, config } = this.serialConnection; - - this.logger.info( - `<<< Disposed serial connection for ${Board.toString(config.board, { - useFqbn: false, - })} on port ${config.port.address}.` - ); - - duplex.cancel(); - } finally { - this.serialConnection = undefined; - this.updateWsConfigParam({ connected: !!this.serialConnection }); - this.messages.length = 0; - - setTimeout(() => { - resolve(Status.OK); - }, 200); - } - }); - } - - async sendMessageToSerial(message: string): Promise { - if (!this.serialConnection) { - return Status.NOT_CONNECTED; - } - const req = new StreamingOpenRequest(); - req.setData(new TextEncoder().encode(message)); - return new Promise((resolve) => { - if (this.serialConnection) { - this.serialConnection.duplex.write(req, () => { - resolve(Status.OK); - }); - return; - } - this.disconnect().then(() => resolve(Status.NOT_CONNECTED)); - }); - } - - protected mapType( - type?: SerialConfig.ConnectionType - ): GrpcMonitorConfig.TargetType { - switch (type) { - case SerialConfig.ConnectionType.SERIAL: - return GrpcMonitorConfig.TargetType.TARGET_TYPE_SERIAL; - default: - return GrpcMonitorConfig.TargetType.TARGET_TYPE_SERIAL; - } - } -} - -// converts 'ab\nc\nd' => [ab\n,c\n,d] -function stringToArray(string: string, separator = '\n') { - const retArray: string[] = []; - - let prevChar = separator; - - for (let i = 0; i < string.length; i++) { - const currChar = string[i]; - - if (prevChar === separator) { - retArray.push(currChar); - } else { - const lastWord = retArray[retArray.length - 1]; - retArray[retArray.length - 1] = lastWord + currChar; - } - - prevChar = currChar; - } - return retArray; -} diff --git a/arduino-ide-extension/src/test/node/serial-service-impl.test.ts b/arduino-ide-extension/src/test/node/serial-service-impl.test.ts deleted file mode 100644 index db77a8b87..000000000 --- a/arduino-ide-extension/src/test/node/serial-service-impl.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { SerialServiceImpl } from './../../node/serial/serial-service-impl'; -import { IMock, It, Mock } from 'typemoq'; -import { createSandbox } from 'sinon'; -import * as sinonChai from 'sinon-chai'; -import { expect, use } from 'chai'; -use(sinonChai); - -import { ILogger } from '@theia/core/lib/common/logger'; -import { MonitorClientProvider } from '../../node/serial/monitor-client-provider'; -import { WebSocketProvider } from '../../node/web-socket/web-socket-provider'; -import { MonitorServiceClient } from '../../node/cli-protocol/cc/arduino/cli/monitor/v1/monitor_grpc_pb'; -import { Status } from '../../common/protocol'; - -describe('SerialServiceImpl', () => { - let subject: SerialServiceImpl; - - let logger: IMock; - let serialClientProvider: IMock; - let webSocketService: IMock; - - beforeEach(() => { - logger = Mock.ofType(); - logger.setup((b) => b.info(It.isAnyString())); - logger.setup((b) => b.warn(It.isAnyString())); - logger.setup((b) => b.error(It.isAnyString())); - - serialClientProvider = Mock.ofType(); - webSocketService = Mock.ofType(); - - subject = new SerialServiceImpl( - logger.object, - serialClientProvider.object, - webSocketService.object - ); - }); - - context('when a serial connection is requested', () => { - const sandbox = createSandbox(); - beforeEach(() => { - subject.uploadInProgress = false; - sandbox.spy(subject, 'disconnect'); - sandbox.spy(subject, 'updateWsConfigParam'); - }); - - afterEach(function () { - sandbox.restore(); - }); - - context('and an upload is in progress', () => { - beforeEach(async () => { - subject.uploadInProgress = true; - }); - - it('should not change the connection status', async () => { - await subject.connectSerialIfRequired(); - expect(subject.disconnect).to.have.callCount(0); - }); - }); - - context('and there is no upload in progress', () => { - beforeEach(async () => { - subject.uploadInProgress = false; - }); - - context('and there are 0 attached ws clients', () => { - it('should disconnect', async () => { - await subject.connectSerialIfRequired(); - expect(subject.disconnect).to.have.been.calledOnce; - }); - }); - - context('and there are > 0 attached ws clients', () => { - beforeEach(() => { - webSocketService - .setup((b) => b.getConnectedClientsNumber()) - .returns(() => 1); - }); - - it('should not call the disconenct', async () => { - await subject.connectSerialIfRequired(); - expect(subject.disconnect).to.have.callCount(0); - }); - }); - }); - }); - - context('when a disconnection is requested', () => { - const sandbox = createSandbox(); - beforeEach(() => { }); - - afterEach(function () { - sandbox.restore(); - }); - - context('and a serialConnection is not set', () => { - it('should return a NOT_CONNECTED status', async () => { - const status = await subject.disconnect(); - expect(status).to.be.equal(Status.NOT_CONNECTED); - }); - }); - - context('and a serialConnection is set', async () => { - beforeEach(async () => { - sandbox.spy(subject, 'updateWsConfigParam'); - await subject.disconnect(); - }); - - it('should dispose the serialConnection', async () => { - const serialConnectionOpen = await subject.isSerialPortOpen(); - expect(serialConnectionOpen).to.be.false; - }); - - it('should call updateWsConfigParam with disconnected status', async () => { - expect(subject.updateWsConfigParam).to.be.calledWith({ - connected: false, - }); - }); - }); - }); - - context('when a new config is passed in', () => { - const sandbox = createSandbox(); - beforeEach(async () => { - subject.uploadInProgress = false; - webSocketService - .setup((b) => b.getConnectedClientsNumber()) - .returns(() => 1); - - serialClientProvider - .setup((b) => b.client()) - .returns(async () => { - return { - streamingOpen: () => { - return { - on: (str: string, cb: any) => { }, - write: (chunk: any, cb: any) => { - cb(); - }, - cancel: () => { }, - }; - }, - } as MonitorServiceClient; - }); - - sandbox.spy(subject, 'disconnect'); - - await subject.setSerialConfig({ - board: { name: 'test' }, - port: { id: 'test|test', address: 'test', addressLabel: 'test', protocol: 'test', protocolLabel: 'test' }, - }); - }); - - afterEach(function () { - sandbox.restore(); - subject.dispose(); - }); - - it('should disconnect from previous connection', async () => { - expect(subject.disconnect).to.be.called; - }); - - it('should create the serialConnection', async () => { - const serialConnectionOpen = await subject.isSerialPortOpen(); - expect(serialConnectionOpen).to.be.true; - }); - }); -}); From ee265aec9048c30692a474901a10f526fe18a36e Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Mar 2022 16:07:10 +0100 Subject: [PATCH 12/66] Changed how connection is handled on upload --- .../browser/contributions/burn-bootloader.ts | 5 ----- .../browser/contributions/upload-sketch.ts | 6 ----- .../browser/contributions/verify-sketch.ts | 7 +++++- .../firmware-uploader-component.tsx | 5 +++-- .../firmware-uploader-dialog.tsx | 3 ++- .../protocol/arduino-firmware-uploader.ts | 4 +++- .../node/arduino-firmware-uploader-impl.ts | 22 ++++++++++--------- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts index 96953db7b..b79ad079e 100644 --- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -3,7 +3,6 @@ import { OutputChannelManager } from '@theia/output/lib/browser/output-channel'; import { CoreService } from '../../common/protocol'; import { ArduinoMenus } from '../menu/arduino-menus'; import { BoardsDataStore } from '../boards/boards-data-store'; -import { SerialConnectionManager } from '../serial/serial-connection-manager'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { SketchContribution, @@ -18,8 +17,6 @@ export class BurnBootloader extends SketchContribution { @inject(CoreService) protected readonly coreService: CoreService; - @inject(SerialConnectionManager) - protected readonly serialConnection: SerialConnectionManager; @inject(BoardsDataStore) protected readonly boardsDataStore: BoardsDataStore; @@ -91,8 +88,6 @@ export class BurnBootloader extends SketchContribution { errorMessage = e.toString(); } this.messageService.error(errorMessage); - } finally { - await this.serialConnection.reconnectAfterUpload(); } } } diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index b86949fd3..2aa75a1de 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -4,7 +4,6 @@ import { BoardUserField, CoreService } from '../../common/protocol'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { BoardsDataStore } from '../boards/boards-data-store'; -import { SerialConnectionManager } from '../serial/serial-connection-manager'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { SketchContribution, @@ -22,9 +21,6 @@ export class UploadSketch extends SketchContribution { @inject(CoreService) protected readonly coreService: CoreService; - @inject(SerialConnectionManager) - protected readonly serialConnection: SerialConnectionManager; - @inject(MenuModelRegistry) protected readonly menuRegistry: MenuModelRegistry; @@ -294,8 +290,6 @@ export class UploadSketch extends SketchContribution { } finally { this.uploadInProgress = false; this.onDidChangeEmitter.fire(); - - setTimeout(() => this.serialConnection.reconnectAfterUpload(), 5000); } } } diff --git a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts index 898953ae8..2c060a6ee 100644 --- a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts @@ -110,12 +110,17 @@ export class VerifySketch extends SketchContribution { ), this.sourceOverride(), ]); + const board = { + ...boardsConfig.selectedBoard, + name: boardsConfig.selectedBoard?.name || '', + fqbn, + } const verbose = this.preferences.get('arduino.compile.verbose'); const compilerWarnings = this.preferences.get('arduino.compile.warnings'); this.outputChannelManager.getChannel('Arduino').clear(); await this.coreService.compile({ sketchUri: sketch.uri, - fqbn, + board, optimizeForDebug: this.editorMode.compileForDebug, verbose, exportBinaries, diff --git a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-component.tsx b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-component.tsx index 8e5c6d32d..f892cc1b1 100644 --- a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-component.tsx +++ b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-component.tsx @@ -1,5 +1,6 @@ import { nls } from '@theia/core/lib/common'; import * as React from 'react'; +import { Port } from '../../../common/protocol'; import { ArduinoFirmwareUploader, FirmwareInfo, @@ -20,7 +21,7 @@ export const FirmwareUploaderComponent = ({ availableBoards: AvailableBoard[]; firmwareUploader: ArduinoFirmwareUploader; updatableFqbns: string[]; - flashFirmware: (firmware: FirmwareInfo, port: string) => Promise; + flashFirmware: (firmware: FirmwareInfo, port: Port) => Promise; isOpen: any; }): React.ReactElement => { // boolean states for buttons @@ -81,7 +82,7 @@ export const FirmwareUploaderComponent = ({ const installStatus = !!firmwareToFlash && !!selectedBoard?.port && - (await flashFirmware(firmwareToFlash, selectedBoard?.port.address)); + (await flashFirmware(firmwareToFlash, selectedBoard?.port)); setInstallFeedback((installStatus && 'ok') || 'fail'); } catch { diff --git a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx index 759757e39..e9f8149d1 100644 --- a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx @@ -15,6 +15,7 @@ import { } from '../../../common/protocol/arduino-firmware-uploader'; import { FirmwareUploaderComponent } from './firmware-uploader-component'; import { UploadFirmware } from '../../contributions/upload-firmware'; +import { Port } from '../../../common/protocol'; @injectable() export class UploadFirmwareDialogWidget extends ReactWidget { @@ -49,7 +50,7 @@ export class UploadFirmwareDialogWidget extends ReactWidget { }); } - protected flashFirmware(firmware: FirmwareInfo, port: string): Promise { + protected flashFirmware(firmware: FirmwareInfo, port: Port): Promise { this.busyCallback(true); return this.arduinoFirmwareUploader .flash(firmware, port) diff --git a/arduino-ide-extension/src/common/protocol/arduino-firmware-uploader.ts b/arduino-ide-extension/src/common/protocol/arduino-firmware-uploader.ts index f1e2a439f..3cf9437d3 100644 --- a/arduino-ide-extension/src/common/protocol/arduino-firmware-uploader.ts +++ b/arduino-ide-extension/src/common/protocol/arduino-firmware-uploader.ts @@ -1,3 +1,5 @@ +import { Port } from "./boards-service"; + export const ArduinoFirmwareUploaderPath = '/services/arduino-firmware-uploader'; export const ArduinoFirmwareUploader = Symbol('ArduinoFirmwareUploader'); @@ -10,7 +12,7 @@ export type FirmwareInfo = { }; export interface ArduinoFirmwareUploader { list(fqbn?: string): Promise; - flash(firmware: FirmwareInfo, port: string): Promise; + flash(firmware: FirmwareInfo, port: Port): Promise; uploadCertificates(command: string): Promise; updatableBoards(): Promise; availableFirmwares(fqbn: string): Promise; diff --git a/arduino-ide-extension/src/node/arduino-firmware-uploader-impl.ts b/arduino-ide-extension/src/node/arduino-firmware-uploader-impl.ts index d4150ea73..4e5eaa843 100644 --- a/arduino-ide-extension/src/node/arduino-firmware-uploader-impl.ts +++ b/arduino-ide-extension/src/node/arduino-firmware-uploader-impl.ts @@ -3,10 +3,10 @@ import { FirmwareInfo, } from '../common/protocol/arduino-firmware-uploader'; import { injectable, inject, named } from 'inversify'; -import { ExecutableService } from '../common/protocol'; -import { SerialService } from '../common/protocol/serial-service'; +import { ExecutableService, Port } from '../common/protocol'; import { getExecPath, spawnCommand } from './exec-util'; import { ILogger } from '@theia/core/lib/common/logger'; +import { MonitorManager } from './monitor-manager'; @injectable() export class ArduinoFirmwareUploaderImpl implements ArduinoFirmwareUploader { @@ -19,8 +19,8 @@ export class ArduinoFirmwareUploaderImpl implements ArduinoFirmwareUploader { @named('fwuploader') protected readonly logger: ILogger; - @inject(SerialService) - protected readonly serialService: SerialService; + @inject(MonitorManager) + protected readonly monitorManager: MonitorManager; protected onError(error: any): void { this.logger.error(error); @@ -69,26 +69,28 @@ export class ArduinoFirmwareUploaderImpl implements ArduinoFirmwareUploader { return await this.list(fqbn); } - async flash(firmware: FirmwareInfo, port: string): Promise { + async flash(firmware: FirmwareInfo, port: Port): Promise { let output; + const board = { + name: firmware.board_name, + fqbn: firmware.board_fqbn, + } try { - this.serialService.uploadInProgress = true; - await this.serialService.disconnect(); + this.monitorManager.notifyUploadStarted(board, port); output = await this.runCommand([ 'firmware', 'flash', '--fqbn', firmware.board_fqbn, '--address', - port, + port.address, '--module', `${firmware.module}@${firmware.firmware_version}`, ]); } catch (e) { throw e; } finally { - this.serialService.uploadInProgress = false; - this.serialService.connectSerialIfRequired(); + this.monitorManager.notifyUploadFinished(board, port); return output; } } From bf958fd8cf0f537fbff943c3524e276f15aae308 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Mar 2022 16:13:50 +0100 Subject: [PATCH 13/66] Proxied more monitor methods to frontend --- .../monitor-manager-proxy-client-impl.ts | 104 ++++++++++++++++-- .../src/common/protocol/monitor-service.ts | 46 +++++++- .../src/node/monitor-manager-proxy-impl.ts | 18 +-- .../src/node/monitor-service.ts | 27 ++--- 4 files changed, 155 insertions(+), 40 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 76425a4e7..7af890a4e 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -1,13 +1,103 @@ -import { Emitter } from "@theia/core"; -import { injectable } from "@theia/core/shared/inversify"; -import { MonitorManagerProxyClient } from "../common/protocol/monitor-service"; +import { Emitter, JsonRpcProxy, MessageService } from "@theia/core"; +import { inject, injectable } from "@theia/core/shared/inversify"; +import { Board, Port } from "../common/protocol"; +import { Monitor, MonitorManagerProxy, MonitorManagerProxyClient, MonitorSettings } from "../common/protocol/monitor-service"; @injectable() export class MonitorManagerProxyClientImpl implements MonitorManagerProxyClient { - protected readonly onWebSocketChangedEmitter = new Emitter(); - readonly onWebSocketChanged = this.onWebSocketChangedEmitter.event; + // When pluggable monitor messages are received from the backend + // this event is triggered. + // Ideally a frontend component is connected to this event + // to update the UI. + protected readonly onMessagesReceivedEmitter = new Emitter<{ messages: string[] }>(); + readonly onMessagesReceived = this.onMessagesReceivedEmitter.event; - notifyWebSocketChanged(message: number): void { - this.onWebSocketChangedEmitter.fire(message); + // WebSocket used to handle pluggable monitor communication between + // frontend and backend. + private webSocket?: WebSocket; + private wsPort?: number; + + getWebSocketPort(): number | undefined { + return this.wsPort; + } + + constructor( + @inject(MessageService) + protected messageService: MessageService, + + // This is necessary to call the backend methods from the frontend + @inject(MonitorManagerProxy) + protected server: JsonRpcProxy + ) { + + } + + /** + * Connects a localhost WebSocket using the specified port. + * @param addressPort port of the WebSocket + */ + connect(addressPort: number): void { + if (this.webSocket) { + return; + } + try { + this.webSocket = new WebSocket(`ws://localhost:${addressPort}`); + } catch { + this.messageService.error('Unable to connect to websocket'); + return; + } + + this.webSocket.onmessage = (res) => { + const messages = JSON.parse(res.data); + this.onMessagesReceivedEmitter.fire({ messages }); + } + this.wsPort = addressPort; + } + + /** + * Disconnects the WebSocket if connected. + */ + disconnect(): void { + try { + this.webSocket?.close(); + this.webSocket = undefined; + } catch { + this.messageService.error('Unable to close websocket'); + } + } + + async isWSConnected(): Promise { + return !!this.webSocket; + } + + async startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise { + return this.server.startMonitor(board, port, settings); + } + + getCurrentSettings(board: Board, port: Port): MonitorSettings { + return this.server.getCurrentSettings(board, port); + } + + send(message: string): void { + if (!this.webSocket) { + return; + } + + this.webSocket.send(JSON.stringify({ + command: Monitor.Command.SEND_MESSAGE, + data: message, + })); + } + + changeSettings(settings: MonitorSettings): void { + if (!this.webSocket) { + return; + } + + this.webSocket.send(JSON.stringify({ + command: Monitor.Command.CHANGE_SETTINGS, + // TODO: This might be wrong, verify if it works + data: settings, + })); } } diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 11217ca57..71c4b7e65 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -7,13 +7,20 @@ export interface MonitorManagerProxy extends JsonRpcServer; changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise; stopMonitor(board: Board, port: Port): Promise; - getSupportedSettings(protocol: string, fqbn: string): Promise; + getCurrentSettings(board: Board, port: Port): MonitorSettings; } export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); export interface MonitorManagerProxyClient { - onWebSocketChanged: Event; - notifyWebSocketChanged(message: number): void; + onMessagesReceived: Event<{ messages: string[] }>; + connect(addressPort: number): void; + disconnect(): void; + getWebSocketPort(): number | undefined; + isWSConnected(): Promise; + startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise; + getCurrentSettings(board: Board, port: Port): MonitorSettings; + send(message: string): void; + changeSettings(settings: MonitorSettings): void } export interface MonitorSetting { @@ -29,4 +36,35 @@ export interface MonitorSetting { selectedValue: string; } -export type MonitorSettings = Record; \ No newline at end of file +export type MonitorSettings = Record; + +export namespace Monitor { + export enum Command { + SEND_MESSAGE = 'MONITOR_SEND_MESSAGE', + CHANGE_SETTINGS = 'MONITOR_CHANGE_SETTINGS', + } + + export type Message = { + command: Monitor.Command, + data: string; + } +} + +export interface Status { } +export type OK = Status; +export interface ErrorStatus extends Status { + readonly message: string; +} +export namespace Status { + export function isOK(status: Status & { message?: string }): status is OK { + return !!status && typeof status.message !== 'string'; + } + export const OK: OK = {}; + export const NOT_CONNECTED: ErrorStatus = { message: 'Not connected.' }; + export const ALREADY_CONNECTED: ErrorStatus = { + message: 'Already connected.', + }; + export const CONFIG_MISSING: ErrorStatus = { + message: 'Serial Config missing.', + }; +} diff --git a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts index 2291228eb..851a84868 100644 --- a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts +++ b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts @@ -19,7 +19,7 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { } dispose(): void { - // NOOP + this.client?.disconnect(); } /** @@ -36,7 +36,8 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { } const status = await this.manager.startMonitor(board, port); if (status === Status.ALREADY_CONNECTED || status === Status.OK) { - this.client.notifyWebSocketChanged(this.manager.getWebsocketAddressPort(board, port)); + // Monitor started correctly, connect it with the frontend + this.client.connect(this.manager.getWebsocketAddressPort(board, port)); } } @@ -65,15 +66,14 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { } /** - * Returns the settings supported by the pluggable monitor for the specified - * protocol, the fqbn is necessary since it's used to tell different monitors - * using the same protocol. - * @param protocol protocol of a pluggable monitor - * @param fqbn unique ID of a board + * Returns the current settings by the pluggable monitor connected to specified + * by board/port combination. + * @param board board connected to port + * @param port port monitored * @returns a map of MonitorSetting */ - async getSupportedSettings(protocol: string, fqbn: string): Promise { - return this.manager.portMonitorSettings(protocol, fqbn); + getCurrentSettings(board: Board, port: Port): MonitorSettings { + return this.manager.currentMonitorSettings(board, port); } setClient(client: MonitorManagerProxyClient | undefined): void { diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index f843565b1..4503a3ca5 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -321,29 +321,16 @@ export class MonitorService extends CoreClientAware implements Disposable { if (!this.onMessageReceived) { this.onMessageReceived = this.webSocketProvider.onMessageReceived( (msg: string) => { - const message: SerialPlotter.Protocol.Message = JSON.parse(msg); + const message: Monitor.Message = JSON.parse(msg); switch (message.command) { - case SerialPlotter.Protocol.Command.PLOTTER_SEND_MESSAGE: + case Monitor.Command.SEND_MESSAGE: this.send(message.data); - break; - - case SerialPlotter.Protocol.Command.PLOTTER_SET_BAUDRATE: - this.theiaFEClient?.notifyBaudRateChanged( - parseInt(message.data, 10) as SerialConfig.BaudRate - ); - break; - - case SerialPlotter.Protocol.Command.PLOTTER_SET_LINE_ENDING: - this.theiaFEClient?.notifyLineEndingChanged(message.data); - break; - - case SerialPlotter.Protocol.Command.PLOTTER_SET_INTERPOLATE: - this.theiaFEClient?.notifyInterpolateChanged(message.data); - break; - - default: - break; + break + case Monitor.Command.CHANGE_SETTINGS: + const settings: MonitorSettings = JSON.parse(message.data); + this.changeSettings(settings); + break } } ) From cbd5b4de1b35aaa08ab7850007ccdc9c36b859c7 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Mar 2022 16:15:05 +0100 Subject: [PATCH 14/66] WebSocketProvider is not injectable anymore --- .../src/node/web-socket/web-socket-provider-impl.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts b/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts index 81d258a0b..268928dd2 100644 --- a/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts +++ b/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts @@ -1,9 +1,7 @@ import { Emitter } from '@theia/core'; -import { injectable } from 'inversify'; import * as WebSocket from 'ws'; import { WebSocketProvider } from './web-socket-provider'; -@injectable() export default class WebSocketProviderImpl implements WebSocketProvider { protected wsClients: WebSocket[]; protected server: WebSocket.Server; From 50239c5756ab431003567d7efcdfd54804df6a57 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Mar 2022 16:19:25 +0100 Subject: [PATCH 15/66] Add generic monitor settings storaging --- .../src/browser/monitor-model.ts | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 arduino-ide-extension/src/browser/monitor-model.ts diff --git a/arduino-ide-extension/src/browser/monitor-model.ts b/arduino-ide-extension/src/browser/monitor-model.ts new file mode 100644 index 000000000..5082e98ae --- /dev/null +++ b/arduino-ide-extension/src/browser/monitor-model.ts @@ -0,0 +1,134 @@ +import { Emitter, Event } from "@theia/core"; +import { FrontendApplicationContribution, LocalStorageService } from "@theia/core/lib/browser"; +import { inject, injectable } from "@theia/core/shared/inversify"; + +@injectable() +export class MonitorModel implements FrontendApplicationContribution { + protected static STORAGE_ID = 'arduino-monitor-model'; + + @inject(LocalStorageService) + protected readonly localStorageService: LocalStorageService; + + protected readonly onChangeEmitter: Emitter>; + + protected _autoscroll: boolean; + protected _timestamp: boolean; + protected _lineEnding: MonitorModel.EOL; + protected _interpolate: boolean; + + constructor() { + this._autoscroll = true; + this._timestamp = false; + this._interpolate = false; + this._lineEnding = MonitorModel.EOL.DEFAULT; + + this.onChangeEmitter = new Emitter< + MonitorModel.State.Change + >(); + } + + onStart(): void { + this.localStorageService + .getData(MonitorModel.STORAGE_ID) + .then(this.restoreState); + } + + get onChange(): Event> { + return this.onChangeEmitter.event; + } + + protected restoreState(state: MonitorModel.State): void { + if (!state) { + return; + } + this._autoscroll = state.autoscroll; + this._timestamp = state.timestamp; + this._lineEnding = state.lineEnding; + this._interpolate = state.interpolate; + } + + protected async storeState(): Promise { + return this.localStorageService.setData(MonitorModel.STORAGE_ID, { + autoscroll: this._autoscroll, + timestamp: this._timestamp, + lineEnding: this._lineEnding, + interpolate: this._interpolate, + }); + } + + get autoscroll(): boolean { + return this._autoscroll; + } + + toggleAutoscroll(): void { + this._autoscroll = !this._autoscroll; + this.storeState().then(() => { + this.onChangeEmitter.fire({ + property: 'autoscroll', + value: this._timestamp + }); + }); + } + + get timestamp(): boolean { + return this._timestamp; + } + + toggleTimestamp(): void { + this._timestamp = !this._timestamp; + this.storeState().then(() => + this.onChangeEmitter.fire({ + property: 'timestamp', + value: this._timestamp, + }) + ); + } + + get lineEnding(): MonitorModel.EOL { + return this._lineEnding; + } + + set lineEnding(lineEnding: MonitorModel.EOL) { + this._lineEnding = lineEnding; + this.storeState().then(() => + this.onChangeEmitter.fire({ + property: 'lineEnding', + value: this._lineEnding, + }) + ); + } + + get interpolate(): boolean { + return this._interpolate; + } + + set interpolate(i: boolean) { + this._interpolate = i; + this.storeState().then(() => + this.onChangeEmitter.fire({ + property: 'interpolate', + value: this._interpolate, + }) + ); + } +} + +export namespace MonitorModel { + export interface State { + autoscroll: boolean; + timestamp: boolean; + lineEnding: EOL; + interpolate: boolean; + } + export namespace State { + export interface Change { + readonly property: K; + readonly value: State[K]; + } + } + + export type EOL = '' | '\n' | '\r' | '\r\n'; + export namespace EOL { + export const DEFAULT: EOL = '\n'; + } +} From 6cf61c498a9dfe676b1d4d08d1cf9d74c447bc5b Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Mar 2022 16:18:47 +0100 Subject: [PATCH 16/66] More serial classes removal --- .../browser/arduino-ide-frontend-module.ts | 23 ------------------- .../monitor/monitor-view-contribution.tsx | 5 ++-- .../src/common/protocol/index.ts | 1 - .../src/node/arduino-ide-backend-module.ts | 10 -------- 4 files changed, 3 insertions(+), 36 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index c06f9f7b2..613b446ec 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -69,20 +69,12 @@ import { ScmContribution } from './theia/scm/scm-contribution'; import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspace/search-in-workspace-frontend-contribution'; import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution'; -import { SerialServiceClientImpl } from './serial/serial-service-client-impl'; -import { - SerialServicePath, - SerialService, - SerialServiceClient, -} from '../common/protocol/serial-service'; import { ConfigService, ConfigServicePath, } from '../common/protocol/config-service'; import { MonitorWidget } from './serial/monitor/monitor-widget'; import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; -import { SerialConnectionManager } from './serial/serial-connection-manager'; -import { SerialModel } from './serial/serial-model'; import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator'; import { TabBarDecoratorService } from './theia/core/tab-bar-decorator'; import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser'; @@ -409,8 +401,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope(); // Serial monitor - bind(SerialModel).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(SerialModel); bind(MonitorWidget).toSelf(); bindViewContribution(bind, MonitorViewContribution); bind(TabBarToolbarContribution).toService(MonitorViewContribution); @@ -418,20 +408,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { id: MonitorWidget.ID, createWidget: () => context.container.get(MonitorWidget), })); - // Frontend binding for the serial service - bind(SerialService) - .toDynamicValue((context) => { - const connection = context.container.get(WebSocketConnectionProvider); - const client = context.container.get( - SerialServiceClient - ); - return connection.createProxy(SerialServicePath, client); - }) - .inSingletonScope(); - bind(SerialConnectionManager).toSelf().inSingletonScope(); - // Serial service client to receive and delegate notifications from the backend. - bind(SerialServiceClient).to(SerialServiceClientImpl).inSingletonScope(); // Monitor manager proxy client to receive and delegate pluggable monitors // notifications from the backend diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index 970819c97..a335d6009 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -8,9 +8,9 @@ import { TabBarToolbarRegistry, } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { ArduinoToolbar } from '../../toolbar/arduino-toolbar'; -import { SerialModel } from '../serial-model'; import { ArduinoMenus } from '../../menu/arduino-menus'; import { nls } from '@theia/core/lib/common'; +import { MonitorModel } from '../../monitor-model'; export namespace SerialMonitor { export namespace Commands { @@ -48,7 +48,8 @@ export class MonitorViewContribution static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR = MonitorWidget.ID + ':toggle-toolbar'; - @inject(SerialModel) protected readonly model: SerialModel; + @inject(MonitorModel) + protected readonly model: MonitorModel; constructor() { super({ diff --git a/arduino-ide-extension/src/common/protocol/index.ts b/arduino-ide-extension/src/common/protocol/index.ts index 1a9a25c28..4adf94223 100644 --- a/arduino-ide-extension/src/common/protocol/index.ts +++ b/arduino-ide-extension/src/common/protocol/index.ts @@ -6,7 +6,6 @@ export * from './core-service'; export * from './filesystem-ext'; export * from './installable'; export * from './library-service'; -export * from './serial-service'; export * from './searchable'; export * from './sketches-service'; export * from './examples-service'; diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 6b3f585c8..fd4b81833 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -40,7 +40,6 @@ import { ArduinoDaemon, ArduinoDaemonPath, } from '../common/protocol/arduino-daemon'; -import { SerialServiceName } from './serial/serial-service-impl'; import { ConfigServiceImpl } from './config-service-impl'; import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables'; @@ -303,15 +302,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope() .whenTargetNamed('config'); - // Logger for the serial service. - bind(ILogger) - .toDynamicValue((ctx) => { - const parentLogger = ctx.container.get(ILogger); - return parentLogger.child(SerialServiceName); - }) - .inSingletonScope() - .whenTargetNamed(SerialServiceName); - bind(DefaultGitInit).toSelf(); rebind(GitInit).toService(DefaultGitInit); From ad781f0bfc7e74f6d86481a51a105c8bd14952c2 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Mar 2022 16:25:23 +0100 Subject: [PATCH 17/66] Remove unused file --- .../src/test/browser/fixtures/serial.ts | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 arduino-ide-extension/src/test/browser/fixtures/serial.ts diff --git a/arduino-ide-extension/src/test/browser/fixtures/serial.ts b/arduino-ide-extension/src/test/browser/fixtures/serial.ts deleted file mode 100644 index ab8b333a6..000000000 --- a/arduino-ide-extension/src/test/browser/fixtures/serial.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { SerialConfig } from '../../../common/protocol/serial-service'; -import { aBoard, anotherBoard, anotherPort, aPort } from './boards'; - -export const aSerialConfig: SerialConfig = { - board: aBoard, - port: aPort, - baudRate: 9600, -}; - -export const anotherSerialConfig: SerialConfig = { - board: anotherBoard, - port: anotherPort, - baudRate: 9600, -}; - -export class WebSocketMock { - readonly url: string; - constructor(url: string) { - this.url = url; - } - close() {} -} From 6b7b33356da23c56c7692c6706e8dd16449ada9f Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Mar 2022 17:03:30 +0100 Subject: [PATCH 18/66] Changed plotter contribution to use new manager proxy --- .../plotter/plotter-frontend-contribution.ts | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index f4e957063..3f22f2071 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -6,15 +6,14 @@ import { MaybePromise, MenuModelRegistry, } from '@theia/core'; -import { SerialModel } from '../serial-model'; import { ArduinoMenus } from '../../menu/arduino-menus'; import { Contribution } from '../../contributions/contribution'; import { Endpoint, FrontendApplication } from '@theia/core/lib/browser'; import { ipcRenderer } from '@theia/electron/shared/electron'; -import { SerialConfig } from '../../../common/protocol'; -import { SerialConnectionManager } from '../serial-connection-manager'; +import { MonitorManagerProxyClient } from '../../../common/protocol'; import { SerialPlotter } from './protocol'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; +import { MonitorModel } from '../../monitor-model'; const queryString = require('query-string'); export namespace SerialPlotterContribution { @@ -33,14 +32,14 @@ export class PlotterFrontendContribution extends Contribution { protected url: string; protected wsPort: number; - @inject(SerialModel) - protected readonly model: SerialModel; + @inject(MonitorModel) + protected readonly model: MonitorModel; @inject(ThemeService) protected readonly themeService: ThemeService; - @inject(SerialConnectionManager) - protected readonly serialConnection: SerialConnectionManager; + @inject(MonitorManagerProxyClient) + protected readonly monitorManagerProxy: MonitorManagerProxyClient; @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; @@ -75,7 +74,7 @@ export class PlotterFrontendContribution extends Contribution { this.window.focus(); return; } - const wsPort = this.serialConnection.getWsPort(); + const wsPort = this.monitorManagerProxy.getWebSocketPort(); if (wsPort) { this.open(wsPort); } else { @@ -84,14 +83,27 @@ export class PlotterFrontendContribution extends Contribution { } protected async open(wsPort: number): Promise { + const board = this.boardsServiceProvider.boardsConfig.selectedBoard; + const port = this.boardsServiceProvider.boardsConfig.selectedPort; + let baudrates: number[] = []; + let currentBaudrate = -1; + if (board && port) { + const settings = this.monitorManagerProxy.getCurrentSettings(board, port); + if ('baudrate' in settings) { + // Convert from string to numbers + baudrates = settings['baudrate'].values.map(b => +b); + currentBaudrate = +settings['baudrate'].selectedValue; + } + } + const initConfig: Partial = { - baudrates: SerialConfig.BaudRates.map((b) => b), - currentBaudrate: this.model.baudRate, + baudrates, + currentBaudrate, currentLineEnding: this.model.lineEnding, darkTheme: this.themeService.getCurrentTheme().type === 'dark', wsPort, interpolate: this.model.interpolate, - connected: await this.serialConnection.isBESerialConnected(), + connected: await this.monitorManagerProxy.isWSConnected(), serialPort: this.boardsServiceProvider.boardsConfig.selectedPort?.address, }; const urlWithParams = queryString.stringifyUrl( From 7889f40834216fca0ffc2a36bb8c8b846531b082 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Thu, 10 Mar 2022 17:04:07 +0100 Subject: [PATCH 19/66] Changed MonitorWidget and children to use new monitor proxy --- .../browser/serial/monitor/monitor-widget.tsx | 120 ++++++++++++------ .../monitor/serial-monitor-send-input.tsx | 28 ++-- .../monitor/serial-monitor-send-output.tsx | 19 ++- 3 files changed, 96 insertions(+), 71 deletions(-) diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index f5e68df54..a7e02cec0 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -9,14 +9,16 @@ import { Widget, MessageLoop, } from '@theia/core/lib/browser/widgets'; -import { SerialConfig } from '../../../common/protocol/serial-service'; import { ArduinoSelect } from '../../widgets/arduino-select'; -import { SerialModel } from '../serial-model'; -import { SerialConnectionManager } from '../serial-connection-manager'; import { SerialMonitorSendInput } from './serial-monitor-send-input'; import { SerialMonitorOutput } from './serial-monitor-send-output'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { nls } from '@theia/core/lib/common'; +import { + MonitorManagerProxyClient, + MonitorSettings, +} from '../../../common/protocol'; +import { MonitorModel } from '../../monitor-model'; @injectable() export class MonitorWidget extends ReactWidget { @@ -26,11 +28,11 @@ export class MonitorWidget extends ReactWidget { ); static readonly ID = 'serial-monitor'; - @inject(SerialModel) - protected readonly serialModel: SerialModel; + @inject(MonitorModel) + protected readonly monitorModel: MonitorModel; - @inject(SerialConnectionManager) - protected readonly serialConnection: SerialConnectionManager; + @inject(MonitorManagerProxyClient) + protected readonly monitorManagerProxy: MonitorManagerProxyClient; @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; @@ -57,17 +59,27 @@ export class MonitorWidget extends ReactWidget { this.scrollOptions = undefined; this.toDispose.push(this.clearOutputEmitter); this.toDispose.push( - Disposable.create(() => this.serialConnection.closeWStoBE()) + Disposable.create(() => this.monitorManagerProxy.disconnect()) + ); + + this.toDispose.push( + this.boardsServiceProvider.onBoardsConfigChanged( + async ({ selectedBoard, selectedPort }) => { + if (selectedBoard && selectedBoard.fqbn && selectedPort) { + await this.monitorManagerProxy.startMonitor( + selectedBoard, + selectedPort + ); + } + } + ) ); } @postConstruct() protected init(): void { this.update(); - this.toDispose.push( - this.serialConnection.onConnectionChanged(() => this.clearConsole()) - ); - this.toDispose.push(this.serialModel.onChange(() => this.update())); + this.toDispose.push(this.monitorModel.onChange(() => this.update())); } clearConsole(): void { @@ -79,11 +91,6 @@ export class MonitorWidget extends ReactWidget { super.dispose(); } - protected onAfterAttach(msg: Message): void { - super.onAfterAttach(msg); - this.serialConnection.openWSToBE(); - } - onCloseRequest(msg: Message): void { this.closing = true; super.onCloseRequest(msg); @@ -119,7 +126,7 @@ export class MonitorWidget extends ReactWidget { }; protected get lineEndings(): OptionsType< - SerialMonitorOutput.SelectOption + SerialMonitorOutput.SelectOption > { return [ { @@ -144,32 +151,61 @@ export class MonitorWidget extends ReactWidget { ]; } - protected get baudRates(): OptionsType< - SerialMonitorOutput.SelectOption - > { - const baudRates: Array = [ - 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, - ]; - return baudRates.map((baudRate) => ({ - label: baudRate + ' baud', - value: baudRate, - })); + private getCurrentSettings(): MonitorSettings { + const board = this.boardsServiceProvider.boardsConfig.selectedBoard; + const port = this.boardsServiceProvider.boardsConfig.selectedPort; + if (!board || !port) { + return {}; + } + return this.monitorManagerProxy.getCurrentSettings(board, port); + } + + ////////////////////////////////////////////////// + ////////////////////IMPORTANT///////////////////// + ////////////////////////////////////////////////// + // baudRates and selectedBaudRates as of now are hardcoded + // like this to retrieve the baudrate settings from the ones + // received by the monitor. + // We're doing it like since the frontend as of now doesn't + // support a fully customizable list of options that would + // be require to support pluggable monitors completely. + // As soon as the frontend UI is updated to support + // any custom settings this methods MUST be removed and + // made generic. + // + // This breaks if the user tries to open a monitor that + // doesn't support the baudrate setting. + protected get baudRates(): string[] { + const settings = this.getCurrentSettings(); + const baudRateSettings = settings['baudrate']; + if (!baudRateSettings) { + return []; + } + return baudRateSettings.values; + } + + protected get selectedBaudRate(): string { + const settings = this.getCurrentSettings(); + const baudRateSettings = settings['baudrate']; + if (!baudRateSettings) { + return ''; + } + return baudRateSettings.selectedValue; } protected render(): React.ReactNode { const { baudRates, lineEndings } = this; const lineEnding = - lineEndings.find((item) => item.value === this.serialModel.lineEnding) || + lineEndings.find((item) => item.value === this.monitorModel.lineEnding) || lineEndings[1]; // Defaults to `\n`. - const baudRate = - baudRates.find((item) => item.value === this.serialModel.baudRate) || - baudRates[4]; // Defaults to `9600`. + const baudRate = baudRates.find((item) => item === this.selectedBaudRate); return (
@@ -196,8 +232,8 @@ export class MonitorWidget extends ReactWidget {
@@ -208,18 +244,18 @@ export class MonitorWidget extends ReactWidget { protected readonly onSend = (value: string) => this.doSend(value); protected async doSend(value: string): Promise { - this.serialConnection.send(value); + this.monitorManagerProxy.send(value); } protected readonly onChangeLineEnding = ( - option: SerialMonitorOutput.SelectOption + option: SerialMonitorOutput.SelectOption ) => { - this.serialModel.lineEnding = option.value; + this.monitorModel.lineEnding = option.value; }; - protected readonly onChangeBaudRate = ( - option: SerialMonitorOutput.SelectOption - ) => { - this.serialModel.baudRate = option.value; + protected readonly onChangeBaudRate = (value: string) => { + const settings = this.getCurrentSettings(); + settings['baudrate'].selectedValue = value; + this.monitorManagerProxy.changeSettings(settings); }; } diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx index e15d55eac..ed545fc86 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx @@ -3,12 +3,13 @@ import { Key, KeyCode } from '@theia/core/lib/browser/keys'; import { Board } from '../../../common/protocol/boards-service'; import { isOSX } from '@theia/core/lib/common/os'; import { DisposableCollection, nls } from '@theia/core/lib/common'; -import { SerialConnectionManager } from '../serial-connection-manager'; -import { SerialPlotter } from '../plotter/protocol'; +import { MonitorManagerProxyClient } from '../../../common/protocol'; +import { BoardsServiceProvider } from '../../boards/boards-service-provider'; export namespace SerialMonitorSendInput { export interface Props { - readonly serialConnection: SerialConnectionManager; + readonly boardsServiceProvider: BoardsServiceProvider; + readonly monitorManagerProxy: MonitorManagerProxyClient; readonly onSend: (text: string) => void; readonly resolveFocus: (element: HTMLElement | undefined) => void; } @@ -33,21 +34,9 @@ export class SerialMonitorSendInput extends React.Component< } componentDidMount(): void { - this.props.serialConnection.isBESerialConnected().then((connected) => { + this.props.monitorManagerProxy.isWSConnected().then((connected) => { this.setState({ connected }); }); - - this.toDisposeBeforeUnmount.pushAll([ - this.props.serialConnection.onRead(({ messages }) => { - if ( - messages.command === - SerialPlotter.Protocol.Command.MIDDLEWARE_CONFIG_CHANGED && - 'connected' in messages.data - ) { - this.setState({ connected: messages.data.connected }); - } - }), - ]); } componentWillUnmount(): void { @@ -70,14 +59,15 @@ export class SerialMonitorSendInput extends React.Component< } protected get placeholder(): string { - const serialConfig = this.props.serialConnection.getConfig(); - if (!this.state.connected || !serialConfig) { + const board = this.props.boardsServiceProvider.boardsConfig.selectedBoard; + const port = this.props.boardsServiceProvider.boardsConfig.selectedPort; + if (!this.state.connected || !board || !port) { return nls.localize( 'arduino/serial/notConnected', 'Not connected. Select a board and a port to connect automatically.' ); } - const { board, port } = serialConfig; + return nls.localize( 'arduino/serial/message', "Message ({0} + Enter to send message to '{1}' on '{2}')", diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx index 2c3df28fc..383965124 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx @@ -2,10 +2,10 @@ import * as React from 'react'; import { Event } from '@theia/core/lib/common/event'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { areEqual, FixedSizeList as List } from 'react-window'; -import { SerialModel } from '../serial-model'; -import { SerialConnectionManager } from '../serial-connection-manager'; import dateFormat = require('dateformat'); import { messagesToLines, truncateLines } from './monitor-utils'; +import { MonitorManagerProxyClient } from '../../../common/protocol'; +import { MonitorModel } from '../../monitor-model'; export type Line = { message: string; timestamp?: Date; lineLen: number }; @@ -24,7 +24,7 @@ export class SerialMonitorOutput extends React.Component< this.listRef = React.createRef(); this.state = { lines: [], - timestamp: this.props.serialModel.timestamp, + timestamp: this.props.monitorModel.timestamp, charCount: 0, }; } @@ -58,14 +58,13 @@ export class SerialMonitorOutput extends React.Component< componentDidMount(): void { this.scrollToBottom(); this.toDisposeBeforeUnmount.pushAll([ - this.props.serialConnection.onRead(({ messages }) => { + this.props.monitorManagerProxy.onMessagesReceived(({ messages }) => { const [newLines, totalCharCount] = messagesToLines( messages, this.state.lines, this.state.charCount ); const [lines, charCount] = truncateLines(newLines, totalCharCount); - this.setState({ lines, charCount, @@ -75,9 +74,9 @@ export class SerialMonitorOutput extends React.Component< this.props.clearConsoleEvent(() => this.setState({ lines: [], charCount: 0 }) ), - this.props.serialModel.onChange(({ property }) => { + this.props.monitorModel.onChange(({ property }) => { if (property === 'timestamp') { - const { timestamp } = this.props.serialModel; + const { timestamp } = this.props.monitorModel; this.setState({ timestamp }); } if (property === 'autoscroll') { @@ -93,7 +92,7 @@ export class SerialMonitorOutput extends React.Component< } scrollToBottom = ((): void => { - if (this.listRef.current && this.props.serialModel.autoscroll) { + if (this.listRef.current && this.props.monitorModel.autoscroll) { this.listRef.current.scrollToItem(this.state.lines.length, 'end'); } }).bind(this); @@ -128,8 +127,8 @@ const Row = React.memo(_Row, areEqual); export namespace SerialMonitorOutput { export interface Props { - readonly serialModel: SerialModel; - readonly serialConnection: SerialConnectionManager; + readonly monitorModel: MonitorModel; + readonly monitorManagerProxy: MonitorManagerProxyClient; readonly clearConsoleEvent: Event; readonly height: number; } From ce2f1c227a92bfea039a6e03f48a74dd08752ff8 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 11 Mar 2022 14:34:18 +0100 Subject: [PATCH 20/66] Updated MonitorWidget to use new monitor proxy --- .../browser/arduino-ide-frontend-module.ts | 16 +++++++++-- .../browser/serial/monitor/monitor-widget.tsx | 28 ++++++++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 613b446ec..446bd09aa 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -152,7 +152,7 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, OutputChannelRegistryMainImpl, } from './theia/plugin-ext/output-channel-registry-main'; -import { ExecutableService, ExecutableServicePath } from '../common/protocol'; +import { ExecutableService, ExecutableServicePath, MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyPath } from '../common/protocol'; import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service'; import { ResponseServiceImpl } from './response-service-impl'; @@ -267,7 +267,7 @@ import { IDEUpdaterDialogWidget, } from './dialogs/ide-updater/ide-updater-dialog'; import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider'; -import { MonitorManagerProxyClient } from '../common/monitor-manager-proxy'; +import { MonitorModel } from './monitor-model'; import { MonitorManagerProxyClientImpl } from './monitor-manager-proxy-client-impl'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -402,13 +402,23 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // Serial monitor bind(MonitorWidget).toSelf(); + bind(MonitorModel).toSelf().inSingletonScope(); bindViewContribution(bind, MonitorViewContribution); bind(TabBarToolbarContribution).toService(MonitorViewContribution); bind(WidgetFactory).toDynamicValue((context) => ({ id: MonitorWidget.ID, - createWidget: () => context.container.get(MonitorWidget), + createWidget: () => { + return new MonitorWidget( + context.container.get(MonitorModel), + context.container.get(MonitorManagerProxyClient), + context.container.get(BoardsServiceProvider), + ); + } })); + bind(MonitorManagerProxy).toDynamicValue((context) => + WebSocketConnectionProvider.createProxy(context.container, MonitorManagerProxyPath) + ).inSingletonScope(); // Monitor manager proxy client to receive and delegate pluggable monitors // notifications from the backend diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index a7e02cec0..43f3a3b27 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -28,15 +28,6 @@ export class MonitorWidget extends ReactWidget { ); static readonly ID = 'serial-monitor'; - @inject(MonitorModel) - protected readonly monitorModel: MonitorModel; - - @inject(MonitorManagerProxyClient) - protected readonly monitorManagerProxy: MonitorManagerProxyClient; - - @inject(BoardsServiceProvider) - protected readonly boardsServiceProvider: BoardsServiceProvider; - protected widgetHeight: number; /** @@ -50,7 +41,16 @@ export class MonitorWidget extends ReactWidget { protected closing = false; protected readonly clearOutputEmitter = new Emitter(); - constructor() { + constructor( + @inject(MonitorModel) + protected readonly monitorModel: MonitorModel, + + @inject(MonitorManagerProxyClient) + protected readonly monitorManagerProxy: MonitorManagerProxyClient, + + @inject(BoardsServiceProvider) + protected readonly boardsServiceProvider: BoardsServiceProvider + ) { super(); this.id = MonitorWidget.ID; this.title.label = MonitorWidget.LABEL; @@ -62,6 +62,13 @@ export class MonitorWidget extends ReactWidget { Disposable.create(() => this.monitorManagerProxy.disconnect()) ); + // Start monitor right away if there is already a board/port combination selected + const { selectedBoard, selectedPort } = + this.boardsServiceProvider.boardsConfig; + if (selectedBoard && selectedBoard.fqbn && selectedPort) { + this.monitorManagerProxy.startMonitor(selectedBoard, selectedPort); + } + this.toDispose.push( this.boardsServiceProvider.onBoardsConfigChanged( async ({ selectedBoard, selectedPort }) => { @@ -70,6 +77,7 @@ export class MonitorWidget extends ReactWidget { selectedBoard, selectedPort ); + this.update(); } } ) From b97af32bb83b4676d3b92bebdad4ad54b9377366 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Mon, 14 Mar 2022 11:01:47 +0100 Subject: [PATCH 21/66] Fix backend logger bindings --- .../src/node/arduino-ide-backend-module.ts | 20 ++++++++++++++++++- .../src/node/monitor-manager-proxy-impl.ts | 7 +------ .../src/node/monitor-manager.ts | 4 +++- .../src/node/monitor-service.ts | 4 +++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index fd4b81833..9ecd1bdea 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -84,8 +84,9 @@ import { PlotterBackendContribution } from './plotter/plotter-backend-contributi import { ArduinoLocalizationContribution } from './arduino-localization-contribution'; import { LocalizationContribution } from '@theia/core/lib/node/i18n/localization-contribution'; import { MonitorManagerProxyImpl } from './monitor-manager-proxy-impl'; -import { MonitorManager } from './monitor-manager'; +import { MonitorManager, MonitorManagerName } from './monitor-manager'; import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyPath } from '../common/protocol/monitor-service'; +import { MonitorServiceName } from './monitor-service'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -302,6 +303,23 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope() .whenTargetNamed('config'); + // Logger for the monitor manager and its services + bind(ILogger) + .toDynamicValue((ctx) => { + const parentLogger = ctx.container.get(ILogger); + return parentLogger.child(MonitorManagerName); + }) + .inSingletonScope() + .whenTargetNamed(MonitorManagerName); + + bind(ILogger) + .toDynamicValue((ctx) => { + const parentLogger = ctx.container.get(ILogger); + return parentLogger.child(MonitorServiceName); + }) + .inSingletonScope() + .whenTargetNamed(MonitorServiceName); + bind(DefaultGitInit).toSelf(); rebind(GitInit).toService(DefaultGitInit); diff --git a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts index 851a84868..e1fa1eefc 100644 --- a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts +++ b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts @@ -1,5 +1,4 @@ -import { ILogger } from "@theia/core"; -import { inject, injectable, named } from "@theia/core/shared/inversify"; +import { inject, injectable } from "@theia/core/shared/inversify"; import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorSettings, Status } from "../common/protocol"; import { Board, Port } from "../common/protocol"; import { MonitorManager } from "./monitor-manager"; @@ -9,10 +8,6 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { protected client: MonitorManagerProxyClient; constructor( - @inject(ILogger) - @named("monitor-manager-proxy") - protected readonly logger: ILogger, - @inject(MonitorManager) protected readonly manager: MonitorManager, ) { diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 1f38bac8c..183aec77d 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -6,6 +6,8 @@ import { MonitorService } from "./monitor-service"; type MonitorID = string; +export const MonitorManagerName = 'monitor-manager'; + @injectable() export class MonitorManager extends CoreClientAware { // Map of monitor services that manage the running pluggable monitors. @@ -16,7 +18,7 @@ export class MonitorManager extends CoreClientAware { constructor( @inject(ILogger) - @named('monitor-manager') + @named(MonitorManagerName) protected readonly logger: ILogger, ) { super(); diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 4503a3ca5..e5a1069d5 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -8,6 +8,8 @@ import { WebSocketProvider } from "./web-socket/web-socket-provider"; import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb' import WebSocketProviderImpl from "./web-socket/web-socket-provider-impl"; +export const MonitorServiceName = 'monitor-service'; + export class MonitorService extends CoreClientAware implements Disposable { // Bidirectional gRPC stream used to receive and send data from the running // pluggable monitor managed by the Arduino CLI. @@ -39,7 +41,7 @@ export class MonitorService extends CoreClientAware implements Disposable { constructor( @inject(ILogger) - @named("monitor-service") + @named(MonitorServiceName) protected readonly logger: ILogger, private readonly board: Board, From f9da9fc24b69a5e353f7dacc877a253cd0dd73e5 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 15 Mar 2022 14:10:31 +0100 Subject: [PATCH 22/66] Delete unnecessary Symbol --- arduino-ide-extension/src/node/web-socket/web-socket-provider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts b/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts index 6aa102040..7c402ad54 100644 --- a/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts +++ b/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts @@ -1,7 +1,6 @@ import { Event } from '@theia/core/lib/common/event'; import * as WebSocket from 'ws'; -export const WebSocketProvider = Symbol('WebSocketProvider'); export interface WebSocketProvider { getAddress(): WebSocket.AddressInfo; sendMessage(message: string): void; From 397ca5665fc89b647f507e36b0191288308c83f6 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 15 Mar 2022 14:12:26 +0100 Subject: [PATCH 23/66] coreClientProvider is now set when constructing MonitorService --- arduino-ide-extension/src/node/monitor-manager.ts | 3 ++- arduino-ide-extension/src/node/monitor-service.ts | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 183aec77d..a51c40033 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -179,7 +179,8 @@ export class MonitorManager extends CoreClientAware { const monitor = new MonitorService( this.logger, board, - port + port, + this.coreClientProvider, ); monitor.onDispose((() => { this.monitorServices.delete(monitorID); diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index e5a1069d5..8d221c6ac 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -3,7 +3,7 @@ import { Disposable, Emitter, ILogger } from "@theia/core"; import { inject, named } from "@theia/core/shared/inversify"; import { Board, Port, Status, MonitorSettings, Monitor } from "../common/protocol"; import { EnumerateMonitorPortSettingsRequest, EnumerateMonitorPortSettingsResponse, MonitorPortConfiguration, MonitorPortSetting, MonitorRequest, MonitorResponse } from "./cli-protocol/cc/arduino/cli/commands/v1/monitor_pb"; -import { CoreClientAware } from "./core-client-provider"; +import { CoreClientAware, CoreClientProvider } from "./core-client-provider"; import { WebSocketProvider } from "./web-socket/web-socket-provider"; import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb' import WebSocketProviderImpl from "./web-socket/web-socket-provider-impl"; @@ -46,6 +46,7 @@ export class MonitorService extends CoreClientAware implements Disposable { private readonly board: Board, private readonly port: Port, + protected readonly coreClientProvider: CoreClientProvider, ) { super(); @@ -98,6 +99,7 @@ export class MonitorService extends CoreClientAware implements Disposable { } this.logger.info("starting monitor"); + await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { client, instance } = coreClient; @@ -151,6 +153,7 @@ export class MonitorService extends CoreClientAware implements Disposable { this.startMessagesHandlers(); this.logger.info(`started monitor to ${this.port?.address} using ${this.port?.protocol}`) resolve(Status.OK); + return; } this.logger.warn(`failed starting monitor to ${this.port?.address} using ${this.port?.protocol}`) resolve(Status.NOT_CONNECTED); @@ -212,6 +215,7 @@ export class MonitorService extends CoreClientAware implements Disposable { if (!this.duplex) { return Status.NOT_CONNECTED; } + await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { instance } = coreClient; @@ -245,6 +249,7 @@ export class MonitorService extends CoreClientAware implements Disposable { * @returns a map of all the settings supported by the monitor */ private async portMonitorSettings(protocol: string, fqbn: string): Promise { + await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { client, instance } = coreClient; const req = new EnumerateMonitorPortSettingsRequest(); @@ -295,6 +300,7 @@ export class MonitorService extends CoreClientAware implements Disposable { if (!this.duplex) { return Status.NOT_CONNECTED; } + await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { instance } = coreClient; From a8d803e7c33e37a83e67c11d161662cfe19bc3bd Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 15 Mar 2022 14:13:34 +0100 Subject: [PATCH 24/66] Add missing binding --- arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 446bd09aa..fea0754bc 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -402,6 +402,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // Serial monitor bind(MonitorWidget).toSelf(); + bind(FrontendApplicationContribution).toService(MonitorModel); bind(MonitorModel).toSelf().inSingletonScope(); bindViewContribution(bind, MonitorViewContribution); bind(TabBarToolbarContribution).toService(MonitorViewContribution); From fbe8fb421a84fe461aa46adf0048e379632db3a1 Mon Sep 17 00:00:00 2001 From: Mark Sujew Date: Tue, 22 Mar 2022 15:10:20 +0100 Subject: [PATCH 25/66] Fix `MonitorManagerProxy` DI issue --- .../src/browser/arduino-ide-frontend-module.ts | 6 ++++-- .../src/browser/monitor-manager-proxy-client-impl.ts | 12 ++++++------ .../src/common/protocol/monitor-service.ts | 3 +++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index fea0754bc..5f003e7cb 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -152,7 +152,7 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, OutputChannelRegistryMainImpl, } from './theia/plugin-ext/output-channel-registry-main'; -import { ExecutableService, ExecutableServicePath, MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyPath } from '../common/protocol'; +import { ExecutableService, ExecutableServicePath, MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyFactory, MonitorManagerProxyPath } from '../common/protocol'; import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service'; import { ResponseServiceImpl } from './response-service-impl'; @@ -417,8 +417,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { } })); + bind(MonitorManagerProxyFactory).toFactory((context) => () => context.container.get(MonitorManagerProxy)) + bind(MonitorManagerProxy).toDynamicValue((context) => - WebSocketConnectionProvider.createProxy(context.container, MonitorManagerProxyPath) + WebSocketConnectionProvider.createProxy(context.container, MonitorManagerProxyPath, context.container.get(MonitorManagerProxyClient)) ).inSingletonScope(); // Monitor manager proxy client to receive and delegate pluggable monitors diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 7af890a4e..3666238a6 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -1,7 +1,7 @@ -import { Emitter, JsonRpcProxy, MessageService } from "@theia/core"; +import { Emitter, MessageService } from "@theia/core"; import { inject, injectable } from "@theia/core/shared/inversify"; import { Board, Port } from "../common/protocol"; -import { Monitor, MonitorManagerProxy, MonitorManagerProxyClient, MonitorSettings } from "../common/protocol/monitor-service"; +import { Monitor, MonitorManagerProxyClient, MonitorManagerProxyFactory, MonitorSettings } from "../common/protocol/monitor-service"; @injectable() export class MonitorManagerProxyClientImpl implements MonitorManagerProxyClient { @@ -26,8 +26,8 @@ export class MonitorManagerProxyClientImpl implements MonitorManagerProxyClient protected messageService: MessageService, // This is necessary to call the backend methods from the frontend - @inject(MonitorManagerProxy) - protected server: JsonRpcProxy + @inject(MonitorManagerProxyFactory) + protected server: MonitorManagerProxyFactory ) { } @@ -71,11 +71,11 @@ export class MonitorManagerProxyClientImpl implements MonitorManagerProxyClient } async startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise { - return this.server.startMonitor(board, port, settings); + return this.server().startMonitor(board, port, settings); } getCurrentSettings(board: Board, port: Port): MonitorSettings { - return this.server.getCurrentSettings(board, port); + return this.server().getCurrentSettings(board, port); } send(message: string): void { diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 71c4b7e65..71dcbc4c0 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -1,6 +1,9 @@ import { Event, JsonRpcServer } from "@theia/core"; import { Board, Port } from './boards-service'; +export const MonitorManagerProxyFactory = Symbol('MonitorManagerProxyFactory'); +export type MonitorManagerProxyFactory = () => MonitorManagerProxy; + export const MonitorManagerProxyPath = '/services/monitor-manager-proxy'; export const MonitorManagerProxy = Symbol('MonitorManagerProxy'); export interface MonitorManagerProxy extends JsonRpcServer { From eff960bb7f4f661ced36127409186e6e7291d54e Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 8 Apr 2022 15:42:52 +0200 Subject: [PATCH 26/66] fix monitor connection --- .../monitor-manager-proxy-client-impl.ts | 204 ++--- .../monitor/serial-monitor-send-input.tsx | 36 +- .../src/common/protocol/monitor-service.ts | 106 +-- .../src/node/monitor-manager.ts | 351 ++++----- .../src/node/monitor-service.ts | 709 +++++++++--------- 5 files changed, 750 insertions(+), 656 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 3666238a6..2a7b7cc9f 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -1,103 +1,123 @@ -import { Emitter, MessageService } from "@theia/core"; -import { inject, injectable } from "@theia/core/shared/inversify"; -import { Board, Port } from "../common/protocol"; -import { Monitor, MonitorManagerProxyClient, MonitorManagerProxyFactory, MonitorSettings } from "../common/protocol/monitor-service"; +import { Emitter, MessageService } from '@theia/core'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { Board, Port } from '../common/protocol'; +import { + Monitor, + MonitorManagerProxyClient, + MonitorManagerProxyFactory, + MonitorSettings, +} from '../common/protocol/monitor-service'; @injectable() -export class MonitorManagerProxyClientImpl implements MonitorManagerProxyClient { - // When pluggable monitor messages are received from the backend - // this event is triggered. - // Ideally a frontend component is connected to this event - // to update the UI. - protected readonly onMessagesReceivedEmitter = new Emitter<{ messages: string[] }>(); - readonly onMessagesReceived = this.onMessagesReceivedEmitter.event; - - // WebSocket used to handle pluggable monitor communication between - // frontend and backend. - private webSocket?: WebSocket; - private wsPort?: number; - - getWebSocketPort(): number | undefined { - return this.wsPort; +export class MonitorManagerProxyClientImpl + implements MonitorManagerProxyClient +{ + // When pluggable monitor messages are received from the backend + // this event is triggered. + // Ideally a frontend component is connected to this event + // to update the UI. + protected readonly onMessagesReceivedEmitter = new Emitter<{ + messages: string[]; + }>(); + readonly onMessagesReceived = this.onMessagesReceivedEmitter.event; + + protected readonly onWSConnectionChangedEmitter = new Emitter(); + readonly onWSConnectionChanged = this.onWSConnectionChangedEmitter.event; + + // WebSocket used to handle pluggable monitor communication between + // frontend and backend. + private webSocket?: WebSocket; + private wsPort?: number; + + getWebSocketPort(): number | undefined { + return this.wsPort; + } + + constructor( + @inject(MessageService) + protected messageService: MessageService, + + // This is necessary to call the backend methods from the frontend + @inject(MonitorManagerProxyFactory) + protected server: MonitorManagerProxyFactory + ) {} + + /** + * Connects a localhost WebSocket using the specified port. + * @param addressPort port of the WebSocket + */ + connect(addressPort: number): void { + if (this.webSocket) { + return; } - - constructor( - @inject(MessageService) - protected messageService: MessageService, - - // This is necessary to call the backend methods from the frontend - @inject(MonitorManagerProxyFactory) - protected server: MonitorManagerProxyFactory - ) { - + try { + this.webSocket = new WebSocket(`ws://localhost:${addressPort}`); + this.onWSConnectionChangedEmitter.fire(true); + } catch { + this.messageService.error('Unable to connect to websocket'); + return; } - /** - * Connects a localhost WebSocket using the specified port. - * @param addressPort port of the WebSocket - */ - connect(addressPort: number): void { - if (this.webSocket) { - return; - } - try { - this.webSocket = new WebSocket(`ws://localhost:${addressPort}`); - } catch { - this.messageService.error('Unable to connect to websocket'); - return; - } - - this.webSocket.onmessage = (res) => { - const messages = JSON.parse(res.data); - this.onMessagesReceivedEmitter.fire({ messages }); - } - this.wsPort = addressPort; + this.webSocket.onmessage = (res) => { + const messages = JSON.parse(res.data); + this.onMessagesReceivedEmitter.fire({ messages }); + }; + this.wsPort = addressPort; + } + + /** + * Disconnects the WebSocket if connected. + */ + disconnect(): void { + try { + this.webSocket?.close(); + this.webSocket = undefined; + this.onWSConnectionChangedEmitter.fire(false); + } catch { + this.messageService.error('Unable to close websocket'); } - - /** - * Disconnects the WebSocket if connected. - */ - disconnect(): void { - try { - this.webSocket?.close(); - this.webSocket = undefined; - } catch { - this.messageService.error('Unable to close websocket'); - } + } + + async isWSConnected(): Promise { + return !!this.webSocket; + } + + async startMonitor( + board: Board, + port: Port, + settings?: MonitorSettings + ): Promise { + return this.server().startMonitor(board, port, settings); + } + + getCurrentSettings(board: Board, port: Port): MonitorSettings { + return this.server().getCurrentSettings(board, port); + } + + send(message: string): void { + if (!this.webSocket) { + return; } - async isWSConnected(): Promise { - return !!this.webSocket; + this.webSocket.send( + JSON.stringify({ + command: Monitor.Command.SEND_MESSAGE, + data: message, + }) + ); + } + + changeSettings(settings: MonitorSettings): void { + if (!this.webSocket) { + return; } - async startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise { - return this.server().startMonitor(board, port, settings); - } - - getCurrentSettings(board: Board, port: Port): MonitorSettings { - return this.server().getCurrentSettings(board, port); - } - - send(message: string): void { - if (!this.webSocket) { - return; - } - - this.webSocket.send(JSON.stringify({ - command: Monitor.Command.SEND_MESSAGE, - data: message, - })); - } - - changeSettings(settings: MonitorSettings): void { - if (!this.webSocket) { - return; - } - - this.webSocket.send(JSON.stringify({ - command: Monitor.Command.CHANGE_SETTINGS, - // TODO: This might be wrong, verify if it works - data: settings, - })); - } + this.webSocket.send( + JSON.stringify({ + command: Monitor.Command.CHANGE_SETTINGS, + // TODO: This might be wrong, verify if it works + data: settings, + }) + ); + } } diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx index ed545fc86..59ad9c9ad 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx @@ -5,6 +5,7 @@ import { isOSX } from '@theia/core/lib/common/os'; import { DisposableCollection, nls } from '@theia/core/lib/common'; import { MonitorManagerProxyClient } from '../../../common/protocol'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; +import { timeout } from '@theia/core/lib/common/promise-util'; export namespace SerialMonitorSendInput { export interface Props { @@ -27,16 +28,33 @@ export class SerialMonitorSendInput extends React.Component< constructor(props: Readonly) { super(props); - this.state = { text: '', connected: false }; + this.state = { text: '', connected: true }; this.onChange = this.onChange.bind(this); this.onSend = this.onSend.bind(this); this.onKeyDown = this.onKeyDown.bind(this); } componentDidMount(): void { - this.props.monitorManagerProxy.isWSConnected().then((connected) => { - this.setState({ connected }); + this.setState({ connected: true }); + + const checkWSConnection = new Promise((resolve) => { + this.props.monitorManagerProxy.onWSConnectionChanged((connected) => { + this.setState({ connected }); + resolve(true); + }); }); + + const checkWSTimeout = timeout(1000).then(() => false); + + Promise.race([checkWSConnection, checkWSTimeout]).then( + async (resolved) => { + if (!resolved) { + const connected = + await this.props.monitorManagerProxy.isWSConnected(); + this.setState({ connected }); + } + } + ); } componentWillUnmount(): void { @@ -49,7 +67,7 @@ export class SerialMonitorSendInput extends React.Component< MonitorManagerProxy; export const MonitorManagerProxyPath = '/services/monitor-manager-proxy'; export const MonitorManagerProxy = Symbol('MonitorManagerProxy'); -export interface MonitorManagerProxy extends JsonRpcServer { - startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise; - changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise; - stopMonitor(board: Board, port: Port): Promise; - getCurrentSettings(board: Board, port: Port): MonitorSettings; +export interface MonitorManagerProxy + extends JsonRpcServer { + startMonitor( + board: Board, + port: Port, + settings?: MonitorSettings + ): Promise; + changeMonitorSettings( + board: Board, + port: Port, + settings: MonitorSettings + ): Promise; + stopMonitor(board: Board, port: Port): Promise; + getCurrentSettings(board: Board, port: Port): MonitorSettings; } export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); export interface MonitorManagerProxyClient { - onMessagesReceived: Event<{ messages: string[] }>; - connect(addressPort: number): void; - disconnect(): void; - getWebSocketPort(): number | undefined; - isWSConnected(): Promise; - startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise; - getCurrentSettings(board: Board, port: Port): MonitorSettings; - send(message: string): void; - changeSettings(settings: MonitorSettings): void + onMessagesReceived: Event<{ messages: string[] }>; + onWSConnectionChanged: Event; + connect(addressPort: number): void; + disconnect(): void; + getWebSocketPort(): number | undefined; + isWSConnected(): Promise; + startMonitor( + board: Board, + port: Port, + settings?: MonitorSettings + ): Promise; + getCurrentSettings(board: Board, port: Port): MonitorSettings; + send(message: string): void; + changeSettings(settings: MonitorSettings): void; } export interface MonitorSetting { - // The setting identifier - readonly id: string; - // A human-readable label of the setting (to be displayed on the GUI) - readonly label: string; - // The setting type (at the moment only "enum" is avaiable) - readonly type: string; - // The values allowed on "enum" types - readonly values: string[]; - // The selected value - selectedValue: string; + // The setting identifier + readonly id: string; + // A human-readable label of the setting (to be displayed on the GUI) + readonly label: string; + // The setting type (at the moment only "enum" is avaiable) + readonly type: string; + // The values allowed on "enum" types + readonly values: string[]; + // The selected value + selectedValue: string; } export type MonitorSettings = Record; export namespace Monitor { - export enum Command { - SEND_MESSAGE = 'MONITOR_SEND_MESSAGE', - CHANGE_SETTINGS = 'MONITOR_CHANGE_SETTINGS', - } + export enum Command { + SEND_MESSAGE = 'MONITOR_SEND_MESSAGE', + CHANGE_SETTINGS = 'MONITOR_CHANGE_SETTINGS', + } - export type Message = { - command: Monitor.Command, - data: string; - } + export type Message = { + command: Monitor.Command; + data: string; + }; } -export interface Status { } +export interface Status {} export type OK = Status; export interface ErrorStatus extends Status { - readonly message: string; + readonly message: string; } export namespace Status { - export function isOK(status: Status & { message?: string }): status is OK { - return !!status && typeof status.message !== 'string'; - } - export const OK: OK = {}; - export const NOT_CONNECTED: ErrorStatus = { message: 'Not connected.' }; - export const ALREADY_CONNECTED: ErrorStatus = { - message: 'Already connected.', - }; - export const CONFIG_MISSING: ErrorStatus = { - message: 'Serial Config missing.', - }; + export function isOK(status: Status & { message?: string }): status is OK { + return !!status && typeof status.message !== 'string'; + } + export const OK: OK = {}; + export const NOT_CONNECTED: ErrorStatus = { message: 'Not connected.' }; + export const ALREADY_CONNECTED: ErrorStatus = { + message: 'Already connected.', + }; + export const CONFIG_MISSING: ErrorStatus = { + message: 'Serial Config missing.', + }; } diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index a51c40033..fa8b708f7 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -1,8 +1,8 @@ -import { ILogger } from "@theia/core"; -import { inject, injectable, named } from "@theia/core/shared/inversify"; -import { Board, Port, Status, MonitorSettings } from "../common/protocol"; -import { CoreClientAware } from "./core-client-provider"; -import { MonitorService } from "./monitor-service"; +import { ILogger } from '@theia/core'; +import { inject, injectable, named } from '@theia/core/shared/inversify'; +import { Board, Port, Status, MonitorSettings } from '../common/protocol'; +import { CoreClientAware } from './core-client-provider'; +import { MonitorService } from './monitor-service'; type MonitorID = string; @@ -10,191 +10,194 @@ export const MonitorManagerName = 'monitor-manager'; @injectable() export class MonitorManager extends CoreClientAware { - // Map of monitor services that manage the running pluggable monitors. - // Each service handles the lifetime of one, and only one, monitor. - // If either the board or port managed changes a new service must - // be started. - private monitorServices = new Map(); + // Map of monitor services that manage the running pluggable monitors. + // Each service handles the lifetime of one, and only one, monitor. + // If either the board or port managed changes a new service must + // be started. + private monitorServices = new Map(); - constructor( - @inject(ILogger) - @named(MonitorManagerName) - protected readonly logger: ILogger, - ) { - super(); - } + constructor( + @inject(ILogger) + @named(MonitorManagerName) + protected readonly logger: ILogger + ) { + super(); + } - /** - * Used to know if a monitor is started - * @param board board connected to port - * @param port port to monitor - * @returns true if the monitor is currently monitoring the board/port - * combination specifed, false in all other cases. - */ - isStarted(board: Board, port: Port): boolean { - const monitorID = this.monitorID(board, port); - const monitor = this.monitorServices.get(monitorID); - if (monitor) { - return monitor.isStarted(); - } - return false; + /** + * Used to know if a monitor is started + * @param board board connected to port + * @param port port to monitor + * @returns true if the monitor is currently monitoring the board/port + * combination specifed, false in all other cases. + */ + isStarted(board: Board, port: Port): boolean { + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (monitor) { + return monitor.isStarted(); } + return false; + } - /** - * Start a pluggable monitor that receives and sends messages - * to the specified board and port combination. - * @param board board connected to port - * @param port port to monitor - * @returns a Status object to know if the process has been - * started or if there have been errors. - */ - async startMonitor(board: Board, port: Port): Promise { - const monitorID = this.monitorID(board, port); - let monitor = this.monitorServices.get(monitorID); - if (!monitor) { - monitor = this.createMonitor(board, port) - } - return await monitor.start(); + /** + * Start a pluggable monitor that receives and sends messages + * to the specified board and port combination. + * @param board board connected to port + * @param port port to monitor + * @returns a Status object to know if the process has been + * started or if there have been errors. + */ + async startMonitor(board: Board, port: Port): Promise { + const monitorID = this.monitorID(board, port); + let monitor = this.monitorServices.get(monitorID); + if (!monitor) { + monitor = this.createMonitor(board, port); } + return await monitor.start(); + } - /** - * Stop a pluggable monitor connected to the specified board/port - * combination. It's a noop if monitor is not running. - * @param board board connected to port - * @param port port monitored - */ - async stopMonitor(board: Board, port: Port): Promise { - const monitorID = this.monitorID(board, port); - const monitor = this.monitorServices.get(monitorID); - if (!monitor) { - // There's no monitor to stop, bail - return; - } - return await monitor.stop(); + /** + * Stop a pluggable monitor connected to the specified board/port + * combination. It's a noop if monitor is not running. + * @param board board connected to port + * @param port port monitored + */ + async stopMonitor(board: Board, port: Port): Promise { + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + // There's no monitor to stop, bail + return; } + return await monitor.stop(); + } - /** - * Returns the port of the WebSocket used by the MonitorService - * that is handling the board/port combination - * @param board board connected to port - * @param port port to monitor - * @returns port of the MonitorService's WebSocket - */ - getWebsocketAddressPort(board: Board, port: Port): number { - const monitorID = this.monitorID(board, port); - const monitor = this.monitorServices.get(monitorID); - if (!monitor) { - return -1; - } - return monitor.getWebsocketAddressPort(); + /** + * Returns the port of the WebSocket used by the MonitorService + * that is handling the board/port combination + * @param board board connected to port + * @param port port to monitor + * @returns port of the MonitorService's WebSocket + */ + getWebsocketAddressPort(board: Board, port: Port): number { + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + return -1; } + return monitor.getWebsocketAddressPort(); + } - /** - * Notifies the monitor service of that board/port combination - * that an upload process started on that exact board/port combination. - * This must be done so that we can stop the monitor for the time being - * until the upload process finished. - * @param board board connected to port - * @param port port to monitor - */ - async notifyUploadStarted(board?: Board, port?: Port): Promise { - if (!board || !port) { - // We have no way of knowing which monitor - // to retrieve if we don't have this information. - return; - } - const monitorID = this.monitorID(board, port); - const monitor = this.monitorServices.get(monitorID); - if (!monitor) { - // There's no monitor running there, bail - return; - } - return await monitor.pause(); + /** + * Notifies the monitor service of that board/port combination + * that an upload process started on that exact board/port combination. + * This must be done so that we can stop the monitor for the time being + * until the upload process finished. + * @param board board connected to port + * @param port port to monitor + */ + async notifyUploadStarted(board?: Board, port?: Port): Promise { + if (!board || !port) { + // We have no way of knowing which monitor + // to retrieve if we don't have this information. + return; } - - /** - * Notifies the monitor service of that board/port combination - * that an upload process started on that exact board/port combination. - * @param board board connected to port - * @param port port to monitor - * @returns a Status object to know if the process has been - * started or if there have been errors. - */ - async notifyUploadFinished(board?: Board, port?: Port): Promise { - if (!board || !port) { - // We have no way of knowing which monitor - // to retrieve if we don't have this information. - return Status.NOT_CONNECTED; - } - const monitorID = this.monitorID(board, port); - const monitor = this.monitorServices.get(monitorID); - if (!monitor) { - // There's no monitor running there, bail - return Status.NOT_CONNECTED; - } - return await monitor.start(); + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + // There's no monitor running there, bail + return; } + return await monitor.pause(); + } - /** - * Changes the settings of a pluggable monitor even if it's running. - * If monitor is not running they're going to be used as soon as it's started. - * @param board board connected to port - * @param port port to monitor - * @param settings monitor settings to change - */ - changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings) { - const monitorID = this.monitorID(board, port); - let monitor = this.monitorServices.get(monitorID); - if (!monitor) { - monitor = this.createMonitor(board, port) - monitor.changeSettings(settings); - } + /** + * Notifies the monitor service of that board/port combination + * that an upload process started on that exact board/port combination. + * @param board board connected to port + * @param port port to monitor + * @returns a Status object to know if the process has been + * started or if there have been errors. + */ + async notifyUploadFinished(board?: Board, port?: Port): Promise { + if (!board || !port) { + // We have no way of knowing which monitor + // to retrieve if we don't have this information. + return Status.NOT_CONNECTED; } - - /** - * Returns the settings currently used by the pluggable monitor - * that's communicating with the specified board/port combination. - * @param board board connected to port - * @param port port monitored - * @returns map of current monitor settings - */ - currentMonitorSettings(board: Board, port: Port): MonitorSettings { - const monitorID = this.monitorID(board, port); - const monitor = this.monitorServices.get(monitorID); - if (!monitor) { - return {}; - } - return monitor.currentSettings(); + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + // There's no monitor running there, bail + return Status.NOT_CONNECTED; } + return await monitor.start(); + } - /** - * Creates a MonitorService that handles the lifetime and the - * communication via WebSocket with the frontend. - * @param board board connected to specified port - * @param port port to monitor - * @returns a new instance of MonitorService ready to use. - */ - private createMonitor(board: Board, port: Port): MonitorService { - const monitorID = this.monitorID(board, port); - const monitor = new MonitorService( - this.logger, - board, - port, - this.coreClientProvider, - ); - monitor.onDispose((() => { - this.monitorServices.delete(monitorID); - }).bind(this)); - return monitor + /** + * Changes the settings of a pluggable monitor even if it's running. + * If monitor is not running they're going to be used as soon as it's started. + * @param board board connected to port + * @param port port to monitor + * @param settings monitor settings to change + */ + changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings) { + const monitorID = this.monitorID(board, port); + let monitor = this.monitorServices.get(monitorID); + if (!monitor) { + monitor = this.createMonitor(board, port); + monitor.changeSettings(settings); } + } - /** - * Utility function to create a unique ID for a monitor service. - * @param board - * @param port - * @returns a unique monitor ID - */ - private monitorID(board: Board, port: Port): MonitorID { - return `${board.fqbn}-${port.address}-${port.protocol}`; + /** + * Returns the settings currently used by the pluggable monitor + * that's communicating with the specified board/port combination. + * @param board board connected to port + * @param port port monitored + * @returns map of current monitor settings + */ + currentMonitorSettings(board: Board, port: Port): MonitorSettings { + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + return {}; } -} \ No newline at end of file + return monitor.currentSettings(); + } + + /** + * Creates a MonitorService that handles the lifetime and the + * communication via WebSocket with the frontend. + * @param board board connected to specified port + * @param port port to monitor + * @returns a new instance of MonitorService ready to use. + */ + private createMonitor(board: Board, port: Port): MonitorService { + const monitorID = this.monitorID(board, port); + const monitor = new MonitorService( + this.logger, + board, + port, + this.coreClientProvider + ); + this.monitorServices.set(monitorID, monitor); + monitor.onDispose( + (() => { + this.monitorServices.delete(monitorID); + }).bind(this) + ); + return monitor; + } + + /** + * Utility function to create a unique ID for a monitor service. + * @param board + * @param port + * @returns a unique monitor ID + */ + private monitorID(board: Board, port: Port): MonitorID { + return `${board.fqbn}-${port.address}-${port.protocol}`; + } +} diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 8d221c6ac..35f77f45c 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -1,365 +1,398 @@ -import { ClientDuplexStream } from "@grpc/grpc-js"; -import { Disposable, Emitter, ILogger } from "@theia/core"; -import { inject, named } from "@theia/core/shared/inversify"; -import { Board, Port, Status, MonitorSettings, Monitor } from "../common/protocol"; -import { EnumerateMonitorPortSettingsRequest, EnumerateMonitorPortSettingsResponse, MonitorPortConfiguration, MonitorPortSetting, MonitorRequest, MonitorResponse } from "./cli-protocol/cc/arduino/cli/commands/v1/monitor_pb"; -import { CoreClientAware, CoreClientProvider } from "./core-client-provider"; -import { WebSocketProvider } from "./web-socket/web-socket-provider"; -import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb' -import WebSocketProviderImpl from "./web-socket/web-socket-provider-impl"; +import { ClientDuplexStream } from '@grpc/grpc-js'; +import { Disposable, Emitter, ILogger } from '@theia/core'; +import { inject, named } from '@theia/core/shared/inversify'; +import { + Board, + Port, + Status, + MonitorSettings, + Monitor, +} from '../common/protocol'; +import { + EnumerateMonitorPortSettingsRequest, + EnumerateMonitorPortSettingsResponse, + MonitorPortConfiguration, + MonitorPortSetting, + MonitorRequest, + MonitorResponse, +} from './cli-protocol/cc/arduino/cli/commands/v1/monitor_pb'; +import { CoreClientAware, CoreClientProvider } from './core-client-provider'; +import { WebSocketProvider } from './web-socket/web-socket-provider'; +import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb'; +import WebSocketProviderImpl from './web-socket/web-socket-provider-impl'; export const MonitorServiceName = 'monitor-service'; export class MonitorService extends CoreClientAware implements Disposable { - // Bidirectional gRPC stream used to receive and send data from the running - // pluggable monitor managed by the Arduino CLI. - protected duplex: ClientDuplexStream | null; - - // Settings used by the currently running pluggable monitor. - // They can be freely modified while running. - protected settings: MonitorSettings; - - // List of messages received from the running pluggable monitor. - // These are flushed from time to time to the frontend. - protected messages: string[] = []; - - // Handles messages received from the frontend via websocket. - protected onMessageReceived?: Disposable; - - // Sends messages to the frontend from time to time. - protected flushMessagesInterval?: NodeJS.Timeout; - - // Triggered each time the number of clients connected - // to the this service WebSocket changes. - protected onWSClientsNumberChanged?: Disposable; - - // Used to notify that the monitor is being disposed - protected readonly onDisposeEmitter = new Emitter(); - readonly onDispose = this.onDisposeEmitter.event; - - protected readonly webSocketProvider: WebSocketProvider = new WebSocketProviderImpl(); - - constructor( - @inject(ILogger) - @named(MonitorServiceName) - protected readonly logger: ILogger, - - private readonly board: Board, - private readonly port: Port, - protected readonly coreClientProvider: CoreClientProvider, - ) { - super(); - - this.onWSClientsNumberChanged = this.webSocketProvider.onClientsNumberChanged(async (clients: number) => { - if (clients === 0) { - // There are no more clients that want to receive - // data from this monitor, we can freely close - // and dispose it. - this.dispose(); - } - }); - - // Sets default settings for this monitor - this.portMonitorSettings(port.protocol, board.fqbn!).then( - settings => this.settings = settings - ); + // Bidirectional gRPC stream used to receive and send data from the running + // pluggable monitor managed by the Arduino CLI. + protected duplex: ClientDuplexStream | null; + + // Settings used by the currently running pluggable monitor. + // They can be freely modified while running. + protected settings: MonitorSettings; + + // List of messages received from the running pluggable monitor. + // These are flushed from time to time to the frontend. + protected messages: string[] = []; + + // Handles messages received from the frontend via websocket. + protected onMessageReceived?: Disposable; + + // Sends messages to the frontend from time to time. + protected flushMessagesInterval?: NodeJS.Timeout; + + // Triggered each time the number of clients connected + // to the this service WebSocket changes. + protected onWSClientsNumberChanged?: Disposable; + + // Used to notify that the monitor is being disposed + protected readonly onDisposeEmitter = new Emitter(); + readonly onDispose = this.onDisposeEmitter.event; + + protected readonly webSocketProvider: WebSocketProvider = + new WebSocketProviderImpl(); + + constructor( + @inject(ILogger) + @named(MonitorServiceName) + protected readonly logger: ILogger, + + private readonly board: Board, + private readonly port: Port, + protected readonly coreClientProvider: CoreClientProvider + ) { + super(); + + this.onWSClientsNumberChanged = + this.webSocketProvider.onClientsNumberChanged(async (clients: number) => { + if (clients === 0) { + // There are no more clients that want to receive + // data from this monitor, we can freely close + // and dispose it. + this.dispose(); + } + }); + + // Sets default settings for this monitor + this.portMonitorSettings(port.protocol, board.fqbn!).then( + (settings) => (this.settings = settings) + ); + } + + getWebsocketAddressPort(): number { + return this.webSocketProvider.getAddress().port; + } + + dispose(): void { + this.stop(); + this.onDisposeEmitter.fire(); + } + + /** + * isStarted is used to know if the currently running pluggable monitor is started. + * @returns true if pluggable monitor communication duplex is open, + * false in all other cases. + */ + isStarted(): boolean { + return !!this.duplex; + } + + /** + * Start and connects a monitor using currently set board and port. + * If a monitor is already started or board fqbn, port address and/or protocol + * are missing nothing happens. + * @returns a status to verify connection has been established. + */ + async start(): Promise { + if (this.duplex) { + return Status.ALREADY_CONNECTED; } - getWebsocketAddressPort(): number { - return this.webSocketProvider.getAddress().port; + if (!this.board?.fqbn || !this.port?.address || !this.port?.protocol) { + return Status.CONFIG_MISSING; } - dispose(): void { - this.stop(); - this.onDisposeEmitter.fire(); - } + this.logger.info('starting monitor'); + await this.coreClientProvider.initialized; + const coreClient = await this.coreClient(); + const { client, instance } = coreClient; - /** - * isStarted is used to know if the currently running pluggable monitor is started. - * @returns true if pluggable monitor communication duplex is open, - * false in all other cases. - */ - isStarted(): boolean { - return !!this.duplex; + this.duplex = client.monitor(); + this.duplex + .on('close', () => { + this.logger.info( + `monitor to ${this.port?.address} using ${this.port?.protocol} closed by client` + ); + }) + .on('end', () => { + this.logger.info( + `monitor to ${this.port?.address} using ${this.port?.protocol} closed by server` + ); + }) + .on('error', (err: Error) => { + this.logger.error(err); + // TODO + // this.theiaFEClient?.notifyError() + }) + .on( + 'data', + ((res: MonitorResponse) => { + if (res.getError()) { + // TODO: Maybe disconnect + this.logger.error(res.getError()); + return; + } + const data = res.getRxData(); + const message = + typeof data === 'string' + ? data + : new TextDecoder('utf8').decode(data); + this.messages.push(...splitLines(message)); + }).bind(this) + ); + + const req = new MonitorRequest(); + req.setInstance(instance); + if (this.board?.fqbn) { + req.setFqbn(this.board.fqbn); } - - /** - * Start and connects a monitor using currently set board and port. - * If a monitor is already started or board fqbn, port address and/or protocol - * are missing nothing happens. - * @returns a status to verify connection has been established. - */ - async start(): Promise { - if (this.duplex) { - return Status.ALREADY_CONNECTED; - } - - if (!this.board?.fqbn || !this.port?.address || !this.port?.protocol) { - return Status.CONFIG_MISSING - } - - this.logger.info("starting monitor"); - await this.coreClientProvider.initialized; - const coreClient = await this.coreClient(); - const { client, instance } = coreClient; - - this.duplex = client.monitor() - this.duplex - .on('close', () => { - this.logger.info(`monitor to ${this.port?.address} using ${this.port?.protocol} closed by client`) - }) - .on('end', () => { - this.logger.info(`monitor to ${this.port?.address} using ${this.port?.protocol} closed by server`) - }) - .on('error', (err: Error) => { - this.logger.error(err); - // TODO - // this.theiaFEClient?.notifyError() - }) - .on('data', ((res: MonitorResponse) => { - if (res.getError()) { - // TODO: Maybe disconnect - this.logger.error(res.getError()); - return; - } - const data = res.getRxData() - const message = - typeof data === 'string' ? data : new TextDecoder('utf8').decode(data); - this.messages.push(...splitLines(message)) - }).bind(this)); - - const req = new MonitorRequest(); - req.setInstance(instance); - if (this.board?.fqbn) { - req.setFqbn(this.board.fqbn) - } - if (this.port?.address && this.port?.protocol) { - const port = new gRPCPort() - port.setAddress(this.port.address); - port.setProtocol(this.port.protocol); - req.setPort(port); - } - const config = new MonitorPortConfiguration(); - for (const id in this.settings) { - const s = new MonitorPortSetting(); - s.setSettingId(id); - s.setValue(this.settings[id].selectedValue); - config.addSettings(s); - } - req.setPortConfiguration(config) - - const connect = new Promise(resolve => { - if (this.duplex?.write(req)) { - this.startMessagesHandlers(); - this.logger.info(`started monitor to ${this.port?.address} using ${this.port?.protocol}`) - resolve(Status.OK); - return; - } - this.logger.warn(`failed starting monitor to ${this.port?.address} using ${this.port?.protocol}`) - resolve(Status.NOT_CONNECTED); - }); - - const connectTimeout = new Promise(resolve => { - setTimeout(async () => { - this.logger.warn(`timeout starting monitor to ${this.port?.address} using ${this.port?.protocol}`) - resolve(Status.NOT_CONNECTED); - }, 1000); - }); - // Try opening a monitor connection with a timeout - return await Promise.race([ - connect, - connectTimeout, - ]) + if (this.port?.address && this.port?.protocol) { + const port = new gRPCPort(); + port.setAddress(this.port.address); + port.setProtocol(this.port.protocol); + req.setPort(port); } - - /** - * Pauses the currently running monitor, it still closes the gRPC connection - * with the underlying monitor process but it doesn't stop the message handlers - * currently running. - * This is mainly used to handle upload when to the board/port combination - * the monitor is listening to. - * @returns - */ - async pause(): Promise { - return new Promise(resolve => { - if (!this.duplex) { - this.logger.warn(`monitor to ${this.port?.address} using ${this.port?.protocol} already stopped`) - return resolve(); - } - // It's enough to close the connection with the client - // to stop the monitor process - this.duplex.cancel(); - this.duplex = null; - this.logger.info(`stopped monitor to ${this.port?.address} using ${this.port?.protocol}`) - resolve(); - }) + const config = new MonitorPortConfiguration(); + for (const id in this.settings) { + const s = new MonitorPortSetting(); + s.setSettingId(id); + s.setValue(this.settings[id].selectedValue); + config.addSettings(s); } + req.setPortConfiguration(config); - /** - * Stop the monitor currently running - */ - async stop(): Promise { - return this.pause().finally( - this.stopMessagesHandlers + const connect = new Promise((resolve) => { + if (this.duplex?.write(req)) { + this.startMessagesHandlers(); + this.logger.info( + `started monitor to ${this.port?.address} using ${this.port?.protocol}` + ); + resolve(Status.OK); + return; + } + this.logger.warn( + `failed starting monitor to ${this.port?.address} using ${this.port?.protocol}` + ); + resolve(Status.NOT_CONNECTED); + }); + + const connectTimeout = new Promise((resolve) => { + setTimeout(async () => { + this.logger.warn( + `timeout starting monitor to ${this.port?.address} using ${this.port?.protocol}` ); + resolve(Status.NOT_CONNECTED); + }, 1000); + }); + // Try opening a monitor connection with a timeout + return await Promise.race([connect, connectTimeout]); + } + + /** + * Pauses the currently running monitor, it still closes the gRPC connection + * with the underlying monitor process but it doesn't stop the message handlers + * currently running. + * This is mainly used to handle upload when to the board/port combination + * the monitor is listening to. + * @returns + */ + async pause(): Promise { + return new Promise(async (resolve) => { + if (!this.duplex) { + this.logger.warn( + `monitor to ${this.port?.address} using ${this.port?.protocol} already stopped` + ); + return resolve(); + } + // It's enough to close the connection with the client + // to stop the monitor process + this.duplex.end(); + this.duplex = null; + this.logger.info( + `stopped monitor to ${this.port?.address} using ${this.port?.protocol}` + ); + resolve(); + }); + } + + /** + * Stop the monitor currently running + */ + async stop(): Promise { + return this.pause().finally(this.stopMessagesHandlers.bind(this)); + } + + /** + * Send a message to the running monitor, a well behaved monitor + * will then send that message to the board. + * We MUST NEVER send a message that wasn't a user's input to the board. + * @param message string sent to running monitor + * @returns a status to verify message has been sent. + */ + async send(message: string): Promise { + if (!this.duplex) { + return Status.NOT_CONNECTED; } - - /** - * Send a message to the running monitor, a well behaved monitor - * will then send that message to the board. - * We MUST NEVER send a message that wasn't a user's input to the board. - * @param message string sent to running monitor - * @returns a status to verify message has been sent. - */ - async send(message: string): Promise { - if (!this.duplex) { - return Status.NOT_CONNECTED; - } - await this.coreClientProvider.initialized; - const coreClient = await this.coreClient(); - const { instance } = coreClient; - - const req = new MonitorRequest(); - req.setInstance(instance); - req.setTxData(new TextEncoder().encode(message)); - return new Promise(resolve => { - if (this.duplex) { - this.duplex?.write(req, () => { - resolve(Status.OK); - }); - return; - } - this.stop().then(() => resolve(Status.NOT_CONNECTED)); - }) + await this.coreClientProvider.initialized; + const coreClient = await this.coreClient(); + const { instance } = coreClient; + + const req = new MonitorRequest(); + req.setInstance(instance); + req.setTxData(new TextEncoder().encode(message)); + return new Promise((resolve) => { + if (this.duplex) { + this.duplex?.write(req, () => { + resolve(Status.OK); + }); + return; + } + this.stop().then(() => resolve(Status.NOT_CONNECTED)); + }); + } + + /** + * + * @returns map of current monitor settings + */ + currentSettings(): MonitorSettings { + return this.settings; + } + + /** + * Returns the possible configurations used to connect a monitor + * to the board specified by fqbn using the specified protocol + * @param protocol the protocol of the monitor we want get settings for + * @param fqbn the fqbn of the board we want to monitor + * @returns a map of all the settings supported by the monitor + */ + private async portMonitorSettings( + protocol: string, + fqbn: string + ): Promise { + await this.coreClientProvider.initialized; + const coreClient = await this.coreClient(); + const { client, instance } = coreClient; + const req = new EnumerateMonitorPortSettingsRequest(); + req.setInstance(instance); + req.setPortProtocol(protocol); + req.setFqbn(fqbn); + + const res = await new Promise( + (resolve, reject) => { + client.enumerateMonitorPortSettings(req, (err, resp) => { + if (!!err) { + reject(err); + } + resolve(resp); + }); + } + ); + + const settings: MonitorSettings = {}; + for (const iterator of res.getSettingsList()) { + settings[iterator.getSettingId()] = { + id: iterator.getSettingId(), + label: iterator.getLabel(), + type: iterator.getType(), + values: iterator.getEnumValuesList(), + selectedValue: iterator.getValue(), + }; } - - /** - * - * @returns map of current monitor settings - */ - currentSettings(): MonitorSettings { - return this.settings; + return settings; + } + + /** + * Set monitor settings, if there is a running monitor they'll be sent + * to it, otherwise they'll be used when starting one. + * Only values in settings parameter will be change, other values won't + * be changed in any way. + * @param settings map of monitor settings to change + * @returns a status to verify settings have been sent. + */ + async changeSettings(settings: MonitorSettings): Promise { + const config = new MonitorPortConfiguration(); + for (const id in settings) { + const s = new MonitorPortSetting(); + s.setSettingId(id); + s.setValue(settings[id].selectedValue); + config.addSettings(s); + this.settings[id] = settings[id]; } - /** - * Returns the possible configurations used to connect a monitor - * to the board specified by fqbn using the specified protocol - * @param protocol the protocol of the monitor we want get settings for - * @param fqbn the fqbn of the board we want to monitor - * @returns a map of all the settings supported by the monitor - */ - private async portMonitorSettings(protocol: string, fqbn: string): Promise { - await this.coreClientProvider.initialized; - const coreClient = await this.coreClient(); - const { client, instance } = coreClient; - const req = new EnumerateMonitorPortSettingsRequest(); - req.setInstance(instance); - req.setPortProtocol(protocol); - req.setFqbn(fqbn); - - const res = await new Promise((resolve, reject) => { - client.enumerateMonitorPortSettings(req, (err, resp) => { - if (!!err) { - reject(err) - } - resolve(resp) - }) - }); - - let settings: MonitorSettings = {}; - for (const iterator of res.getSettingsList()) { - settings[iterator.getSettingId()] = { - 'id': iterator.getSettingId(), - 'label': iterator.getLabel(), - 'type': iterator.getType(), - 'values': iterator.getEnumValuesList(), - 'selectedValue': iterator.getValue(), - } - } - return settings; + if (!this.duplex) { + return Status.NOT_CONNECTED; } - - /** - * Set monitor settings, if there is a running monitor they'll be sent - * to it, otherwise they'll be used when starting one. - * Only values in settings parameter will be change, other values won't - * be changed in any way. - * @param settings map of monitor settings to change - * @returns a status to verify settings have been sent. - */ - async changeSettings(settings: MonitorSettings): Promise { - const config = new MonitorPortConfiguration(); - for (const id in settings) { - const s = new MonitorPortSetting(); - s.setSettingId(id); - s.setValue(settings[id].selectedValue); - config.addSettings(s); - this.settings[id] = settings[id]; + await this.coreClientProvider.initialized; + const coreClient = await this.coreClient(); + const { instance } = coreClient; + + const req = new MonitorRequest(); + req.setInstance(instance); + req.setPortConfiguration(config); + this.duplex.write(req); + return Status.OK; + } + + /** + * Starts the necessary handlers to send and receive + * messages to and from the frontend and the running monitor + */ + private startMessagesHandlers(): void { + if (!this.flushMessagesInterval) { + const flushMessagesToFrontend = () => { + if (this.messages.length) { + this.webSocketProvider.sendMessage(JSON.stringify(this.messages)); + this.messages = []; } - - if (!this.duplex) { - return Status.NOT_CONNECTED; - } - await this.coreClientProvider.initialized; - const coreClient = await this.coreClient(); - const { instance } = coreClient; - - const req = new MonitorRequest(); - req.setInstance(instance); - req.setPortConfiguration(config) - this.duplex.write(req); - return Status.OK + }; + this.flushMessagesInterval = setInterval(flushMessagesToFrontend, 32); } - /** - * Starts the necessary handlers to send and receive - * messages to and from the frontend and the running monitor - */ - private startMessagesHandlers(): void { - if (!this.flushMessagesInterval) { - const flushMessagesToFrontend = () => { - if (this.messages.length) { - this.webSocketProvider.sendMessage(JSON.stringify(this.messages)); - this.messages = []; - } - }; - this.flushMessagesInterval = setInterval(flushMessagesToFrontend, 32); - } - - if (!this.onMessageReceived) { - this.onMessageReceived = this.webSocketProvider.onMessageReceived( - (msg: string) => { - const message: Monitor.Message = JSON.parse(msg); - - switch (message.command) { - case Monitor.Command.SEND_MESSAGE: - this.send(message.data); - break - case Monitor.Command.CHANGE_SETTINGS: - const settings: MonitorSettings = JSON.parse(message.data); - this.changeSettings(settings); - break - } - } - ) + if (!this.onMessageReceived) { + this.onMessageReceived = this.webSocketProvider.onMessageReceived( + (msg: string) => { + const message: Monitor.Message = JSON.parse(msg); + + switch (message.command) { + case Monitor.Command.SEND_MESSAGE: + this.send(message.data); + break; + case Monitor.Command.CHANGE_SETTINGS: + const settings: MonitorSettings = JSON.parse(message.data); + this.changeSettings(settings); + break; + } } + ); } - - /** - * Stops the necessary handlers to send and receive messages to - * and from the frontend and the running monitor - */ - private stopMessagesHandlers(): void { - if (this.flushMessagesInterval) { - clearInterval(this.flushMessagesInterval); - this.flushMessagesInterval = undefined; - } - if (this.onMessageReceived) { - this.onMessageReceived.dispose(); - this.onMessageReceived = undefined; - } + } + + /** + * Stops the necessary handlers to send and receive messages to + * and from the frontend and the running monitor + */ + private stopMessagesHandlers(): void { + if (this.flushMessagesInterval) { + clearInterval(this.flushMessagesInterval); + this.flushMessagesInterval = undefined; } - + if (this.onMessageReceived) { + this.onMessageReceived.dispose(); + this.onMessageReceived = undefined; + } + } } /** @@ -368,5 +401,5 @@ export class MonitorService extends CoreClientAware implements Disposable { * @returns an lines array */ function splitLines(s: string): string[] { - return s.split(/(?<=\n)/); + return s.split(/(?<=\n)/); } From 9b58c9d0c85da5d6160c4fe934c1c43549eb23e2 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 10 May 2022 15:53:48 +0200 Subject: [PATCH 27/66] delete duplex when connection is closed --- arduino-ide-extension/src/node/monitor-service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 35f77f45c..eabc39156 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -121,11 +121,13 @@ export class MonitorService extends CoreClientAware implements Disposable { this.duplex = client.monitor(); this.duplex .on('close', () => { + this.duplex = null; this.logger.info( `monitor to ${this.port?.address} using ${this.port?.protocol} closed by client` ); }) .on('end', () => { + this.duplex = null; this.logger.info( `monitor to ${this.port?.address} using ${this.port?.protocol} closed by server` ); @@ -218,7 +220,6 @@ export class MonitorService extends CoreClientAware implements Disposable { // It's enough to close the connection with the client // to stop the monitor process this.duplex.end(); - this.duplex = null; this.logger.info( `stopped monitor to ${this.port?.address} using ${this.port?.protocol}` ); From 62eaeb1c7429b988e666d05c575cbcc17e36bc0f Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 10 May 2022 15:54:04 +0200 Subject: [PATCH 28/66] update arduino-cli to 0.22.0 --- arduino-ide-extension/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 426665701..5ed069807 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -157,7 +157,7 @@ ], "arduino": { "cli": { - "version": "0.21.0" + "version": "0.22.0" }, "fwuploader": { "version": "2.0.0" From 1982609c87dfcdd6d9fe70b24c803516d04ac50c Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 12 May 2022 15:28:13 +0200 Subject: [PATCH 29/66] fix upload when monitor is open --- .../src/common/protocol/monitor-service.ts | 3 +++ .../src/node/monitor-manager.ts | 4 +++- .../src/node/monitor-service.ts | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index f33ac10a4..765b7dc21 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -84,4 +84,7 @@ export namespace Status { export const CONFIG_MISSING: ErrorStatus = { message: 'Serial Config missing.', }; + export const UPLOAD_IN_PROGRESS: ErrorStatus = { + message: 'Upload in progress.', + }; } diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index fa8b708f7..e13708186 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -12,7 +12,7 @@ export const MonitorManagerName = 'monitor-manager'; export class MonitorManager extends CoreClientAware { // Map of monitor services that manage the running pluggable monitors. // Each service handles the lifetime of one, and only one, monitor. - // If either the board or port managed changes a new service must + // If either the board or port managed changes, a new service must // be started. private monitorServices = new Map(); @@ -109,6 +109,7 @@ export class MonitorManager extends CoreClientAware { // There's no monitor running there, bail return; } + monitor.setUploadInProgress(true); return await monitor.pause(); } @@ -132,6 +133,7 @@ export class MonitorManager extends CoreClientAware { // There's no monitor running there, bail return Status.NOT_CONNECTED; } + monitor.setUploadInProgress(false); return await monitor.start(); } diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index eabc39156..94b980b41 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -53,6 +53,8 @@ export class MonitorService extends CoreClientAware implements Disposable { protected readonly webSocketProvider: WebSocketProvider = new WebSocketProviderImpl(); + protected uploadInProgress = false; + constructor( @inject(ILogger) @named(MonitorServiceName) @@ -80,6 +82,10 @@ export class MonitorService extends CoreClientAware implements Disposable { ); } + setUploadInProgress(status: boolean): void { + this.uploadInProgress = status; + } + getWebsocketAddressPort(): number { return this.webSocketProvider.getAddress().port; } @@ -113,11 +119,14 @@ export class MonitorService extends CoreClientAware implements Disposable { return Status.CONFIG_MISSING; } + if (this.uploadInProgress) { + return Status.UPLOAD_IN_PROGRESS; + } + this.logger.info('starting monitor'); await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { client, instance } = coreClient; - this.duplex = client.monitor(); this.duplex .on('close', () => { @@ -205,7 +214,7 @@ export class MonitorService extends CoreClientAware implements Disposable { * Pauses the currently running monitor, it still closes the gRPC connection * with the underlying monitor process but it doesn't stop the message handlers * currently running. - * This is mainly used to handle upload when to the board/port combination + * This is mainly used to handle upload with the board/port combination * the monitor is listening to. * @returns */ @@ -223,7 +232,8 @@ export class MonitorService extends CoreClientAware implements Disposable { this.logger.info( `stopped monitor to ${this.port?.address} using ${this.port?.protocol}` ); - resolve(); + + this.duplex.on('end', resolve); }); } From 7bf4ea06375e1bb6d6974c52df84890d21a61128 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 17 May 2022 17:45:47 +0200 Subject: [PATCH 30/66] add MonitorSettingsProvider interface --- .../monitor-manager-proxy-client-impl.ts | 2 +- .../src/browser/monitor-model.ts | 259 +++++++++--------- .../browser/serial/monitor/monitor-widget.tsx | 6 +- .../plotter/plotter-frontend-contribution.ts | 2 +- .../src/common/protocol/monitor-service.ts | 3 +- .../src/node/monitor-manager-proxy-impl.ts | 146 +++++----- .../src/node/monitor-manager.ts | 7 +- .../src/node/monitor-service.ts | 29 +- .../monitor-settings-provider.ts | 14 + 9 files changed, 250 insertions(+), 218 deletions(-) create mode 100644 arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 2a7b7cc9f..acde19986 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -5,8 +5,8 @@ import { Monitor, MonitorManagerProxyClient, MonitorManagerProxyFactory, - MonitorSettings, } from '../common/protocol/monitor-service'; +import { MonitorSettings } from '../node/monitor-settings/monitor-settings-provider'; @injectable() export class MonitorManagerProxyClientImpl diff --git a/arduino-ide-extension/src/browser/monitor-model.ts b/arduino-ide-extension/src/browser/monitor-model.ts index 5082e98ae..ccbd073c6 100644 --- a/arduino-ide-extension/src/browser/monitor-model.ts +++ b/arduino-ide-extension/src/browser/monitor-model.ts @@ -1,134 +1,139 @@ -import { Emitter, Event } from "@theia/core"; -import { FrontendApplicationContribution, LocalStorageService } from "@theia/core/lib/browser"; -import { inject, injectable } from "@theia/core/shared/inversify"; +import { Emitter, Event } from '@theia/core'; +import { + FrontendApplicationContribution, + LocalStorageService, +} from '@theia/core/lib/browser'; +import { inject, injectable } from '@theia/core/shared/inversify'; @injectable() export class MonitorModel implements FrontendApplicationContribution { - protected static STORAGE_ID = 'arduino-monitor-model'; - - @inject(LocalStorageService) - protected readonly localStorageService: LocalStorageService; - - protected readonly onChangeEmitter: Emitter>; - - protected _autoscroll: boolean; - protected _timestamp: boolean; - protected _lineEnding: MonitorModel.EOL; - protected _interpolate: boolean; - - constructor() { - this._autoscroll = true; - this._timestamp = false; - this._interpolate = false; - this._lineEnding = MonitorModel.EOL.DEFAULT; - - this.onChangeEmitter = new Emitter< - MonitorModel.State.Change - >(); - } - - onStart(): void { - this.localStorageService - .getData(MonitorModel.STORAGE_ID) - .then(this.restoreState); - } - - get onChange(): Event> { - return this.onChangeEmitter.event; - } - - protected restoreState(state: MonitorModel.State): void { - if (!state) { - return; - } - this._autoscroll = state.autoscroll; - this._timestamp = state.timestamp; - this._lineEnding = state.lineEnding; - this._interpolate = state.interpolate; - } - - protected async storeState(): Promise { - return this.localStorageService.setData(MonitorModel.STORAGE_ID, { - autoscroll: this._autoscroll, - timestamp: this._timestamp, - lineEnding: this._lineEnding, - interpolate: this._interpolate, - }); - } - - get autoscroll(): boolean { - return this._autoscroll; - } - - toggleAutoscroll(): void { - this._autoscroll = !this._autoscroll; - this.storeState().then(() => { - this.onChangeEmitter.fire({ - property: 'autoscroll', - value: this._timestamp - }); - }); - } - - get timestamp(): boolean { - return this._timestamp; - } - - toggleTimestamp(): void { - this._timestamp = !this._timestamp; - this.storeState().then(() => - this.onChangeEmitter.fire({ - property: 'timestamp', - value: this._timestamp, - }) - ); - } - - get lineEnding(): MonitorModel.EOL { - return this._lineEnding; - } - - set lineEnding(lineEnding: MonitorModel.EOL) { - this._lineEnding = lineEnding; - this.storeState().then(() => - this.onChangeEmitter.fire({ - property: 'lineEnding', - value: this._lineEnding, - }) - ); - } - - get interpolate(): boolean { - return this._interpolate; - } - - set interpolate(i: boolean) { - this._interpolate = i; - this.storeState().then(() => - this.onChangeEmitter.fire({ - property: 'interpolate', - value: this._interpolate, - }) - ); - } + protected static STORAGE_ID = 'arduino-monitor-model'; + + @inject(LocalStorageService) + protected readonly localStorageService: LocalStorageService; + + protected readonly onChangeEmitter: Emitter< + MonitorModel.State.Change + >; + + protected _autoscroll: boolean; + protected _timestamp: boolean; + protected _lineEnding: MonitorModel.EOL; + protected _interpolate: boolean; + + constructor() { + this._autoscroll = true; + this._timestamp = false; + this._interpolate = false; + this._lineEnding = MonitorModel.EOL.DEFAULT; + + this.onChangeEmitter = new Emitter< + MonitorModel.State.Change + >(); + } + + onStart(): void { + this.localStorageService + .getData(MonitorModel.STORAGE_ID) + .then(this.restoreState); + } + + get onChange(): Event> { + return this.onChangeEmitter.event; + } + + protected restoreState(state: MonitorModel.State): void { + if (!state) { + return; + } + this._autoscroll = state.autoscroll; + this._timestamp = state.timestamp; + this._lineEnding = state.lineEnding; + this._interpolate = state.interpolate; + } + + protected async storeState(): Promise { + return this.localStorageService.setData(MonitorModel.STORAGE_ID, { + autoscroll: this._autoscroll, + timestamp: this._timestamp, + lineEnding: this._lineEnding, + interpolate: this._interpolate, + }); + } + + get autoscroll(): boolean { + return this._autoscroll; + } + + toggleAutoscroll(): void { + this._autoscroll = !this._autoscroll; + this.storeState().then(() => { + this.onChangeEmitter.fire({ + property: 'autoscroll', + value: this._timestamp, + }); + }); + } + + get timestamp(): boolean { + return this._timestamp; + } + + toggleTimestamp(): void { + this._timestamp = !this._timestamp; + this.storeState().then(() => + this.onChangeEmitter.fire({ + property: 'timestamp', + value: this._timestamp, + }) + ); + } + + get lineEnding(): MonitorModel.EOL { + return this._lineEnding; + } + + set lineEnding(lineEnding: MonitorModel.EOL) { + this._lineEnding = lineEnding; + this.storeState().then(() => + this.onChangeEmitter.fire({ + property: 'lineEnding', + value: this._lineEnding, + }) + ); + } + + get interpolate(): boolean { + return this._interpolate; + } + + set interpolate(i: boolean) { + this._interpolate = i; + this.storeState().then(() => + this.onChangeEmitter.fire({ + property: 'interpolate', + value: this._interpolate, + }) + ); + } } export namespace MonitorModel { - export interface State { - autoscroll: boolean; - timestamp: boolean; - lineEnding: EOL; - interpolate: boolean; - } - export namespace State { - export interface Change { - readonly property: K; - readonly value: State[K]; - } - } - - export type EOL = '' | '\n' | '\r' | '\r\n'; - export namespace EOL { - export const DEFAULT: EOL = '\n'; - } + export interface State { + autoscroll: boolean; + timestamp: boolean; + lineEnding: EOL; + interpolate: boolean; + } + export namespace State { + export interface Change { + readonly property: K; + readonly value: State[K]; + } + } + + export type EOL = '' | '\n' | '\r' | '\r\n'; + export namespace EOL { + export const DEFAULT: EOL = '\n'; + } } diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index 43f3a3b27..9d9a08304 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -14,11 +14,9 @@ import { SerialMonitorSendInput } from './serial-monitor-send-input'; import { SerialMonitorOutput } from './serial-monitor-send-output'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { nls } from '@theia/core/lib/common'; -import { - MonitorManagerProxyClient, - MonitorSettings, -} from '../../../common/protocol'; +import { MonitorManagerProxyClient } from '../../../common/protocol'; import { MonitorModel } from '../../monitor-model'; +import { MonitorSettings } from '../../../node/monitor-settings/monitor-settings-provider'; @injectable() export class MonitorWidget extends ReactWidget { diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index 3f22f2071..1cc5e0f66 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -91,7 +91,7 @@ export class PlotterFrontendContribution extends Contribution { const settings = this.monitorManagerProxy.getCurrentSettings(board, port); if ('baudrate' in settings) { // Convert from string to numbers - baudrates = settings['baudrate'].values.map(b => +b); + baudrates = settings['baudrate'].values.map((b) => +b); currentBaudrate = +settings['baudrate'].selectedValue; } } diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 765b7dc21..4cfb76a39 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -1,4 +1,5 @@ import { Event, JsonRpcServer } from '@theia/core'; +import { MonitorSettings } from '../../node/monitor-settings/monitor-settings-provider'; import { Board, Port } from './boards-service'; export const MonitorManagerProxyFactory = Symbol('MonitorManagerProxyFactory'); @@ -53,8 +54,6 @@ export interface MonitorSetting { selectedValue: string; } -export type MonitorSettings = Record; - export namespace Monitor { export enum Command { SEND_MESSAGE = 'MONITOR_SEND_MESSAGE', diff --git a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts index e1fa1eefc..9e78274bb 100644 --- a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts +++ b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts @@ -1,80 +1,92 @@ -import { inject, injectable } from "@theia/core/shared/inversify"; -import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorSettings, Status } from "../common/protocol"; -import { Board, Port } from "../common/protocol"; -import { MonitorManager } from "./monitor-manager"; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { + MonitorManagerProxy, + MonitorManagerProxyClient, + Status, +} from '../common/protocol'; +import { Board, Port } from '../common/protocol'; +import { MonitorManager } from './monitor-manager'; +import { MonitorSettings } from './monitor-settings/monitor-settings-provider'; @injectable() export class MonitorManagerProxyImpl implements MonitorManagerProxy { - protected client: MonitorManagerProxyClient; + protected client: MonitorManagerProxyClient; - constructor( - @inject(MonitorManager) - protected readonly manager: MonitorManager, - ) { - } + constructor( + @inject(MonitorManager) + protected readonly manager: MonitorManager + ) {} - dispose(): void { - this.client?.disconnect(); - } + dispose(): void { + this.client?.disconnect(); + } - /** - * Start a pluggable monitor and/or change its settings. - * If settings are defined they'll be set before starting the monitor, - * otherwise default ones will be used by the monitor. - * @param board board connected to port - * @param port port to monitor - * @param settings map of supported configuration by the monitor - */ - async startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise { - if (settings) { - await this.changeMonitorSettings(board, port, settings); - } - const status = await this.manager.startMonitor(board, port); - if (status === Status.ALREADY_CONNECTED || status === Status.OK) { - // Monitor started correctly, connect it with the frontend - this.client.connect(this.manager.getWebsocketAddressPort(board, port)); - } + /** + * Start a pluggable monitor and/or change its settings. + * If settings are defined they'll be set before starting the monitor, + * otherwise default ones will be used by the monitor. + * @param board board connected to port + * @param port port to monitor + * @param settings map of supported configuration by the monitor + */ + async startMonitor( + board: Board, + port: Port, + settings?: MonitorSettings + ): Promise { + if (settings) { + await this.changeMonitorSettings(board, port, settings); } - - /** - * Changes the settings of a running pluggable monitor, if that monitor is not - * started this function is a noop. - * @param board board connected to port - * @param port port monitored - * @param settings map of supported configuration by the monitor - */ - async changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise { - if (!this.manager.isStarted(board, port)) { - // Monitor is not running, no need to change settings - return; - } - return this.manager.changeMonitorSettings(board, port, settings); + const status = await this.manager.startMonitor(board, port); + if (status === Status.ALREADY_CONNECTED || status === Status.OK) { + // Monitor started correctly, connect it with the frontend + this.client.connect(this.manager.getWebsocketAddressPort(board, port)); } + } - /** - * Stops a running pluggable monitor. - * @param board board connected to port - * @param port port monitored - */ - async stopMonitor(board: Board, port: Port): Promise { - return this.manager.stopMonitor(board, port); + /** + * Changes the settings of a running pluggable monitor, if that monitor is not + * started this function is a noop. + * @param board board connected to port + * @param port port monitored + * @param settings map of supported configuration by the monitor + */ + async changeMonitorSettings( + board: Board, + port: Port, + settings: MonitorSettings + ): Promise { + if (!this.manager.isStarted(board, port)) { + // Monitor is not running, no need to change settings + return; } + return this.manager.changeMonitorSettings(board, port, settings); + } - /** - * Returns the current settings by the pluggable monitor connected to specified - * by board/port combination. - * @param board board connected to port - * @param port port monitored - * @returns a map of MonitorSetting - */ - getCurrentSettings(board: Board, port: Port): MonitorSettings { - return this.manager.currentMonitorSettings(board, port); - } + /** + * Stops a running pluggable monitor. + * @param board board connected to port + * @param port port monitored + */ + async stopMonitor(board: Board, port: Port): Promise { + return this.manager.stopMonitor(board, port); + } + + /** + * Returns the current settings by the pluggable monitor connected to specified + * by board/port combination. + * @param board board connected to port + * @param port port monitored + * @returns a map of MonitorSetting + */ + getCurrentSettings(board: Board, port: Port): MonitorSettings { + return this.manager.currentMonitorSettings(board, port); + } - setClient(client: MonitorManagerProxyClient | undefined): void { - if (!client) { - return; - } - this.client = client; + setClient(client: MonitorManagerProxyClient | undefined): void { + if (!client) { + return; } -} \ No newline at end of file + this.client = client; + } +} diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index e13708186..d4a565577 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -1,8 +1,9 @@ import { ILogger } from '@theia/core'; import { inject, injectable, named } from '@theia/core/shared/inversify'; -import { Board, Port, Status, MonitorSettings } from '../common/protocol'; +import { Board, Port, Status } from '../common/protocol'; import { CoreClientAware } from './core-client-provider'; import { MonitorService } from './monitor-service'; +import { MonitorSettings } from './monitor-settings/monitor-settings-provider'; type MonitorID = string; @@ -54,7 +55,7 @@ export class MonitorManager extends CoreClientAware { if (!monitor) { monitor = this.createMonitor(board, port); } - return await monitor.start(); + return await monitor.start(monitorID); } /** @@ -134,7 +135,7 @@ export class MonitorManager extends CoreClientAware { return Status.NOT_CONNECTED; } monitor.setUploadInProgress(false); - return await monitor.start(); + return await monitor.start(monitorID); } /** diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 94b980b41..405fceaaa 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -1,13 +1,7 @@ import { ClientDuplexStream } from '@grpc/grpc-js'; import { Disposable, Emitter, ILogger } from '@theia/core'; import { inject, named } from '@theia/core/shared/inversify'; -import { - Board, - Port, - Status, - MonitorSettings, - Monitor, -} from '../common/protocol'; +import { Board, Port, Status, Monitor } from '../common/protocol'; import { EnumerateMonitorPortSettingsRequest, EnumerateMonitorPortSettingsResponse, @@ -20,6 +14,10 @@ import { CoreClientAware, CoreClientProvider } from './core-client-provider'; import { WebSocketProvider } from './web-socket/web-socket-provider'; import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb'; import WebSocketProviderImpl from './web-socket/web-socket-provider-impl'; +import { + MonitorSettings, + MonitorSettingsProvider, +} from './monitor-settings/monitor-settings-provider'; export const MonitorServiceName = 'monitor-service'; @@ -50,6 +48,10 @@ export class MonitorService extends CoreClientAware implements Disposable { protected readonly onDisposeEmitter = new Emitter(); readonly onDispose = this.onDisposeEmitter.event; + @inject(MonitorSettingsProvider) + protected readonly monitorSettingsProvider: MonitorSettingsProvider; + + // TODO: use dependency injection protected readonly webSocketProvider: WebSocketProvider = new WebSocketProviderImpl(); @@ -75,11 +77,6 @@ export class MonitorService extends CoreClientAware implements Disposable { this.dispose(); } }); - - // Sets default settings for this monitor - this.portMonitorSettings(port.protocol, board.fqbn!).then( - (settings) => (this.settings = settings) - ); } setUploadInProgress(status: boolean): void { @@ -108,9 +105,10 @@ export class MonitorService extends CoreClientAware implements Disposable { * Start and connects a monitor using currently set board and port. * If a monitor is already started or board fqbn, port address and/or protocol * are missing nothing happens. + * @param id * @returns a status to verify connection has been established. */ - async start(): Promise { + async start(monitorID: string): Promise { if (this.duplex) { return Status.ALREADY_CONNECTED; } @@ -124,6 +122,10 @@ export class MonitorService extends CoreClientAware implements Disposable { } this.logger.info('starting monitor'); + this.settings = await this.monitorSettingsProvider.init( + monitorID, + this.coreClientProvider + ); await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { client, instance } = coreClient; @@ -281,6 +283,7 @@ export class MonitorService extends CoreClientAware implements Disposable { return this.settings; } + // TODO: move this into MonitoSettingsProvider /** * Returns the possible configurations used to connect a monitor * to the board specified by fqbn using the specified protocol diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts new file mode 100644 index 000000000..959ff1ad8 --- /dev/null +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts @@ -0,0 +1,14 @@ +import { MonitorSetting } from '../../common/protocol'; +import { CoreClientProvider } from '../core-client-provider'; + +export type MonitorSettings = Record; + +export const MonitorSettingsProvider = Symbol('MonitorSettingsProvider'); +export interface MonitorSettingsProvider { + init( + id: string, + coreClientProvider: CoreClientProvider + ): Promise; + get(): Promise; + set(settings: MonitorSettings): Promise; +} From 355dec8aaa209f0ce247f3d98bf949fa8f751832 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Thu, 19 May 2022 10:40:01 +0200 Subject: [PATCH 31/66] monitor settings provider stub --- .../monitor-settings-provider-impl.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts new file mode 100644 index 000000000..87bb38ab2 --- /dev/null +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts @@ -0,0 +1,39 @@ +import { injectable } from 'inversify'; +import { CoreClientProvider } from '../core-client-provider'; +import { + MonitorSettings, + MonitorSettingsProvider, +} from './monitor-settings-provider'; + +@injectable() +export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { + init( + id: string, + coreClientProvider: CoreClientProvider + ): Promise { + throw new Error('Method not implemented.'); + + // query the CLI (via coreClientProvider) and return all available settings for the pluggable monitor. + // store these for later checkings + + // check for the settings file in the user's home directory + // if it doesn't exist, create it + + // if it does exist, start searching for the longest prefix matching the id + + // at the end of the search you can have a hit or a miss + + // if you have a miss, create a new entry with the id and all default settings coming from the CLI + + // if you have a hit, check if the existing settings are present in the settings from the CLI + // if they are not present in the CLI, remove from the settings file + // if there are settings in the CLI that are not in the file, add to the file with the default from the CLI + // save the updated settings file + } + get(): Promise { + throw new Error('Method not implemented.'); + } + set(settings: MonitorSettings): Promise { + throw new Error('Method not implemented.'); + } +} From 80ade4c37e1696ef161e3c0354849780660d6872 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Thu, 19 May 2022 10:59:20 +0200 Subject: [PATCH 32/66] updated pseudo code --- .../monitor-settings-provider-impl.ts | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts index 87bb38ab2..49d150ed3 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts @@ -7,33 +7,40 @@ import { @injectable() export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { + // this is populated with all settings coming from the CLI. This should never get modified + // as it is used to double actual values set by the user + private monitorSettings: MonitorSettings; + + // this contains values for setting of the monitorSettings + // the key is MonitorSetting.id, the value should be one of the MonitorSetting.values + private monitorSettingsValues: Record; + init( id: string, coreClientProvider: CoreClientProvider ): Promise { throw new Error('Method not implemented.'); - // query the CLI (via coreClientProvider) and return all available settings for the pluggable monitor. - // store these for later checkings - - // check for the settings file in the user's home directory - // if it doesn't exist, create it - - // if it does exist, start searching for the longest prefix matching the id + // 1. query the CLI (via coreClientProvider) and return all available settings for the pluggable monitor. + // store these in `monitorSettings` for later checkings - // at the end of the search you can have a hit or a miss - - // if you have a miss, create a new entry with the id and all default settings coming from the CLI - - // if you have a hit, check if the existing settings are present in the settings from the CLI - // if they are not present in the CLI, remove from the settings file - // if there are settings in the CLI that are not in the file, add to the file with the default from the CLI - // save the updated settings file + // 2. check for the settings file in the user's home directory + // a. if it doesn't exist, create it as an empty json file + // 3. search the file, looking for the longest prefix matching the id + // a. miss: populate `monitorSettingsValues` with all default settings from `monitorSettings` + // b. hit: populate `monitorSettingsValues` with the result for the search + // i. purge the `monitorSettingsValues` removing keys that are not defined in `monitorSettings` + // and adding those that are missing + // ii. save the `monitorSettingsValues` in the file, using the id as the key } get(): Promise { throw new Error('Method not implemented.'); } set(settings: MonitorSettings): Promise { throw new Error('Method not implemented.'); + + // 1. parse the settings parameter and remove any setting that is not defined in `monitorSettings` + // 2. update `monitorSettingsValues` accordingly + // 3. save it to the file } } From 0427759fdb98856ec588b0d3bb92c35a7e7e2ff3 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 19 May 2022 11:06:23 +0200 Subject: [PATCH 33/66] refactor monitor settings interfaces --- .../monitor-manager-proxy-client-impl.ts | 8 +++++-- .../src/browser/monitor-model.ts | 1 + .../browser/serial/monitor/monitor-widget.tsx | 23 +++++++++++-------- .../plotter/plotter-frontend-contribution.ts | 9 ++++---- .../src/common/protocol/monitor-service.ts | 15 +++++++----- .../src/node/monitor-manager-proxy-impl.ts | 8 +++---- .../src/node/monitor-manager.ts | 10 +++++--- .../src/node/monitor-service.ts | 16 +++++++------ .../monitor-settings-provider-impl.ts | 9 ++++---- .../monitor-settings-provider.ts | 15 ++++++++---- 10 files changed, 70 insertions(+), 44 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index acde19986..5a9293d49 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -6,7 +6,10 @@ import { MonitorManagerProxyClient, MonitorManagerProxyFactory, } from '../common/protocol/monitor-service'; -import { MonitorSettings } from '../node/monitor-settings/monitor-settings-provider'; +import { + PluggableMonitorSettings, + MonitorSettings, +} from '../node/monitor-settings/monitor-settings-provider'; @injectable() export class MonitorManagerProxyClientImpl @@ -85,7 +88,7 @@ export class MonitorManagerProxyClientImpl async startMonitor( board: Board, port: Port, - settings?: MonitorSettings + settings?: PluggableMonitorSettings ): Promise { return this.server().startMonitor(board, port, settings); } @@ -116,6 +119,7 @@ export class MonitorManagerProxyClientImpl JSON.stringify({ command: Monitor.Command.CHANGE_SETTINGS, // TODO: This might be wrong, verify if it works + // SPOILER: It doesn't data: settings, }) ); diff --git a/arduino-ide-extension/src/browser/monitor-model.ts b/arduino-ide-extension/src/browser/monitor-model.ts index ccbd073c6..41baca1ab 100644 --- a/arduino-ide-extension/src/browser/monitor-model.ts +++ b/arduino-ide-extension/src/browser/monitor-model.ts @@ -118,6 +118,7 @@ export class MonitorModel implements FrontendApplicationContribution { } } +// TODO: Move this to /common export namespace MonitorModel { export interface State { autoscroll: boolean; diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index 9d9a08304..ce098be68 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -182,20 +182,22 @@ export class MonitorWidget extends ReactWidget { // This breaks if the user tries to open a monitor that // doesn't support the baudrate setting. protected get baudRates(): string[] { - const settings = this.getCurrentSettings(); - const baudRateSettings = settings['baudrate']; - if (!baudRateSettings) { + const { pluggableMonitorSettings } = this.getCurrentSettings(); + if (!pluggableMonitorSettings || !pluggableMonitorSettings['baudrate']) { return []; } + + const baudRateSettings = pluggableMonitorSettings['baudrate']; + return baudRateSettings.values; } protected get selectedBaudRate(): string { - const settings = this.getCurrentSettings(); - const baudRateSettings = settings['baudrate']; - if (!baudRateSettings) { + const { pluggableMonitorSettings } = this.getCurrentSettings(); + if (!pluggableMonitorSettings || !pluggableMonitorSettings['baudrate']) { return ''; } + const baudRateSettings = pluggableMonitorSettings['baudrate']; return baudRateSettings.selectedValue; } @@ -260,8 +262,11 @@ export class MonitorWidget extends ReactWidget { }; protected readonly onChangeBaudRate = (value: string) => { - const settings = this.getCurrentSettings(); - settings['baudrate'].selectedValue = value; - this.monitorManagerProxy.changeSettings(settings); + const { pluggableMonitorSettings } = this.getCurrentSettings(); + if (!pluggableMonitorSettings || !pluggableMonitorSettings['baudrate']) + return; + const baudRateSettings = pluggableMonitorSettings['baudrate']; + baudRateSettings.selectedValue = value; + this.monitorManagerProxy.changeSettings(pluggableMonitorSettings); }; } diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index 1cc5e0f66..083477e05 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -88,11 +88,12 @@ export class PlotterFrontendContribution extends Contribution { let baudrates: number[] = []; let currentBaudrate = -1; if (board && port) { - const settings = this.monitorManagerProxy.getCurrentSettings(board, port); - if ('baudrate' in settings) { + const { pluggableMonitorSettings } = + this.monitorManagerProxy.getCurrentSettings(board, port); + if (pluggableMonitorSettings && 'baudrate' in pluggableMonitorSettings) { // Convert from string to numbers - baudrates = settings['baudrate'].values.map((b) => +b); - currentBaudrate = +settings['baudrate'].selectedValue; + baudrates = pluggableMonitorSettings['baudrate'].values.map((b) => +b); + currentBaudrate = +pluggableMonitorSettings['baudrate'].selectedValue; } } diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 4cfb76a39..18f67367e 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -1,5 +1,8 @@ import { Event, JsonRpcServer } from '@theia/core'; -import { MonitorSettings } from '../../node/monitor-settings/monitor-settings-provider'; +import { + PluggableMonitorSettings, + MonitorSettings, +} from '../../node/monitor-settings/monitor-settings-provider'; import { Board, Port } from './boards-service'; export const MonitorManagerProxyFactory = Symbol('MonitorManagerProxyFactory'); @@ -12,15 +15,15 @@ export interface MonitorManagerProxy startMonitor( board: Board, port: Port, - settings?: MonitorSettings + settings?: PluggableMonitorSettings ): Promise; changeMonitorSettings( board: Board, port: Port, - settings: MonitorSettings + settings: PluggableMonitorSettings ): Promise; stopMonitor(board: Board, port: Port): Promise; - getCurrentSettings(board: Board, port: Port): MonitorSettings; + getCurrentSettings(board: Board, port: Port): PluggableMonitorSettings; } export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); @@ -34,14 +37,14 @@ export interface MonitorManagerProxyClient { startMonitor( board: Board, port: Port, - settings?: MonitorSettings + settings?: PluggableMonitorSettings ): Promise; getCurrentSettings(board: Board, port: Port): MonitorSettings; send(message: string): void; changeSettings(settings: MonitorSettings): void; } -export interface MonitorSetting { +export interface PluggableMonitorSetting { // The setting identifier readonly id: string; // A human-readable label of the setting (to be displayed on the GUI) diff --git a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts index 9e78274bb..75ade7452 100644 --- a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts +++ b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts @@ -6,7 +6,7 @@ import { } from '../common/protocol'; import { Board, Port } from '../common/protocol'; import { MonitorManager } from './monitor-manager'; -import { MonitorSettings } from './monitor-settings/monitor-settings-provider'; +import { PluggableMonitorSettings } from './monitor-settings/monitor-settings-provider'; @injectable() export class MonitorManagerProxyImpl implements MonitorManagerProxy { @@ -32,7 +32,7 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { async startMonitor( board: Board, port: Port, - settings?: MonitorSettings + settings?: PluggableMonitorSettings ): Promise { if (settings) { await this.changeMonitorSettings(board, port, settings); @@ -54,7 +54,7 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { async changeMonitorSettings( board: Board, port: Port, - settings: MonitorSettings + settings: PluggableMonitorSettings ): Promise { if (!this.manager.isStarted(board, port)) { // Monitor is not running, no need to change settings @@ -79,7 +79,7 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { * @param port port monitored * @returns a map of MonitorSetting */ - getCurrentSettings(board: Board, port: Port): MonitorSettings { + getCurrentSettings(board: Board, port: Port): PluggableMonitorSettings { return this.manager.currentMonitorSettings(board, port); } diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index d4a565577..265f1f189 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -3,7 +3,7 @@ import { inject, injectable, named } from '@theia/core/shared/inversify'; import { Board, Port, Status } from '../common/protocol'; import { CoreClientAware } from './core-client-provider'; import { MonitorService } from './monitor-service'; -import { MonitorSettings } from './monitor-settings/monitor-settings-provider'; +import { PluggableMonitorSettings } from './monitor-settings/monitor-settings-provider'; type MonitorID = string; @@ -145,7 +145,11 @@ export class MonitorManager extends CoreClientAware { * @param port port to monitor * @param settings monitor settings to change */ - changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings) { + changeMonitorSettings( + board: Board, + port: Port, + settings: PluggableMonitorSettings + ) { const monitorID = this.monitorID(board, port); let monitor = this.monitorServices.get(monitorID); if (!monitor) { @@ -161,7 +165,7 @@ export class MonitorManager extends CoreClientAware { * @param port port monitored * @returns map of current monitor settings */ - currentMonitorSettings(board: Board, port: Port): MonitorSettings { + currentMonitorSettings(board: Board, port: Port): PluggableMonitorSettings { const monitorID = this.monitorID(board, port); const monitor = this.monitorServices.get(monitorID); if (!monitor) { diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 405fceaaa..1696756f7 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -15,7 +15,7 @@ import { WebSocketProvider } from './web-socket/web-socket-provider'; import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb'; import WebSocketProviderImpl from './web-socket/web-socket-provider-impl'; import { - MonitorSettings, + PluggableMonitorSettings, MonitorSettingsProvider, } from './monitor-settings/monitor-settings-provider'; @@ -28,7 +28,7 @@ export class MonitorService extends CoreClientAware implements Disposable { // Settings used by the currently running pluggable monitor. // They can be freely modified while running. - protected settings: MonitorSettings; + protected settings: PluggableMonitorSettings; // List of messages received from the running pluggable monitor. // These are flushed from time to time to the frontend. @@ -279,7 +279,7 @@ export class MonitorService extends CoreClientAware implements Disposable { * * @returns map of current monitor settings */ - currentSettings(): MonitorSettings { + currentSettings(): PluggableMonitorSettings { return this.settings; } @@ -294,7 +294,7 @@ export class MonitorService extends CoreClientAware implements Disposable { private async portMonitorSettings( protocol: string, fqbn: string - ): Promise { + ): Promise { await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { client, instance } = coreClient; @@ -314,7 +314,7 @@ export class MonitorService extends CoreClientAware implements Disposable { } ); - const settings: MonitorSettings = {}; + const settings: PluggableMonitorSettings = {}; for (const iterator of res.getSettingsList()) { settings[iterator.getSettingId()] = { id: iterator.getSettingId(), @@ -335,7 +335,7 @@ export class MonitorService extends CoreClientAware implements Disposable { * @param settings map of monitor settings to change * @returns a status to verify settings have been sent. */ - async changeSettings(settings: MonitorSettings): Promise { + async changeSettings(settings: PluggableMonitorSettings): Promise { const config = new MonitorPortConfiguration(); for (const id in settings) { const s = new MonitorPortSetting(); @@ -384,7 +384,9 @@ export class MonitorService extends CoreClientAware implements Disposable { this.send(message.data); break; case Monitor.Command.CHANGE_SETTINGS: - const settings: MonitorSettings = JSON.parse(message.data); + const settings: PluggableMonitorSettings = JSON.parse( + message.data + ); this.changeSettings(settings); break; } diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts index 49d150ed3..40dca6d71 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts @@ -1,8 +1,9 @@ import { injectable } from 'inversify'; import { CoreClientProvider } from '../core-client-provider'; import { - MonitorSettings, + PluggableMonitorSettings, MonitorSettingsProvider, + MonitorSettings, } from './monitor-settings-provider'; @injectable() @@ -18,7 +19,7 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { init( id: string, coreClientProvider: CoreClientProvider - ): Promise { + ): Promise { throw new Error('Method not implemented.'); // 1. query the CLI (via coreClientProvider) and return all available settings for the pluggable monitor. @@ -33,10 +34,10 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { // and adding those that are missing // ii. save the `monitorSettingsValues` in the file, using the id as the key } - get(): Promise { + get(): Promise { throw new Error('Method not implemented.'); } - set(settings: MonitorSettings): Promise { + set(settings: PluggableMonitorSettings): Promise { throw new Error('Method not implemented.'); // 1. parse the settings parameter and remove any setting that is not defined in `monitorSettings` diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts index 959ff1ad8..5ca5ad4a7 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts @@ -1,14 +1,19 @@ -import { MonitorSetting } from '../../common/protocol'; +import { MonitorModel } from '../../browser/monitor-model'; +import { PluggableMonitorSetting } from '../../common/protocol'; import { CoreClientProvider } from '../core-client-provider'; -export type MonitorSettings = Record; +export type PluggableMonitorSettings = Record; +export interface MonitorSettings { + pluggableMonitorSettings?: PluggableMonitorSettings; + monitorUISettings?: Partial; +} export const MonitorSettingsProvider = Symbol('MonitorSettingsProvider'); export interface MonitorSettingsProvider { init( id: string, coreClientProvider: CoreClientProvider - ): Promise; - get(): Promise; - set(settings: MonitorSettings): Promise; + ): Promise; + get(): Promise; + set(settings: PluggableMonitorSettings): Promise; } From a4ff05a82b20ffbbfca9f3cbf96f90035d27dc18 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Thu, 19 May 2022 16:56:37 +0200 Subject: [PATCH 34/66] monitor service provider singleton --- .../src/node/arduino-ide-backend-module.ts | 11 +- .../src/node/monitor-service.ts | 12 +- .../monitor-settings-provider-impl.ts | 162 ++++++++++++++---- .../monitor-settings-provider.ts | 13 +- 4 files changed, 157 insertions(+), 41 deletions(-) diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 9ecd1bdea..da1ef8a08 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -85,8 +85,14 @@ import { ArduinoLocalizationContribution } from './arduino-localization-contribu import { LocalizationContribution } from '@theia/core/lib/node/i18n/localization-contribution'; import { MonitorManagerProxyImpl } from './monitor-manager-proxy-impl'; import { MonitorManager, MonitorManagerName } from './monitor-manager'; -import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyPath } from '../common/protocol/monitor-service'; +import { + MonitorManagerProxy, + MonitorManagerProxyClient, + MonitorManagerProxyPath, +} from '../common/protocol/monitor-service'; import { MonitorServiceName } from './monitor-service'; +import { MonitorSettingsProvider } from './monitor-settings/monitor-settings-provider'; +import { MonitorSettingsProviderImpl } from './monitor-settings/monitor-settings-provider-impl'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -198,6 +204,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // a single MonitorManager is responsible for handling the actual connections to the pluggable monitors bind(MonitorManager).toSelf().inSingletonScope(); + bind(MonitorSettingsProviderImpl).toSelf().inSingletonScope(); + bind(MonitorSettingsProvider).toService(MonitorSettingsProviderImpl); + // Serial client provider per connected frontend. bind(ConnectionContainerModule).toConstantValue( ConnectionContainerModule.create(({ bind, bindBackendService }) => { diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 1696756f7..e3facecf6 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -122,10 +122,18 @@ export class MonitorService extends CoreClientAware implements Disposable { } this.logger.info('starting monitor'); - this.settings = await this.monitorSettingsProvider.init( + + // get default monitor settings from the CLI + const defaultSettings = await this.portMonitorSettings( + this.port.protocol, + this.board.fqbn + ); + // get actual settings from the settings provider + this.settings = await this.monitorSettingsProvider.getSettings( monitorID, - this.coreClientProvider + defaultSettings ); + await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); const { client, instance } = coreClient; diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts index 40dca6d71..54e3b5690 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts @@ -1,47 +1,145 @@ -import { injectable } from 'inversify'; -import { CoreClientProvider } from '../core-client-provider'; +import * as fs from 'fs'; +import { join } from 'path'; +import { injectable, inject, postConstruct } from 'inversify'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; +import { FileUri } from '@theia/core/lib/node/file-uri'; +import { promisify } from 'util'; + import { PluggableMonitorSettings, MonitorSettingsProvider, - MonitorSettings, } from './monitor-settings-provider'; +import { Deferred } from '@theia/core/lib/common/promise-util'; + +const MONITOR_SETTINGS_FILE = 'pluggable-monitor-settings.json'; @injectable() export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { - // this is populated with all settings coming from the CLI. This should never get modified - // as it is used to double actual values set by the user - private monitorSettings: MonitorSettings; + @inject(EnvVariablesServer) + protected readonly envVariablesServer: EnvVariablesServer; + + protected ready = new Deferred(); + + // this is populated with all settings coming from the CLI. This should never be modified + // // as it is used to double check the monitorSettings attribute + // private monitorDefaultSettings: PluggableMonitorSettings; + + // this contains actual values coming from the stored file and edited by the user + // this is a map with MonitorId as key and PluggableMonitorSetting as value + private monitorSettings: Record; + + private pluggableMonitorSettingsPath: string; + + @postConstruct() + protected async init(): Promise { + // get the monitor settings file path + const configDirUri = await this.envVariablesServer.getConfigDirUri(); + this.pluggableMonitorSettingsPath = join( + FileUri.fsPath(configDirUri), + MONITOR_SETTINGS_FILE + ); - // this contains values for setting of the monitorSettings - // the key is MonitorSetting.id, the value should be one of the MonitorSetting.values - private monitorSettingsValues: Record; + // read existing settings + this.readFile(); - init( - id: string, - coreClientProvider: CoreClientProvider + console.log(this.monitorSettings); + this.ready.resolve(); + } + + async getSettings( + monitorId: string, + defaultSettings: PluggableMonitorSettings ): Promise { - throw new Error('Method not implemented.'); - - // 1. query the CLI (via coreClientProvider) and return all available settings for the pluggable monitor. - // store these in `monitorSettings` for later checkings - - // 2. check for the settings file in the user's home directory - // a. if it doesn't exist, create it as an empty json file - // 3. search the file, looking for the longest prefix matching the id - // a. miss: populate `monitorSettingsValues` with all default settings from `monitorSettings` - // b. hit: populate `monitorSettingsValues` with the result for the search - // i. purge the `monitorSettingsValues` removing keys that are not defined in `monitorSettings` - // and adding those that are missing - // ii. save the `monitorSettingsValues` in the file, using the id as the key + // wait for the service to complete the init + await this.ready.promise; + + const { matchingSettings } = this.longestPrefixMatch(monitorId); + + return this.reconcileSettings(matchingSettings, defaultSettings); + } + async setSettings( + monitorId: string, + settings: PluggableMonitorSettings + ): Promise { + // wait for the service to complete the init + await this.ready.promise; + + const newSettings = this.reconcileSettings( + settings, + this.monitorSettings[monitorId] + ); + this.monitorSettings[monitorId] = newSettings; + + await this.writeFile(); + return newSettings; + } + + private reconcileSettings( + newSettings: PluggableMonitorSettings, + defaultSettings: PluggableMonitorSettings + ): PluggableMonitorSettings { + // TODO: implement + return newSettings; + } + + private async readFile(): Promise { + const rawJson = await promisify(fs.readFile)( + this.pluggableMonitorSettingsPath, + { + encoding: 'utf-8', + flag: 'a+', // a+ = append and read, creating the file if it doesn't exist + } + ); + + if (!rawJson) { + this.monitorSettings = {}; + } + + try { + this.monitorSettings = JSON.parse(rawJson); + } catch (error) { + console.error( + 'Could not parse the pluggable monitor settings file. Using empty file.' + ); + this.monitorSettings = {}; + } } - get(): Promise { - throw new Error('Method not implemented.'); + + private async writeFile() { + await promisify(fs.writeFile)( + this.pluggableMonitorSettingsPath, + JSON.stringify(this.monitorSettings) + ); } - set(settings: PluggableMonitorSettings): Promise { - throw new Error('Method not implemented.'); - // 1. parse the settings parameter and remove any setting that is not defined in `monitorSettings` - // 2. update `monitorSettingsValues` accordingly - // 3. save it to the file + private longestPrefixMatch(id: string): { + matchingPrefix: string; + matchingSettings: PluggableMonitorSettings; + } { + const separator = '-'; + const idTokens = id.split(separator); + + let matchingPrefix = ''; + let matchingSettings: PluggableMonitorSettings = {}; + + const monitorSettingsKeys = Object.keys(this.monitorSettings); + + for (let i = 0; i < idTokens.length; i++) { + const prefix = idTokens.slice(0, i + 1).join(separator); + + for (let k = 0; k < monitorSettingsKeys.length; k++) { + if (monitorSettingsKeys[k].startsWith(prefix)) { + matchingPrefix = prefix; + matchingSettings = this.monitorSettings[monitorSettingsKeys[k]]; + break; + } + } + + if (matchingPrefix.length) { + break; + } + } + + return { matchingPrefix, matchingSettings }; } } diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts index 5ca5ad4a7..e8949a60b 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider.ts @@ -1,6 +1,5 @@ import { MonitorModel } from '../../browser/monitor-model'; import { PluggableMonitorSetting } from '../../common/protocol'; -import { CoreClientProvider } from '../core-client-provider'; export type PluggableMonitorSettings = Record; export interface MonitorSettings { @@ -10,10 +9,12 @@ export interface MonitorSettings { export const MonitorSettingsProvider = Symbol('MonitorSettingsProvider'); export interface MonitorSettingsProvider { - init( - id: string, - coreClientProvider: CoreClientProvider + getSettings( + monitorId: string, + defaultSettings: PluggableMonitorSettings + ): Promise; + setSettings( + monitorId: string, + settings: PluggableMonitorSettings ): Promise; - get(): Promise; - set(settings: PluggableMonitorSettings): Promise; } From c0a9bbbc2266d351ef8c953ce83269000febef73 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Fri, 20 May 2022 14:00:55 +0200 Subject: [PATCH 35/66] add unit tests --- .../monitor-settings-provider-impl.ts | 51 ++--- .../monitor-settings-utils.ts | 81 ++++++++ .../test/node/monitor-settings-utils.test.ts | 195 ++++++++++++++++++ 3 files changed, 291 insertions(+), 36 deletions(-) create mode 100644 arduino-ide-extension/src/node/monitor-settings/monitor-settings-utils.ts create mode 100644 arduino-ide-extension/src/test/node/monitor-settings-utils.test.ts diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts index 54e3b5690..072000df1 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts @@ -4,12 +4,15 @@ import { injectable, inject, postConstruct } from 'inversify'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { FileUri } from '@theia/core/lib/node/file-uri'; import { promisify } from 'util'; - import { PluggableMonitorSettings, MonitorSettingsProvider, } from './monitor-settings-provider'; import { Deferred } from '@theia/core/lib/common/promise-util'; +import { + longestPrefixMatch, + reconcileSettings, +} from './monitor-settings-utils'; const MONITOR_SETTINGS_FILE = 'pluggable-monitor-settings.json'; @@ -18,16 +21,14 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { @inject(EnvVariablesServer) protected readonly envVariablesServer: EnvVariablesServer; + // deferred used to guarantee file operations are performed after the service is initialized protected ready = new Deferred(); - // this is populated with all settings coming from the CLI. This should never be modified - // // as it is used to double check the monitorSettings attribute - // private monitorDefaultSettings: PluggableMonitorSettings; - // this contains actual values coming from the stored file and edited by the user // this is a map with MonitorId as key and PluggableMonitorSetting as value private monitorSettings: Record; + // this is the path to the pluggable monitor settings file, set during init private pluggableMonitorSettingsPath: string; @postConstruct() @@ -40,9 +41,11 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { ); // read existing settings - this.readFile(); + this.readSettingsFromFS(); console.log(this.monitorSettings); + + // init is done, resolve the deferred and unblock any call that was waiting for it this.ready.resolve(); } @@ -57,6 +60,7 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { return this.reconcileSettings(matchingSettings, defaultSettings); } + async setSettings( monitorId: string, settings: PluggableMonitorSettings @@ -70,7 +74,7 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { ); this.monitorSettings[monitorId] = newSettings; - await this.writeFile(); + await this.writeSettingsToFS(); return newSettings; } @@ -78,11 +82,10 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { newSettings: PluggableMonitorSettings, defaultSettings: PluggableMonitorSettings ): PluggableMonitorSettings { - // TODO: implement - return newSettings; + return reconcileSettings(newSettings, defaultSettings); } - private async readFile(): Promise { + private async readSettingsFromFS(): Promise { const rawJson = await promisify(fs.readFile)( this.pluggableMonitorSettingsPath, { @@ -105,7 +108,7 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { } } - private async writeFile() { + private async writeSettingsToFS(): Promise { await promisify(fs.writeFile)( this.pluggableMonitorSettingsPath, JSON.stringify(this.monitorSettings) @@ -116,30 +119,6 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { matchingPrefix: string; matchingSettings: PluggableMonitorSettings; } { - const separator = '-'; - const idTokens = id.split(separator); - - let matchingPrefix = ''; - let matchingSettings: PluggableMonitorSettings = {}; - - const monitorSettingsKeys = Object.keys(this.monitorSettings); - - for (let i = 0; i < idTokens.length; i++) { - const prefix = idTokens.slice(0, i + 1).join(separator); - - for (let k = 0; k < monitorSettingsKeys.length; k++) { - if (monitorSettingsKeys[k].startsWith(prefix)) { - matchingPrefix = prefix; - matchingSettings = this.monitorSettings[monitorSettingsKeys[k]]; - break; - } - } - - if (matchingPrefix.length) { - break; - } - } - - return { matchingPrefix, matchingSettings }; + return longestPrefixMatch(id, this.monitorSettings); } } diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-utils.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-utils.ts new file mode 100644 index 000000000..3bcfc5775 --- /dev/null +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-utils.ts @@ -0,0 +1,81 @@ +import { PluggableMonitorSettings } from './monitor-settings-provider'; + +export function longestPrefixMatch( + id: string, + monitorSettings: Record +): { + matchingPrefix: string; + matchingSettings: PluggableMonitorSettings; +} { + const separator = '-'; + const idTokens = id.split(separator); + + let matchingPrefix = ''; + let matchingSettings: PluggableMonitorSettings = {}; + + const monitorSettingsKeys = Object.keys(monitorSettings); + + for (let i = idTokens.length - 1; i >= 0; i--) { + const prefix = idTokens.slice(0, i + 1).join(separator); + + for (let k = 0; k < monitorSettingsKeys.length; k++) { + if (monitorSettingsKeys[k].startsWith(prefix)) { + matchingPrefix = prefix; + matchingSettings = monitorSettings[monitorSettingsKeys[k]]; + break; + } + } + + if (matchingPrefix.length) { + break; + } + } + + return { matchingPrefix, matchingSettings }; +} + +export function reconcileSettings( + newSettings: PluggableMonitorSettings, + defaultSettings: PluggableMonitorSettings +): PluggableMonitorSettings { + // create a map with all the keys, merged together + const mergedSettingsKeys = Object.keys({ + ...defaultSettings, + ...newSettings, + }); + + // for every key in the settings, we need to check if it exist in the default + for (const key of mergedSettingsKeys) { + // remove from the newSettings if it was not found in the default + if (defaultSettings[key] === undefined) { + delete newSettings[key]; + } + // add to the newSettings if it was missing + else if (newSettings[key] === undefined) { + newSettings[key] = defaultSettings[key]; + } + // if the key is found in both, reconcile the settings + else { + // save the value set by the user + const value = newSettings[key].selectedValue; + + // settings needs to be overwritten with the defaults + newSettings[key] = defaultSettings[key]; + + // if there are no valid values defined, assume the one selected by the user is valid + // also use the value if it is a valid setting defined in the values + if ( + !Array.isArray(newSettings[key].values) || + newSettings[key].values.length === 0 || + newSettings[key].values.includes(value) + ) { + newSettings[key].selectedValue = value; + } else { + // if there are valid values but the user selected one that is not valid, fallback to the first valid one + newSettings[key].selectedValue = newSettings[key].values[0]; + } + } + } + + return newSettings; +} diff --git a/arduino-ide-extension/src/test/node/monitor-settings-utils.test.ts b/arduino-ide-extension/src/test/node/monitor-settings-utils.test.ts new file mode 100644 index 000000000..f3a8c56b4 --- /dev/null +++ b/arduino-ide-extension/src/test/node/monitor-settings-utils.test.ts @@ -0,0 +1,195 @@ +import { expect } from 'chai'; +import { + longestPrefixMatch, + reconcileSettings, +} from '../../node/monitor-settings/monitor-settings-utils'; +import { PluggableMonitorSettings } from '../../node/monitor-settings/monitor-settings-provider'; + +type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; + +// use(require('chai-string')); + +describe('longestPrefixMatch', () => { + const settings = { + 'arduino:avr:uno-port1-protocol1': { + name: 'Arduino Uno', + }, + 'arduino:avr:due-port1-protocol2': { + name: 'Arduino Due', + }, + }; + + it('should return the exact prefix when found', async () => { + const prefix = 'arduino:avr:uno-port1-protocol1'; + + const { matchingPrefix } = longestPrefixMatch( + prefix, + settings as unknown as Record + ); + + expect(matchingPrefix).to.equal(prefix); + }); + + it('should return the exact object when the prefix match', async () => { + const prefix = 'arduino:avr:uno-port1-protocol1'; + + const { matchingSettings } = longestPrefixMatch( + prefix, + settings as unknown as Record + ); + + expect(matchingSettings).to.have.property('name').to.equal('Arduino Uno'); + }); + + it('should return a partial matching prefix when a similar object is found', async () => { + const prefix = 'arduino:avr:due-port2-protocol2'; + + const { matchingPrefix } = longestPrefixMatch( + prefix, + settings as unknown as Record + ); + + expect(matchingPrefix).to.equal('arduino:avr:due'); + }); + + it('should return the closest object when the prefix partially match', async () => { + const prefix = 'arduino:avr:uno-port1-protocol2'; + + const { matchingSettings } = longestPrefixMatch( + prefix, + settings as unknown as Record + ); + + expect(matchingSettings).to.have.property('name').to.equal('Arduino Uno'); + }); + + it('should return an empty matching prefix when no similar object is found', async () => { + const prefix = 'arduino:avr:tre-port2-protocol2'; + + const { matchingPrefix } = longestPrefixMatch( + prefix, + settings as unknown as Record + ); + + expect(matchingPrefix).to.equal(''); + }); + + it('should return an empty object when no similar object is found', async () => { + const prefix = 'arduino:avr:tre-port1-protocol2'; + + const { matchingSettings } = longestPrefixMatch( + prefix, + settings as unknown as Record + ); + + expect(matchingSettings).to.be.empty; + }); +}); + +describe('reconcileSettings', () => { + const defaultSettings = { + setting1: { + id: 'setting1', + label: 'Label setting1', + type: 'enum', + values: ['a', 'b', 'c'], + selectedValue: 'b', + }, + setting2: { + id: 'setting2', + label: 'Label setting2', + type: 'enum', + values: ['a', 'b', 'c'], + selectedValue: 'b', + }, + setting3: { + id: 'setting3', + label: 'Label setting3', + type: 'enum', + values: ['a', 'b', 'c'], + selectedValue: 'b', + }, + }; + + it('should return default settings if new settings are missing', async () => { + const newSettings: PluggableMonitorSettings = {}; + + const reconciledSettings = reconcileSettings(newSettings, defaultSettings); + + expect(reconciledSettings).to.deep.equal(defaultSettings); + }); + + it('should add missing attributes copying it from the default settings', async () => { + const newSettings: PluggableMonitorSettings = JSON.parse( + JSON.stringify(defaultSettings) + ); + delete newSettings.setting2; + + const reconciledSettings = reconcileSettings(newSettings, defaultSettings); + + expect(reconciledSettings).to.have.property('setting2'); + }); + it('should remove wrong settings attributes using the default settings as a reference', async () => { + const newSettings: PluggableMonitorSettings = JSON.parse( + JSON.stringify(defaultSettings) + ); + newSettings['setting4'] = defaultSettings.setting3; + + const reconciledSettings = reconcileSettings(newSettings, defaultSettings); + + expect(reconciledSettings).not.to.have.property('setting4'); + }); + it('should reset non-value fields to those defiend in the default settings', async () => { + const newSettings: DeepWriteable = JSON.parse( + JSON.stringify(defaultSettings) + ); + newSettings['setting2'].id = 'fake id'; + + const reconciledSettings = reconcileSettings(newSettings, defaultSettings); + + expect(reconciledSettings.setting2) + .to.have.property('id') + .equal('setting2'); + }); + it('should accept a selectedValue if it is a valid one', async () => { + const newSettings: PluggableMonitorSettings = JSON.parse( + JSON.stringify(defaultSettings) + ); + newSettings.setting2.selectedValue = 'c'; + + const reconciledSettings = reconcileSettings(newSettings, defaultSettings); + + expect(reconciledSettings.setting2) + .to.have.property('selectedValue') + .to.equal('c'); + }); + it('should fall a back to the first valid setting when the selectedValue is not valid', async () => { + const newSettings: PluggableMonitorSettings = JSON.parse( + JSON.stringify(defaultSettings) + ); + newSettings.setting2.selectedValue = 'z'; + + const reconciledSettings = reconcileSettings(newSettings, defaultSettings); + + expect(reconciledSettings.setting2) + .to.have.property('selectedValue') + .to.equal('a'); + }); + it('should accept any value if default values are not set', async () => { + const wrongDefaults: DeepWriteable = JSON.parse( + JSON.stringify(defaultSettings) + ); + wrongDefaults.setting2.values = []; + + const newSettings: PluggableMonitorSettings = JSON.parse( + JSON.stringify(wrongDefaults) + ); + newSettings.setting2.selectedValue = 'z'; + + const reconciledSettings = reconcileSettings(newSettings, wrongDefaults); + + expect(reconciledSettings.setting2) + .to.have.property('selectedValue') + .to.equal('z'); + }); +}); From 9a16cf9e02a17630693e7bf83058861fd5b908b5 Mon Sep 17 00:00:00 2001 From: David Simpson <45690499+davegarthsimpson@users.noreply.github.com> Date: Fri, 20 May 2022 17:53:34 +0200 Subject: [PATCH 36/66] change MonitorService providers to injectable deps --- .../src/node/arduino-ide-backend-module.ts | 37 ++++++++++++++++++- .../src/node/monitor-manager.ts | 11 ++++-- .../src/node/monitor-service-factory.ts | 18 +++++++++ .../src/node/monitor-service.ts | 12 ++---- .../monitor-settings-provider-impl.ts | 2 +- .../web-socket/web-socket-provider-impl.ts | 2 + .../node/web-socket/web-socket-provider.ts | 1 + 7 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 arduino-ide-extension/src/node/monitor-service-factory.ts diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index da1ef8a08..50cc4d16c 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -90,9 +90,15 @@ import { MonitorManagerProxyClient, MonitorManagerProxyPath, } from '../common/protocol/monitor-service'; -import { MonitorServiceName } from './monitor-service'; +import { MonitorService, MonitorServiceName } from './monitor-service'; import { MonitorSettingsProvider } from './monitor-settings/monitor-settings-provider'; import { MonitorSettingsProviderImpl } from './monitor-settings/monitor-settings-provider-impl'; +import { + MonitorServiceFactory, + MonitorServiceFactoryOptions, +} from './monitor-service-factory'; +import WebSocketProviderImpl from './web-socket/web-socket-provider-impl'; +import { WebSocketProvider } from './web-socket/web-socket-provider'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -204,9 +210,38 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // a single MonitorManager is responsible for handling the actual connections to the pluggable monitors bind(MonitorManager).toSelf().inSingletonScope(); + // monitor service & factory bindings bind(MonitorSettingsProviderImpl).toSelf().inSingletonScope(); bind(MonitorSettingsProvider).toService(MonitorSettingsProviderImpl); + bind(WebSocketProviderImpl).toSelf(); + bind(WebSocketProvider).toService(WebSocketProviderImpl); + + bind(MonitorServiceFactory).toFactory( + ({ container }) => + (options: MonitorServiceFactoryOptions) => { + const logger = container.get(ILogger); + + const monitorSettingsProvider = container.get( + MonitorSettingsProvider + ); + + const webSocketProvider = + container.get(WebSocketProvider); + + const { board, port, coreClientProvider } = options; + + return new MonitorService( + logger, + monitorSettingsProvider, + webSocketProvider, + board, + port, + coreClientProvider + ); + } + ); + // Serial client provider per connected frontend. bind(ConnectionContainerModule).toConstantValue( ConnectionContainerModule.create(({ bind, bindBackendService }) => { diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 265f1f189..02b79274a 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -3,6 +3,7 @@ import { inject, injectable, named } from '@theia/core/shared/inversify'; import { Board, Port, Status } from '../common/protocol'; import { CoreClientAware } from './core-client-provider'; import { MonitorService } from './monitor-service'; +import { MonitorServiceFactory } from './monitor-service-factory'; import { PluggableMonitorSettings } from './monitor-settings/monitor-settings-provider'; type MonitorID = string; @@ -17,6 +18,9 @@ export class MonitorManager extends CoreClientAware { // be started. private monitorServices = new Map(); + @inject(MonitorServiceFactory) + private monitorServiceFactory: MonitorServiceFactory; + constructor( @inject(ILogger) @named(MonitorManagerName) @@ -183,12 +187,11 @@ export class MonitorManager extends CoreClientAware { */ private createMonitor(board: Board, port: Port): MonitorService { const monitorID = this.monitorID(board, port); - const monitor = new MonitorService( - this.logger, + const monitor = this.monitorServiceFactory({ board, port, - this.coreClientProvider - ); + coreClientProvider: this.coreClientProvider, + }); this.monitorServices.set(monitorID, monitor); monitor.onDispose( (() => { diff --git a/arduino-ide-extension/src/node/monitor-service-factory.ts b/arduino-ide-extension/src/node/monitor-service-factory.ts new file mode 100644 index 000000000..30213536f --- /dev/null +++ b/arduino-ide-extension/src/node/monitor-service-factory.ts @@ -0,0 +1,18 @@ +import { Board, Port } from '../common/protocol'; +import { CoreClientProvider } from './core-client-provider'; +import { MonitorService } from './monitor-service'; + +export const MonitorServiceFactory = Symbol('MonitorServiceFactory'); +export interface MonitorServiceFactory { + (options: { + board: Board; + port: Port; + coreClientProvider: CoreClientProvider; + }): MonitorService; +} + +export interface MonitorServiceFactoryOptions { + board: Board; + port: Port; + coreClientProvider: CoreClientProvider; +} diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index e3facecf6..d522f55e1 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -13,7 +13,6 @@ import { import { CoreClientAware, CoreClientProvider } from './core-client-provider'; import { WebSocketProvider } from './web-socket/web-socket-provider'; import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb'; -import WebSocketProviderImpl from './web-socket/web-socket-provider-impl'; import { PluggableMonitorSettings, MonitorSettingsProvider, @@ -48,19 +47,16 @@ export class MonitorService extends CoreClientAware implements Disposable { protected readonly onDisposeEmitter = new Emitter(); readonly onDispose = this.onDisposeEmitter.event; - @inject(MonitorSettingsProvider) - protected readonly monitorSettingsProvider: MonitorSettingsProvider; - - // TODO: use dependency injection - protected readonly webSocketProvider: WebSocketProvider = - new WebSocketProviderImpl(); - protected uploadInProgress = false; constructor( @inject(ILogger) @named(MonitorServiceName) protected readonly logger: ILogger, + @inject(MonitorSettingsProvider) + protected readonly monitorSettingsProvider: MonitorSettingsProvider, + @inject(WebSocketProvider) + protected readonly webSocketProvider: WebSocketProvider, private readonly board: Board, private readonly port: Port, diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts index 54e3b5690..a32f8c482 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts @@ -40,7 +40,7 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { ); // read existing settings - this.readFile(); + await this.readFile(); console.log(this.monitorSettings); this.ready.resolve(); diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts b/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts index 268928dd2..463dadcf7 100644 --- a/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts +++ b/arduino-ide-extension/src/node/web-socket/web-socket-provider-impl.ts @@ -1,7 +1,9 @@ import { Emitter } from '@theia/core'; +import { injectable } from '@theia/core/shared/inversify'; import * as WebSocket from 'ws'; import { WebSocketProvider } from './web-socket-provider'; +@injectable() export default class WebSocketProviderImpl implements WebSocketProvider { protected wsClients: WebSocket[]; protected server: WebSocket.Server; diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts b/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts index 7c402ad54..6aa102040 100644 --- a/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts +++ b/arduino-ide-extension/src/node/web-socket/web-socket-provider.ts @@ -1,6 +1,7 @@ import { Event } from '@theia/core/lib/common/event'; import * as WebSocket from 'ws'; +export const WebSocketProvider = Symbol('WebSocketProvider'); export interface WebSocketProvider { getAddress(): WebSocket.AddressInfo; sendMessage(message: string): void; From 9ea51b28bddd6c567b2404a4a9d5e83e170f3391 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Mon, 23 May 2022 12:55:54 +0200 Subject: [PATCH 37/66] fix monitor settings client communication --- .../browser/arduino-frontend-contribution.tsx | 13 +- .../monitor-manager-proxy-client-impl.ts | 15 ++- .../src/browser/monitor-model.ts | 62 +++++++-- .../browser/serial/monitor/monitor-widget.tsx | 122 +++++++++--------- .../plotter/plotter-frontend-contribution.ts | 2 +- .../src/common/protocol/monitor-service.ts | 9 +- arduino-ide-extension/src/common/utils.ts | 4 + .../src/node/monitor-manager-proxy-impl.ts | 7 +- .../src/node/monitor-manager.ts | 10 +- .../src/node/monitor-service.ts | 72 ++++++++--- 10 files changed, 210 insertions(+), 106 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 18f4df1a1..7e5bb1079 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -46,7 +46,10 @@ import { } from '@theia/editor/lib/browser'; import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution'; import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu'; -import { FileNavigatorCommands, FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution'; +import { + FileNavigatorCommands, + FileNavigatorContribution, +} from '@theia/navigator/lib/browser/navigator-contribution'; import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution'; import { OutputContribution } from '@theia/output/lib/browser/output-contribution'; import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution'; @@ -84,7 +87,8 @@ export class ArduinoFrontendContribution TabBarToolbarContribution, CommandContribution, MenuContribution, - ColorContribution { + ColorContribution +{ @inject(ILogger) protected logger: ILogger; @@ -157,6 +161,7 @@ export class ArduinoFrontendContribution @inject(SketchesServiceClientImpl) protected readonly sketchServiceClient: SketchesServiceClientImpl; + @inject(FrontendApplicationStateService) protected readonly appStateService: FrontendApplicationStateService; @inject(LocalStorageService) @@ -344,7 +349,7 @@ export class ArduinoFrontendContribution app.shell.leftPanelHandler.removeBottomMenu('settings-menu'); - this.fileSystemFrontendContribution.onDidChangeEditorFile(e => { + this.fileSystemFrontendContribution.onDidChangeEditorFile((e) => { if (e.type === FileChangeType.DELETED) { const editorWidget = e.editor; if (SaveableWidget.is(editorWidget)) { @@ -494,7 +499,7 @@ export class ArduinoFrontendContribution EditorCommands.SPLIT_EDITOR_UP, EditorCommands.SPLIT_EDITOR_VERTICAL, EditorCommands.SPLIT_EDITOR_HORIZONTAL, - FileNavigatorCommands.REVEAL_IN_NAVIGATOR + FileNavigatorCommands.REVEAL_IN_NAVIGATOR, ]) { registry.unregisterCommand(command); } diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 5a9293d49..bca97f464 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -24,6 +24,11 @@ export class MonitorManagerProxyClientImpl }>(); readonly onMessagesReceived = this.onMessagesReceivedEmitter.event; + protected readonly onMonitorSettingsDidChangeEmitter = + new Emitter(); + readonly onMonitorSettingsDidChange = + this.onMonitorSettingsDidChangeEmitter.event; + protected readonly onWSConnectionChangedEmitter = new Emitter(); readonly onWSConnectionChanged = this.onWSConnectionChangedEmitter.event; @@ -61,9 +66,11 @@ export class MonitorManagerProxyClientImpl return; } - this.webSocket.onmessage = (res) => { - const messages = JSON.parse(res.data); - this.onMessagesReceivedEmitter.fire({ messages }); + this.webSocket.onmessage = (message) => { + const parsedMessage = JSON.parse(message.data); + if (Array.isArray(parsedMessage)) + this.onMessagesReceivedEmitter.fire({ messages: parsedMessage }); + else this.onMonitorSettingsDidChangeEmitter.fire(parsedMessage); }; this.wsPort = addressPort; } @@ -93,7 +100,7 @@ export class MonitorManagerProxyClientImpl return this.server().startMonitor(board, port, settings); } - getCurrentSettings(board: Board, port: Port): MonitorSettings { + getCurrentSettings(board: Board, port: Port): Promise { return this.server().getCurrentSettings(board, port); } diff --git a/arduino-ide-extension/src/browser/monitor-model.ts b/arduino-ide-extension/src/browser/monitor-model.ts index 41baca1ab..b76de61af 100644 --- a/arduino-ide-extension/src/browser/monitor-model.ts +++ b/arduino-ide-extension/src/browser/monitor-model.ts @@ -4,6 +4,9 @@ import { LocalStorageService, } from '@theia/core/lib/browser'; import { inject, injectable } from '@theia/core/shared/inversify'; +import { MonitorManagerProxyClient } from '../common/protocol'; +import { isNullOrUndefined } from '../common/utils'; +import { MonitorSettings } from '../node/monitor-settings/monitor-settings-provider'; @injectable() export class MonitorModel implements FrontendApplicationContribution { @@ -12,6 +15,9 @@ export class MonitorModel implements FrontendApplicationContribution { @inject(LocalStorageService) protected readonly localStorageService: LocalStorageService; + @inject(MonitorManagerProxyClient) + protected readonly monitorManagerProxy: MonitorManagerProxyClient; + protected readonly onChangeEmitter: Emitter< MonitorModel.State.Change >; @@ -35,7 +41,11 @@ export class MonitorModel implements FrontendApplicationContribution { onStart(): void { this.localStorageService .getData(MonitorModel.STORAGE_ID) - .then(this.restoreState); + .then(this.restoreState.bind(this)); + + this.monitorManagerProxy.onMonitorSettingsDidChange( + this.onMonitorSettingsDidChange.bind(this) + ); } get onChange(): Event> { @@ -65,22 +75,34 @@ export class MonitorModel implements FrontendApplicationContribution { return this._autoscroll; } - toggleAutoscroll(): void { - this._autoscroll = !this._autoscroll; + set autoscroll(autoscroll: boolean) { + if (autoscroll === this._autoscroll) return; + this._autoscroll = autoscroll; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { autoscroll }, + }); this.storeState().then(() => { this.onChangeEmitter.fire({ property: 'autoscroll', - value: this._timestamp, + value: this._autoscroll, }); }); } + toggleAutoscroll(): void { + this.autoscroll = !this._autoscroll; + } + get timestamp(): boolean { return this._timestamp; } - toggleTimestamp(): void { - this._timestamp = !this._timestamp; + set timestamp(timestamp: boolean) { + if (timestamp === this._timestamp) return; + this._timestamp = timestamp; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { timestamp }, + }); this.storeState().then(() => this.onChangeEmitter.fire({ property: 'timestamp', @@ -89,12 +111,20 @@ export class MonitorModel implements FrontendApplicationContribution { ); } + toggleTimestamp(): void { + this.timestamp = !this._timestamp; + } + get lineEnding(): MonitorModel.EOL { return this._lineEnding; } set lineEnding(lineEnding: MonitorModel.EOL) { + if (lineEnding === this._lineEnding) return; this._lineEnding = lineEnding; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { lineEnding }, + }); this.storeState().then(() => this.onChangeEmitter.fire({ property: 'lineEnding', @@ -107,8 +137,12 @@ export class MonitorModel implements FrontendApplicationContribution { return this._interpolate; } - set interpolate(i: boolean) { - this._interpolate = i; + set interpolate(interpolate: boolean) { + if (interpolate === this._interpolate) return; + this._interpolate = interpolate; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { interpolate }, + }); this.storeState().then(() => this.onChangeEmitter.fire({ property: 'interpolate', @@ -116,6 +150,18 @@ export class MonitorModel implements FrontendApplicationContribution { }) ); } + + protected onMonitorSettingsDidChange = (settings: MonitorSettings): void => { + const { monitorUISettings } = settings; + if (!monitorUISettings) return; + const { autoscroll, interpolate, lineEnding, timestamp } = + monitorUISettings; + + if (!isNullOrUndefined(autoscroll)) this.autoscroll = autoscroll; + if (!isNullOrUndefined(interpolate)) this.interpolate = interpolate; + if (!isNullOrUndefined(lineEnding)) this.lineEnding = lineEnding; + if (!isNullOrUndefined(timestamp)) this.timestamp = timestamp; + }; } // TODO: Move this to /common diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index ce098be68..3b25cdf9d 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { postConstruct, injectable, inject } from 'inversify'; +import { injectable, inject } from 'inversify'; import { OptionsType } from 'react-select/src/types'; import { Emitter } from '@theia/core/lib/common/event'; import { Disposable } from '@theia/core/lib/common/disposable'; @@ -26,6 +26,8 @@ export class MonitorWidget extends ReactWidget { ); static readonly ID = 'serial-monitor'; + protected settings: MonitorSettings = {}; + protected widgetHeight: number; /** @@ -82,10 +84,24 @@ export class MonitorWidget extends ReactWidget { ); } - @postConstruct() - protected init(): void { + protected onAfterAttach(msg: Message): void { this.update(); this.toDispose.push(this.monitorModel.onChange(() => this.update())); + this.getCurrentSettings().then(this.onMonitorSettingsDidChange.bind(this)); + this.monitorManagerProxy.onMonitorSettingsDidChange( + this.onMonitorSettingsDidChange.bind(this) + ); + } + + onMonitorSettingsDidChange(settings: MonitorSettings): void { + this.settings = { + ...this.settings, + pluggableMonitorSettings: { + ...this.settings.pluggableMonitorSettings, + ...settings.pluggableMonitorSettings, + }, + }; + this.update(); } clearConsole(): void { @@ -157,56 +173,33 @@ export class MonitorWidget extends ReactWidget { ]; } - private getCurrentSettings(): MonitorSettings { + private getCurrentSettings(): Promise { const board = this.boardsServiceProvider.boardsConfig.selectedBoard; const port = this.boardsServiceProvider.boardsConfig.selectedPort; if (!board || !port) { - return {}; + return Promise.resolve(this.settings || {}); } return this.monitorManagerProxy.getCurrentSettings(board, port); } - ////////////////////////////////////////////////// - ////////////////////IMPORTANT///////////////////// - ////////////////////////////////////////////////// - // baudRates and selectedBaudRates as of now are hardcoded - // like this to retrieve the baudrate settings from the ones - // received by the monitor. - // We're doing it like since the frontend as of now doesn't - // support a fully customizable list of options that would - // be require to support pluggable monitors completely. - // As soon as the frontend UI is updated to support - // any custom settings this methods MUST be removed and - // made generic. - // - // This breaks if the user tries to open a monitor that - // doesn't support the baudrate setting. - protected get baudRates(): string[] { - const { pluggableMonitorSettings } = this.getCurrentSettings(); - if (!pluggableMonitorSettings || !pluggableMonitorSettings['baudrate']) { - return []; - } - - const baudRateSettings = pluggableMonitorSettings['baudrate']; - - return baudRateSettings.values; - } + protected render(): React.ReactNode { + const baudrate = this.settings?.pluggableMonitorSettings + ? this.settings.pluggableMonitorSettings.baudrate + : undefined; - protected get selectedBaudRate(): string { - const { pluggableMonitorSettings } = this.getCurrentSettings(); - if (!pluggableMonitorSettings || !pluggableMonitorSettings['baudrate']) { - return ''; - } - const baudRateSettings = pluggableMonitorSettings['baudrate']; - return baudRateSettings.selectedValue; - } + const baudrateOptions = baudrate?.values.map((b) => ({ + label: b + ' baud', + value: b, + })); + const baudrateSelectedOption = baudrateOptions?.find( + (b) => b.value === baudrate?.selectedValue + ); - protected render(): React.ReactNode { - const { baudRates, lineEndings } = this; const lineEnding = - lineEndings.find((item) => item.value === this.monitorModel.lineEnding) || - lineEndings[1]; // Defaults to `\n`. - const baudRate = baudRates.find((item) => item === this.selectedBaudRate); + this.lineEndings.find( + (item) => item.value === this.monitorModel.lineEnding + ) || this.lineEndings[1]; // Defaults to `\n`. + return (
@@ -222,20 +215,22 @@ export class MonitorWidget extends ReactWidget {
-
- -
+ {baudrateOptions && baudrateSelectedOption && ( +
+ +
+ )}
@@ -257,16 +252,21 @@ export class MonitorWidget extends ReactWidget { protected readonly onChangeLineEnding = ( option: SerialMonitorOutput.SelectOption - ) => { + ): void => { this.monitorModel.lineEnding = option.value; }; - protected readonly onChangeBaudRate = (value: string) => { - const { pluggableMonitorSettings } = this.getCurrentSettings(); - if (!pluggableMonitorSettings || !pluggableMonitorSettings['baudrate']) - return; - const baudRateSettings = pluggableMonitorSettings['baudrate']; - baudRateSettings.selectedValue = value; - this.monitorManagerProxy.changeSettings(pluggableMonitorSettings); + protected readonly onChangeBaudRate = ({ + value, + }: { + value: string; + }): void => { + this.getCurrentSettings().then(({ pluggableMonitorSettings }) => { + if (!pluggableMonitorSettings || !pluggableMonitorSettings['baudrate']) + return; + const baudRateSettings = pluggableMonitorSettings['baudrate']; + baudRateSettings.selectedValue = value; + this.monitorManagerProxy.changeSettings({ pluggableMonitorSettings }); + }); }; } diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index 083477e05..675fe00de 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -89,7 +89,7 @@ export class PlotterFrontendContribution extends Contribution { let currentBaudrate = -1; if (board && port) { const { pluggableMonitorSettings } = - this.monitorManagerProxy.getCurrentSettings(board, port); + await this.monitorManagerProxy.getCurrentSettings(board, port); if (pluggableMonitorSettings && 'baudrate' in pluggableMonitorSettings) { // Convert from string to numbers baudrates = pluggableMonitorSettings['baudrate'].values.map((b) => +b); diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 18f67367e..5e4faabaa 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -20,15 +20,16 @@ export interface MonitorManagerProxy changeMonitorSettings( board: Board, port: Port, - settings: PluggableMonitorSettings + settings: MonitorSettings ): Promise; stopMonitor(board: Board, port: Port): Promise; - getCurrentSettings(board: Board, port: Port): PluggableMonitorSettings; + getCurrentSettings(board: Board, port: Port): Promise; } export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); export interface MonitorManagerProxyClient { onMessagesReceived: Event<{ messages: string[] }>; + onMonitorSettingsDidChange: Event; onWSConnectionChanged: Event; connect(addressPort: number): void; disconnect(): void; @@ -39,7 +40,7 @@ export interface MonitorManagerProxyClient { port: Port, settings?: PluggableMonitorSettings ): Promise; - getCurrentSettings(board: Board, port: Port): MonitorSettings; + getCurrentSettings(board: Board, port: Port): Promise; send(message: string): void; changeSettings(settings: MonitorSettings): void; } @@ -65,7 +66,7 @@ export namespace Monitor { export type Message = { command: Monitor.Command; - data: string; + data: string | MonitorSettings; }; } diff --git a/arduino-ide-extension/src/common/utils.ts b/arduino-ide-extension/src/common/utils.ts index 93dcdd332..8ffa1fd9a 100644 --- a/arduino-ide-extension/src/common/utils.ts +++ b/arduino-ide-extension/src/common/utils.ts @@ -12,3 +12,7 @@ export function firstToLowerCase(what: string): string { export function firstToUpperCase(what: string): string { return what.charAt(0).toUpperCase() + what.slice(1); } + +export function isNullOrUndefined(what: any): what is undefined | null { + return what === undefined || what === null; +} diff --git a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts index 75ade7452..c4e9d59d5 100644 --- a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts +++ b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts @@ -6,7 +6,10 @@ import { } from '../common/protocol'; import { Board, Port } from '../common/protocol'; import { MonitorManager } from './monitor-manager'; -import { PluggableMonitorSettings } from './monitor-settings/monitor-settings-provider'; +import { + MonitorSettings, + PluggableMonitorSettings, +} from './monitor-settings/monitor-settings-provider'; @injectable() export class MonitorManagerProxyImpl implements MonitorManagerProxy { @@ -79,7 +82,7 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy { * @param port port monitored * @returns a map of MonitorSetting */ - getCurrentSettings(board: Board, port: Port): PluggableMonitorSettings { + getCurrentSettings(board: Board, port: Port): Promise { return this.manager.currentMonitorSettings(board, port); } diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 02b79274a..e563f693f 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -4,7 +4,10 @@ import { Board, Port, Status } from '../common/protocol'; import { CoreClientAware } from './core-client-provider'; import { MonitorService } from './monitor-service'; import { MonitorServiceFactory } from './monitor-service-factory'; -import { PluggableMonitorSettings } from './monitor-settings/monitor-settings-provider'; +import { + MonitorSettings, + PluggableMonitorSettings, +} from './monitor-settings/monitor-settings-provider'; type MonitorID = string; @@ -169,7 +172,10 @@ export class MonitorManager extends CoreClientAware { * @param port port monitored * @returns map of current monitor settings */ - currentMonitorSettings(board: Board, port: Port): PluggableMonitorSettings { + async currentMonitorSettings( + board: Board, + port: Port + ): Promise { const monitorID = this.monitorID(board, port); const monitor = this.monitorServices.get(monitorID); if (!monitor) { diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index d522f55e1..c2f3c9573 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -14,9 +14,11 @@ import { CoreClientAware, CoreClientProvider } from './core-client-provider'; import { WebSocketProvider } from './web-socket/web-socket-provider'; import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb'; import { + MonitorSettings, PluggableMonitorSettings, MonitorSettingsProvider, } from './monitor-settings/monitor-settings-provider'; +import { Deferred } from '@theia/core/lib/common/promise-util'; export const MonitorServiceName = 'monitor-service'; @@ -27,7 +29,7 @@ export class MonitorService extends CoreClientAware implements Disposable { // Settings used by the currently running pluggable monitor. // They can be freely modified while running. - protected settings: PluggableMonitorSettings; + protected settings: MonitorSettings = {}; // List of messages received from the running pluggable monitor. // These are flushed from time to time to the frontend. @@ -48,6 +50,7 @@ export class MonitorService extends CoreClientAware implements Disposable { readonly onDispose = this.onDisposeEmitter.event; protected uploadInProgress = false; + protected _initialized = new Deferred(); constructor( @inject(ILogger) @@ -64,6 +67,10 @@ export class MonitorService extends CoreClientAware implements Disposable { ) { super(); + this.monitorSettingsProvider = { + getSettings: () => ({} as Promise), + setSettings: () => ({} as Promise), + }; this.onWSClientsNumberChanged = this.webSocketProvider.onClientsNumberChanged(async (clients: number) => { if (clients === 0) { @@ -73,6 +80,18 @@ export class MonitorService extends CoreClientAware implements Disposable { this.dispose(); } }); + + this.portMonitorSettings(port.protocol, board.fqbn!).then((settings) => { + this.settings = { + ...this.settings, + pluggableMonitorSettings: settings, + }; + this._initialized.resolve(); + }); + } + + get initialized(): Promise { + return this._initialized.promise; } setUploadInProgress(status: boolean): void { @@ -125,10 +144,16 @@ export class MonitorService extends CoreClientAware implements Disposable { this.board.fqbn ); // get actual settings from the settings provider - this.settings = await this.monitorSettingsProvider.getSettings( - monitorID, - defaultSettings - ); + this.settings = { + ...this.settings, + pluggableMonitorSettings: { + ...this.settings.pluggableMonitorSettings, + ...(await this.monitorSettingsProvider.getSettings( + monitorID, + defaultSettings + )), + }, + }; await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); @@ -181,10 +206,10 @@ export class MonitorService extends CoreClientAware implements Disposable { req.setPort(port); } const config = new MonitorPortConfiguration(); - for (const id in this.settings) { + for (const id in this.settings.pluggableMonitorSettings) { const s = new MonitorPortSetting(); s.setSettingId(id); - s.setValue(this.settings[id].selectedValue); + s.setValue(this.settings.pluggableMonitorSettings[id].selectedValue); config.addSettings(s); } req.setPortConfiguration(config); @@ -283,7 +308,8 @@ export class MonitorService extends CoreClientAware implements Disposable { * * @returns map of current monitor settings */ - currentSettings(): PluggableMonitorSettings { + async currentSettings(): Promise { + await this.initialized; return this.settings; } @@ -339,14 +365,23 @@ export class MonitorService extends CoreClientAware implements Disposable { * @param settings map of monitor settings to change * @returns a status to verify settings have been sent. */ - async changeSettings(settings: PluggableMonitorSettings): Promise { + async changeSettings(settings: MonitorSettings): Promise { const config = new MonitorPortConfiguration(); - for (const id in settings) { - const s = new MonitorPortSetting(); - s.setSettingId(id); - s.setValue(settings[id].selectedValue); - config.addSettings(s); - this.settings[id] = settings[id]; + const { pluggableMonitorSettings } = settings; + + this.webSocketProvider.sendMessage(JSON.stringify(settings)); + + if (pluggableMonitorSettings) { + for (const id in pluggableMonitorSettings) { + const s = new MonitorPortSetting(); + s.setSettingId(id); + s.setValue(pluggableMonitorSettings[id].selectedValue); + config.addSettings(s); + this.settings.pluggableMonitorSettings = { + ...this.settings.pluggableMonitorSettings, + [id]: pluggableMonitorSettings[id], + }; + } } if (!this.duplex) { @@ -385,13 +420,10 @@ export class MonitorService extends CoreClientAware implements Disposable { switch (message.command) { case Monitor.Command.SEND_MESSAGE: - this.send(message.data); + this.send(message.data as string); break; case Monitor.Command.CHANGE_SETTINGS: - const settings: PluggableMonitorSettings = JSON.parse( - message.data - ); - this.changeSettings(settings); + this.changeSettings(message.data as MonitorSettings); break; } } From 5035b0bbd2b8d5f499616b11881335e75e4e197e Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Mon, 23 May 2022 15:49:10 +0200 Subject: [PATCH 38/66] refactor monitor commands protocol --- .../browser/monitor-manager-proxy-client-impl.ts | 6 ++---- .../src/common/protocol/monitor-service.ts | 13 +++++++++---- arduino-ide-extension/src/node/monitor-service.ts | 4 ++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index bca97f464..66efd72e5 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -111,7 +111,7 @@ export class MonitorManagerProxyClientImpl this.webSocket.send( JSON.stringify({ - command: Monitor.Command.SEND_MESSAGE, + command: Monitor.ClientCommand.SEND_MESSAGE, data: message, }) ); @@ -124,9 +124,7 @@ export class MonitorManagerProxyClientImpl this.webSocket.send( JSON.stringify({ - command: Monitor.Command.CHANGE_SETTINGS, - // TODO: This might be wrong, verify if it works - // SPOILER: It doesn't + command: Monitor.ClientCommand.CHANGE_SETTINGS, data: settings, }) ); diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 5e4faabaa..8617ac755 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -59,13 +59,18 @@ export interface PluggableMonitorSetting { } export namespace Monitor { - export enum Command { - SEND_MESSAGE = 'MONITOR_SEND_MESSAGE', - CHANGE_SETTINGS = 'MONITOR_CHANGE_SETTINGS', + // Commands sent by the clients to the web socket server + export enum ClientCommand { + SEND_MESSAGE = 'SEND_MESSAGE', + CHANGE_SETTINGS = 'CHANGE_SETTINGS', + } + + export enum MiddlewareCommand { + ON_SETTINGS_DID_CHANGE = 'ON_SETTINGS_DID_CHANGE', } export type Message = { - command: Monitor.Command; + command: Monitor.ClientCommand; data: string | MonitorSettings; }; } diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index c2f3c9573..a73d6972a 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -419,10 +419,10 @@ export class MonitorService extends CoreClientAware implements Disposable { const message: Monitor.Message = JSON.parse(msg); switch (message.command) { - case Monitor.Command.SEND_MESSAGE: + case Monitor.ClientCommand.SEND_MESSAGE: this.send(message.data as string); break; - case Monitor.Command.CHANGE_SETTINGS: + case Monitor.ClientCommand.CHANGE_SETTINGS: this.changeSettings(message.data as MonitorSettings); break; } From a7433ee70278dbffb5a5081c4525b953c86d6b08 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Mon, 23 May 2022 17:27:17 +0200 Subject: [PATCH 39/66] use monitor settings provider properly --- .../monitor-manager-proxy-client-impl.ts | 7 ++- .../browser/serial/monitor/monitor-widget.tsx | 2 +- .../src/common/protocol/monitor-service.ts | 2 +- .../src/node/arduino-ide-backend-module.ts | 3 +- .../src/node/monitor-manager.ts | 1 + .../src/node/monitor-service-factory.ts | 2 + .../src/node/monitor-service.ts | 48 +++++++++++-------- .../monitor-settings-provider-impl.ts | 8 +++- 8 files changed, 47 insertions(+), 26 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 66efd72e5..85fed2934 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -70,7 +70,12 @@ export class MonitorManagerProxyClientImpl const parsedMessage = JSON.parse(message.data); if (Array.isArray(parsedMessage)) this.onMessagesReceivedEmitter.fire({ messages: parsedMessage }); - else this.onMonitorSettingsDidChangeEmitter.fire(parsedMessage); + else if ( + parsedMessage.command === + Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE + ) { + this.onMonitorSettingsDidChangeEmitter.fire(parsedMessage.data); + } }; this.wsPort = addressPort; } diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index 3b25cdf9d..4c5276b8e 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -84,7 +84,7 @@ export class MonitorWidget extends ReactWidget { ); } - protected onAfterAttach(msg: Message): void { + protected onBeforeAttach(msg: Message): void { this.update(); this.toDispose.push(this.monitorModel.onChange(() => this.update())); this.getCurrentSettings().then(this.onMonitorSettingsDidChange.bind(this)); diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 8617ac755..f5a58e932 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -70,7 +70,7 @@ export namespace Monitor { } export type Message = { - command: Monitor.ClientCommand; + command: Monitor.ClientCommand | Monitor.MiddlewareCommand; data: string | MonitorSettings; }; } diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 50cc4d16c..bf4ee0435 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -229,7 +229,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { const webSocketProvider = container.get(WebSocketProvider); - const { board, port, coreClientProvider } = options; + const { board, port, coreClientProvider, monitorID } = options; return new MonitorService( logger, @@ -237,6 +237,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { webSocketProvider, board, port, + monitorID, coreClientProvider ); } diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index e563f693f..9649367bb 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -196,6 +196,7 @@ export class MonitorManager extends CoreClientAware { const monitor = this.monitorServiceFactory({ board, port, + monitorID, coreClientProvider: this.coreClientProvider, }); this.monitorServices.set(monitorID, monitor); diff --git a/arduino-ide-extension/src/node/monitor-service-factory.ts b/arduino-ide-extension/src/node/monitor-service-factory.ts index 30213536f..6f88cdb0f 100644 --- a/arduino-ide-extension/src/node/monitor-service-factory.ts +++ b/arduino-ide-extension/src/node/monitor-service-factory.ts @@ -7,6 +7,7 @@ export interface MonitorServiceFactory { (options: { board: Board; port: Port; + monitorID: string; coreClientProvider: CoreClientProvider; }): MonitorService; } @@ -14,5 +15,6 @@ export interface MonitorServiceFactory { export interface MonitorServiceFactoryOptions { board: Board; port: Port; + monitorID: string; coreClientProvider: CoreClientProvider; } diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index a73d6972a..945c1e76d 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -63,14 +63,11 @@ export class MonitorService extends CoreClientAware implements Disposable { private readonly board: Board, private readonly port: Port, + private readonly monitorID: string, protected readonly coreClientProvider: CoreClientProvider ) { super(); - this.monitorSettingsProvider = { - getSettings: () => ({} as Promise), - setSettings: () => ({} as Promise), - }; this.onWSClientsNumberChanged = this.webSocketProvider.onClientsNumberChanged(async (clients: number) => { if (clients === 0) { @@ -81,13 +78,19 @@ export class MonitorService extends CoreClientAware implements Disposable { } }); - this.portMonitorSettings(port.protocol, board.fqbn!).then((settings) => { - this.settings = { - ...this.settings, - pluggableMonitorSettings: settings, - }; - this._initialized.resolve(); - }); + this.portMonitorSettings(port.protocol, board.fqbn!).then( + async (settings) => { + this.settings = { + ...this.settings, + pluggableMonitorSettings: + await this.monitorSettingsProvider.getSettings( + this.monitorID, + settings + ), + }; + this._initialized.resolve(); + } + ); } get initialized(): Promise { @@ -368,22 +371,27 @@ export class MonitorService extends CoreClientAware implements Disposable { async changeSettings(settings: MonitorSettings): Promise { const config = new MonitorPortConfiguration(); const { pluggableMonitorSettings } = settings; + const reconciledSettings = await this.monitorSettingsProvider.setSettings( + this.monitorID, + pluggableMonitorSettings || {} + ); - this.webSocketProvider.sendMessage(JSON.stringify(settings)); - - if (pluggableMonitorSettings) { - for (const id in pluggableMonitorSettings) { + if (reconciledSettings) { + for (const id in reconciledSettings) { const s = new MonitorPortSetting(); s.setSettingId(id); - s.setValue(pluggableMonitorSettings[id].selectedValue); + s.setValue(reconciledSettings[id].selectedValue); config.addSettings(s); - this.settings.pluggableMonitorSettings = { - ...this.settings.pluggableMonitorSettings, - [id]: pluggableMonitorSettings[id], - }; } } + const command: Monitor.Message = { + command: Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE, + data: { ...settings, pluggableMonitorSettings: reconciledSettings }, + }; + + this.webSocketProvider.sendMessage(JSON.stringify(command)); + if (!this.duplex) { return Status.NOT_CONNECTED; } diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts index 977cb0e5c..ef8050691 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts @@ -58,7 +58,11 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { const { matchingSettings } = this.longestPrefixMatch(monitorId); - return this.reconcileSettings(matchingSettings, defaultSettings); + this.monitorSettings[monitorId] = this.reconcileSettings( + matchingSettings, + defaultSettings + ); + return this.monitorSettings[monitorId]; } async setSettings( @@ -70,7 +74,7 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { const newSettings = this.reconcileSettings( settings, - this.monitorSettings[monitorId] + this.monitorSettings[monitorId] || {} ); this.monitorSettings[monitorId] = newSettings; From f92f875f6964feb2c49af66b260f9e94699619ad Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 24 May 2022 14:20:08 +0200 Subject: [PATCH 40/66] add settings to monitor model --- .../src/browser/monitor-model.ts | 96 ++++++++++++++++++- .../browser/serial/monitor/monitor-widget.tsx | 1 + .../monitor/serial-monitor-send-input.tsx | 28 ++---- .../src/common/protocol/monitor-service.ts | 1 + .../src/node/monitor-manager.ts | 4 +- .../src/node/monitor-service.ts | 28 ++++-- 6 files changed, 125 insertions(+), 33 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-model.ts b/arduino-ide-extension/src/browser/monitor-model.ts index b76de61af..1f906378e 100644 --- a/arduino-ide-extension/src/browser/monitor-model.ts +++ b/arduino-ide-extension/src/browser/monitor-model.ts @@ -26,12 +26,20 @@ export class MonitorModel implements FrontendApplicationContribution { protected _timestamp: boolean; protected _lineEnding: MonitorModel.EOL; protected _interpolate: boolean; + protected _darkTheme: boolean; + protected _wsPort: number; + protected _serialPort: string; + protected _connected: boolean; constructor() { this._autoscroll = true; this._timestamp = false; this._interpolate = false; this._lineEnding = MonitorModel.EOL.DEFAULT; + this._darkTheme = false; + this._wsPort = 0; + this._serialPort = ''; + this._connected = false; this.onChangeEmitter = new Emitter< MonitorModel.State.Change @@ -60,6 +68,7 @@ export class MonitorModel implements FrontendApplicationContribution { this._timestamp = state.timestamp; this._lineEnding = state.lineEnding; this._interpolate = state.interpolate; + this._serialPort = state.serialPort; } protected async storeState(): Promise { @@ -68,6 +77,7 @@ export class MonitorModel implements FrontendApplicationContribution { timestamp: this._timestamp, lineEnding: this._lineEnding, interpolate: this._interpolate, + serialPort: this._serialPort, }); } @@ -151,16 +161,94 @@ export class MonitorModel implements FrontendApplicationContribution { ); } + get darkTheme(): boolean { + return this._darkTheme; + } + + set darkTheme(darkTheme: boolean) { + if (darkTheme === this._darkTheme) return; + this._darkTheme = darkTheme; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { darkTheme }, + }); + this.onChangeEmitter.fire({ + property: 'darkTheme', + value: this._darkTheme, + }); + } + + get wsPort(): number { + return this._wsPort; + } + + set wsPort(wsPort: number) { + if (wsPort === this._wsPort) return; + this._wsPort = wsPort; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { wsPort }, + }); + this.onChangeEmitter.fire({ + property: 'wsPort', + value: this._wsPort, + }); + } + + get serialPort(): string { + return this._serialPort; + } + + set serialPort(serialPort: string) { + if (serialPort === this._serialPort) return; + this._serialPort = serialPort; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { serialPort }, + }); + this.storeState().then(() => + this.onChangeEmitter.fire({ + property: 'serialPort', + value: this._serialPort, + }) + ); + } + + get connected(): boolean { + return this._connected; + } + + set connected(connected: boolean) { + if (connected === this._connected) return; + this._connected = connected; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { connected }, + }); + this.onChangeEmitter.fire({ + property: 'connected', + value: this._connected, + }); + } + protected onMonitorSettingsDidChange = (settings: MonitorSettings): void => { const { monitorUISettings } = settings; if (!monitorUISettings) return; - const { autoscroll, interpolate, lineEnding, timestamp } = - monitorUISettings; + const { + autoscroll, + interpolate, + lineEnding, + timestamp, + darkTheme, + wsPort, + serialPort, + connected, + } = monitorUISettings; if (!isNullOrUndefined(autoscroll)) this.autoscroll = autoscroll; if (!isNullOrUndefined(interpolate)) this.interpolate = interpolate; if (!isNullOrUndefined(lineEnding)) this.lineEnding = lineEnding; if (!isNullOrUndefined(timestamp)) this.timestamp = timestamp; + if (!isNullOrUndefined(darkTheme)) this.darkTheme = darkTheme; + if (!isNullOrUndefined(wsPort)) this.wsPort = wsPort; + if (!isNullOrUndefined(serialPort)) this.serialPort = serialPort; + if (!isNullOrUndefined(connected)) this.connected = connected; }; } @@ -171,6 +259,10 @@ export namespace MonitorModel { timestamp: boolean; lineEnding: EOL; interpolate: boolean; + darkTheme: boolean; + wsPort: number; + serialPort: string; + connected: boolean; } export namespace State { export interface Change { diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index 4c5276b8e..eb3617776 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -207,6 +207,7 @@ export class MonitorWidget extends ReactWidget { diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx index 59ad9c9ad..7b7c00459 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx @@ -5,12 +5,13 @@ import { isOSX } from '@theia/core/lib/common/os'; import { DisposableCollection, nls } from '@theia/core/lib/common'; import { MonitorManagerProxyClient } from '../../../common/protocol'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; -import { timeout } from '@theia/core/lib/common/promise-util'; +import { MonitorModel } from '../../monitor-model'; export namespace SerialMonitorSendInput { export interface Props { readonly boardsServiceProvider: BoardsServiceProvider; readonly monitorManagerProxy: MonitorManagerProxyClient; + readonly monitorModel: MonitorModel; readonly onSend: (text: string) => void; readonly resolveFocus: (element: HTMLElement | undefined) => void; } @@ -35,25 +36,12 @@ export class SerialMonitorSendInput extends React.Component< } componentDidMount(): void { - this.setState({ connected: true }); - - const checkWSConnection = new Promise((resolve) => { - this.props.monitorManagerProxy.onWSConnectionChanged((connected) => { - this.setState({ connected }); - resolve(true); - }); - }); - - const checkWSTimeout = timeout(1000).then(() => false); - - Promise.race([checkWSConnection, checkWSTimeout]).then( - async (resolved) => { - if (!resolved) { - const connected = - await this.props.monitorManagerProxy.isWSConnected(); - this.setState({ connected }); - } - } + this.setState({ connected: this.props.monitorModel.connected }); + this.toDisposeBeforeUnmount.push( + this.props.monitorModel.onChange(({ property }) => { + if (property === 'connected') + this.setState({ connected: this.props.monitorModel.connected }); + }) ); } diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index f5a58e932..4e03c1815 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -65,6 +65,7 @@ export namespace Monitor { CHANGE_SETTINGS = 'CHANGE_SETTINGS', } + // Commands sent by the backend to the clients export enum MiddlewareCommand { ON_SETTINGS_DID_CHANGE = 'ON_SETTINGS_DID_CHANGE', } diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 9649367bb..0f01fc958 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -62,7 +62,7 @@ export class MonitorManager extends CoreClientAware { if (!monitor) { monitor = this.createMonitor(board, port); } - return await monitor.start(monitorID); + return await monitor.start(); } /** @@ -142,7 +142,7 @@ export class MonitorManager extends CoreClientAware { return Status.NOT_CONNECTED; } monitor.setUploadInProgress(false); - return await monitor.start(monitorID); + return await monitor.start(); } /** diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 945c1e76d..338e7fc92 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -123,11 +123,11 @@ export class MonitorService extends CoreClientAware implements Disposable { * Start and connects a monitor using currently set board and port. * If a monitor is already started or board fqbn, port address and/or protocol * are missing nothing happens. - * @param id * @returns a status to verify connection has been established. */ - async start(monitorID: string): Promise { + async start(): Promise { if (this.duplex) { + this.updateClientsSettings({ monitorUISettings: { connected: true } }); return Status.ALREADY_CONNECTED; } @@ -152,7 +152,7 @@ export class MonitorService extends CoreClientAware implements Disposable { pluggableMonitorSettings: { ...this.settings.pluggableMonitorSettings, ...(await this.monitorSettingsProvider.getSettings( - monitorID, + this.monitorID, defaultSettings )), }, @@ -165,12 +165,14 @@ export class MonitorService extends CoreClientAware implements Disposable { this.duplex .on('close', () => { this.duplex = null; + this.updateClientsSettings({ monitorUISettings: { connected: false } }); this.logger.info( `monitor to ${this.port?.address} using ${this.port?.protocol} closed by client` ); }) .on('end', () => { this.duplex = null; + this.updateClientsSettings({ monitorUISettings: { connected: false } }); this.logger.info( `monitor to ${this.port?.address} using ${this.port?.protocol} closed by server` ); @@ -223,6 +225,7 @@ export class MonitorService extends CoreClientAware implements Disposable { this.logger.info( `started monitor to ${this.port?.address} using ${this.port?.protocol}` ); + this.updateClientsSettings({ monitorUISettings: { connected: true } }); resolve(Status.OK); return; } @@ -385,12 +388,10 @@ export class MonitorService extends CoreClientAware implements Disposable { } } - const command: Monitor.Message = { - command: Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE, - data: { ...settings, pluggableMonitorSettings: reconciledSettings }, - }; - - this.webSocketProvider.sendMessage(JSON.stringify(command)); + this.updateClientsSettings({ + ...settings, + pluggableMonitorSettings: reconciledSettings, + }); if (!this.duplex) { return Status.NOT_CONNECTED; @@ -439,6 +440,15 @@ export class MonitorService extends CoreClientAware implements Disposable { } } + updateClientsSettings(settings: MonitorSettings): void { + const command: Monitor.Message = { + command: Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE, + data: settings, + }; + + this.webSocketProvider.sendMessage(JSON.stringify(command)); + } + /** * Stops the necessary handlers to send and receive messages to * and from the frontend and the running monitor From 7daad3bd7e7e509663e1f3fb248656ecff3c4811 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 24 May 2022 14:20:08 +0200 Subject: [PATCH 41/66] add settings to monitor model --- .../src/browser/monitor-model.ts | 96 ++++++++++++++++++- .../browser/serial/monitor/monitor-widget.tsx | 1 + .../monitor/serial-monitor-send-input.tsx | 28 ++---- .../src/common/protocol/monitor-service.ts | 1 + .../src/node/monitor-manager.ts | 4 +- .../src/node/monitor-service.ts | 34 +++++-- 6 files changed, 131 insertions(+), 33 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-model.ts b/arduino-ide-extension/src/browser/monitor-model.ts index b76de61af..3ebea1819 100644 --- a/arduino-ide-extension/src/browser/monitor-model.ts +++ b/arduino-ide-extension/src/browser/monitor-model.ts @@ -26,12 +26,20 @@ export class MonitorModel implements FrontendApplicationContribution { protected _timestamp: boolean; protected _lineEnding: MonitorModel.EOL; protected _interpolate: boolean; + protected _darkTheme: boolean; + protected _wsPort: number; + protected _serialPort: string; + protected _connected: boolean; constructor() { this._autoscroll = true; this._timestamp = false; this._interpolate = false; this._lineEnding = MonitorModel.EOL.DEFAULT; + this._darkTheme = false; + this._wsPort = 0; + this._serialPort = ''; + this._connected = true; this.onChangeEmitter = new Emitter< MonitorModel.State.Change @@ -60,6 +68,7 @@ export class MonitorModel implements FrontendApplicationContribution { this._timestamp = state.timestamp; this._lineEnding = state.lineEnding; this._interpolate = state.interpolate; + this._serialPort = state.serialPort; } protected async storeState(): Promise { @@ -68,6 +77,7 @@ export class MonitorModel implements FrontendApplicationContribution { timestamp: this._timestamp, lineEnding: this._lineEnding, interpolate: this._interpolate, + serialPort: this._serialPort, }); } @@ -151,16 +161,94 @@ export class MonitorModel implements FrontendApplicationContribution { ); } + get darkTheme(): boolean { + return this._darkTheme; + } + + set darkTheme(darkTheme: boolean) { + if (darkTheme === this._darkTheme) return; + this._darkTheme = darkTheme; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { darkTheme }, + }); + this.onChangeEmitter.fire({ + property: 'darkTheme', + value: this._darkTheme, + }); + } + + get wsPort(): number { + return this._wsPort; + } + + set wsPort(wsPort: number) { + if (wsPort === this._wsPort) return; + this._wsPort = wsPort; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { wsPort }, + }); + this.onChangeEmitter.fire({ + property: 'wsPort', + value: this._wsPort, + }); + } + + get serialPort(): string { + return this._serialPort; + } + + set serialPort(serialPort: string) { + if (serialPort === this._serialPort) return; + this._serialPort = serialPort; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { serialPort }, + }); + this.storeState().then(() => + this.onChangeEmitter.fire({ + property: 'serialPort', + value: this._serialPort, + }) + ); + } + + get connected(): boolean { + return this._connected; + } + + set connected(connected: boolean) { + if (connected === this._connected) return; + this._connected = connected; + this.monitorManagerProxy.changeSettings({ + monitorUISettings: { connected }, + }); + this.onChangeEmitter.fire({ + property: 'connected', + value: this._connected, + }); + } + protected onMonitorSettingsDidChange = (settings: MonitorSettings): void => { const { monitorUISettings } = settings; if (!monitorUISettings) return; - const { autoscroll, interpolate, lineEnding, timestamp } = - monitorUISettings; + const { + autoscroll, + interpolate, + lineEnding, + timestamp, + darkTheme, + wsPort, + serialPort, + connected, + } = monitorUISettings; if (!isNullOrUndefined(autoscroll)) this.autoscroll = autoscroll; if (!isNullOrUndefined(interpolate)) this.interpolate = interpolate; if (!isNullOrUndefined(lineEnding)) this.lineEnding = lineEnding; if (!isNullOrUndefined(timestamp)) this.timestamp = timestamp; + if (!isNullOrUndefined(darkTheme)) this.darkTheme = darkTheme; + if (!isNullOrUndefined(wsPort)) this.wsPort = wsPort; + if (!isNullOrUndefined(serialPort)) this.serialPort = serialPort; + if (!isNullOrUndefined(connected)) this.connected = connected; }; } @@ -171,6 +259,10 @@ export namespace MonitorModel { timestamp: boolean; lineEnding: EOL; interpolate: boolean; + darkTheme: boolean; + wsPort: number; + serialPort: string; + connected: boolean; } export namespace State { export interface Change { diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index 4c5276b8e..eb3617776 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -207,6 +207,7 @@ export class MonitorWidget extends ReactWidget { diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx index 59ad9c9ad..7b7c00459 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx @@ -5,12 +5,13 @@ import { isOSX } from '@theia/core/lib/common/os'; import { DisposableCollection, nls } from '@theia/core/lib/common'; import { MonitorManagerProxyClient } from '../../../common/protocol'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; -import { timeout } from '@theia/core/lib/common/promise-util'; +import { MonitorModel } from '../../monitor-model'; export namespace SerialMonitorSendInput { export interface Props { readonly boardsServiceProvider: BoardsServiceProvider; readonly monitorManagerProxy: MonitorManagerProxyClient; + readonly monitorModel: MonitorModel; readonly onSend: (text: string) => void; readonly resolveFocus: (element: HTMLElement | undefined) => void; } @@ -35,25 +36,12 @@ export class SerialMonitorSendInput extends React.Component< } componentDidMount(): void { - this.setState({ connected: true }); - - const checkWSConnection = new Promise((resolve) => { - this.props.monitorManagerProxy.onWSConnectionChanged((connected) => { - this.setState({ connected }); - resolve(true); - }); - }); - - const checkWSTimeout = timeout(1000).then(() => false); - - Promise.race([checkWSConnection, checkWSTimeout]).then( - async (resolved) => { - if (!resolved) { - const connected = - await this.props.monitorManagerProxy.isWSConnected(); - this.setState({ connected }); - } - } + this.setState({ connected: this.props.monitorModel.connected }); + this.toDisposeBeforeUnmount.push( + this.props.monitorModel.onChange(({ property }) => { + if (property === 'connected') + this.setState({ connected: this.props.monitorModel.connected }); + }) ); } diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index f5a58e932..4e03c1815 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -65,6 +65,7 @@ export namespace Monitor { CHANGE_SETTINGS = 'CHANGE_SETTINGS', } + // Commands sent by the backend to the clients export enum MiddlewareCommand { ON_SETTINGS_DID_CHANGE = 'ON_SETTINGS_DID_CHANGE', } diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 9649367bb..0f01fc958 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -62,7 +62,7 @@ export class MonitorManager extends CoreClientAware { if (!monitor) { monitor = this.createMonitor(board, port); } - return await monitor.start(monitorID); + return await monitor.start(); } /** @@ -142,7 +142,7 @@ export class MonitorManager extends CoreClientAware { return Status.NOT_CONNECTED; } monitor.setUploadInProgress(false); - return await monitor.start(monitorID); + return await monitor.start(); } /** diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 945c1e76d..606f58bc6 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -123,19 +123,22 @@ export class MonitorService extends CoreClientAware implements Disposable { * Start and connects a monitor using currently set board and port. * If a monitor is already started or board fqbn, port address and/or protocol * are missing nothing happens. - * @param id * @returns a status to verify connection has been established. */ - async start(monitorID: string): Promise { + async start(): Promise { if (this.duplex) { + this.updateClientsSettings({ monitorUISettings: { connected: true } }); return Status.ALREADY_CONNECTED; } if (!this.board?.fqbn || !this.port?.address || !this.port?.protocol) { + this.updateClientsSettings({ monitorUISettings: { connected: false } }); + return Status.CONFIG_MISSING; } if (this.uploadInProgress) { + this.updateClientsSettings({ monitorUISettings: { connected: false } }); return Status.UPLOAD_IN_PROGRESS; } @@ -152,7 +155,7 @@ export class MonitorService extends CoreClientAware implements Disposable { pluggableMonitorSettings: { ...this.settings.pluggableMonitorSettings, ...(await this.monitorSettingsProvider.getSettings( - monitorID, + this.monitorID, defaultSettings )), }, @@ -165,12 +168,14 @@ export class MonitorService extends CoreClientAware implements Disposable { this.duplex .on('close', () => { this.duplex = null; + this.updateClientsSettings({ monitorUISettings: { connected: false } }); this.logger.info( `monitor to ${this.port?.address} using ${this.port?.protocol} closed by client` ); }) .on('end', () => { this.duplex = null; + this.updateClientsSettings({ monitorUISettings: { connected: false } }); this.logger.info( `monitor to ${this.port?.address} using ${this.port?.protocol} closed by server` ); @@ -223,6 +228,7 @@ export class MonitorService extends CoreClientAware implements Disposable { this.logger.info( `started monitor to ${this.port?.address} using ${this.port?.protocol}` ); + this.updateClientsSettings({ monitorUISettings: { connected: true } }); resolve(Status.OK); return; } @@ -385,12 +391,13 @@ export class MonitorService extends CoreClientAware implements Disposable { } } - const command: Monitor.Message = { - command: Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE, - data: { ...settings, pluggableMonitorSettings: reconciledSettings }, - }; - - this.webSocketProvider.sendMessage(JSON.stringify(command)); + this.updateClientsSettings({ + monitorUISettings: { + ...settings.monitorUISettings, + connected: !!this.duplex, + }, + pluggableMonitorSettings: reconciledSettings, + }); if (!this.duplex) { return Status.NOT_CONNECTED; @@ -439,6 +446,15 @@ export class MonitorService extends CoreClientAware implements Disposable { } } + updateClientsSettings(settings: MonitorSettings): void { + const command: Monitor.Message = { + command: Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE, + data: settings, + }; + + this.webSocketProvider.sendMessage(JSON.stringify(command)); + } + /** * Stops the necessary handlers to send and receive messages to * and from the frontend and the running monitor From 29a4ed4e48d7f7cd5f3b2fb7becd1edc6eb8add6 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 26 May 2022 15:43:26 +0200 Subject: [PATCH 42/66] reset serial monitor when port changes --- .../browser/arduino-ide-frontend-module.ts | 63 +++++++++++++------ .../monitor/monitor-view-contribution.tsx | 11 ++++ .../browser/serial/monitor/monitor-widget.tsx | 24 ++++++- 3 files changed, 76 insertions(+), 22 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 5f003e7cb..21cfeda9b 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -1,7 +1,10 @@ import '../../src/browser/style/index.css'; import { ContainerModule } from 'inversify'; import { WidgetFactory } from '@theia/core/lib/browser/widget-manager'; -import { CommandContribution } from '@theia/core/lib/common/command'; +import { + CommandContribution, + CommandRegistry, +} from '@theia/core/lib/common/command'; import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; import { TabBarToolbarContribution, @@ -152,7 +155,14 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, OutputChannelRegistryMainImpl, } from './theia/plugin-ext/output-channel-registry-main'; -import { ExecutableService, ExecutableServicePath, MonitorManagerProxy, MonitorManagerProxyClient, MonitorManagerProxyFactory, MonitorManagerProxyPath } from '../common/protocol'; +import { + ExecutableService, + ExecutableServicePath, + MonitorManagerProxy, + MonitorManagerProxyClient, + MonitorManagerProxyFactory, + MonitorManagerProxyPath, +} from '../common/protocol'; import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service'; import { ResponseServiceImpl } from './response-service-impl'; @@ -411,21 +421,35 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { createWidget: () => { return new MonitorWidget( context.container.get(MonitorModel), - context.container.get(MonitorManagerProxyClient), + context.container.get( + MonitorManagerProxyClient + ), context.container.get(BoardsServiceProvider), + context.container.get(CommandRegistry) ); - } + }, })); - bind(MonitorManagerProxyFactory).toFactory((context) => () => context.container.get(MonitorManagerProxy)) + bind(MonitorManagerProxyFactory).toFactory( + (context) => () => + context.container.get(MonitorManagerProxy) + ); - bind(MonitorManagerProxy).toDynamicValue((context) => - WebSocketConnectionProvider.createProxy(context.container, MonitorManagerProxyPath, context.container.get(MonitorManagerProxyClient)) - ).inSingletonScope(); + bind(MonitorManagerProxy) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + MonitorManagerProxyPath, + context.container.get(MonitorManagerProxyClient) + ) + ) + .inSingletonScope(); // Monitor manager proxy client to receive and delegate pluggable monitors // notifications from the backend - bind(MonitorManagerProxyClient).to(MonitorManagerProxyClientImpl).inSingletonScope(); + bind(MonitorManagerProxyClient) + .to(MonitorManagerProxyClientImpl) + .inSingletonScope(); bind(WorkspaceService).toSelf().inSingletonScope(); rebind(TheiaWorkspaceService).toService(WorkspaceService); @@ -482,11 +506,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope(); rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope(); rebind(TabBarToolbarFactory).toFactory( - ({ container: parentContainer }) => () => { - const container = parentContainer.createChild(); - container.bind(TabBarToolbar).toSelf().inSingletonScope(); - return container.get(TabBarToolbar); - } + ({ container: parentContainer }) => + () => { + const container = parentContainer.createChild(); + container.bind(TabBarToolbar).toSelf().inSingletonScope(); + return container.get(TabBarToolbar); + } ); bind(OutputWidget).toSelf().inSingletonScope(); rebind(TheiaOutputWidget).toService(OutputWidget); @@ -651,15 +676,13 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // Enable the dirty indicator on uncloseable widgets. rebind(TabBarRendererFactory).toFactory((context) => () => { - const contextMenuRenderer = context.container.get( - ContextMenuRenderer - ); + const contextMenuRenderer = + context.container.get(ContextMenuRenderer); const decoratorService = context.container.get( TabBarDecoratorService ); - const iconThemeService = context.container.get( - IconThemeService - ); + const iconThemeService = + context.container.get(IconThemeService); return new TabBarRenderer( contextMenuRenderer, decoratorService, diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index a335d6009..d6ddc2369 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -47,6 +47,7 @@ export class MonitorViewContribution static readonly TOGGLE_SERIAL_MONITOR = MonitorWidget.ID + ':toggle'; static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR = MonitorWidget.ID + ':toggle-toolbar'; + static readonly RESET_SERIAL_MONITOR = MonitorWidget.ID + ':reset'; @inject(MonitorModel) protected readonly model: MonitorModel; @@ -119,6 +120,10 @@ export class MonitorViewContribution } ); } + commands.registerCommand( + { id: MonitorViewContribution.RESET_SERIAL_MONITOR }, + { execute: () => this.reset() } + ); } protected async toggle(): Promise { @@ -130,6 +135,12 @@ export class MonitorViewContribution } } + protected async reset(): Promise { + const widget = this.tryGetWidget(); + if (widget) widget.dispose(); + await this.openView({ activate: true, reveal: true }); + } + protected renderAutoScrollButton(): React.ReactNode { return ( diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index eb3617776..4b1eb982a 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -13,10 +13,12 @@ import { ArduinoSelect } from '../../widgets/arduino-select'; import { SerialMonitorSendInput } from './serial-monitor-send-input'; import { SerialMonitorOutput } from './serial-monitor-send-output'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; -import { nls } from '@theia/core/lib/common'; +import { CommandRegistry, nls } from '@theia/core/lib/common'; import { MonitorManagerProxyClient } from '../../../common/protocol'; import { MonitorModel } from '../../monitor-model'; import { MonitorSettings } from '../../../node/monitor-settings/monitor-settings-provider'; +import { MonitorViewContribution } from './monitor-view-contribution'; +import { BoardsConfig } from '../../boards/boards-config'; @injectable() export class MonitorWidget extends ReactWidget { @@ -41,6 +43,8 @@ export class MonitorWidget extends ReactWidget { protected closing = false; protected readonly clearOutputEmitter = new Emitter(); + protected lastConnectedBoard: BoardsConfig.Config; + constructor( @inject(MonitorModel) protected readonly monitorModel: MonitorModel, @@ -49,7 +53,10 @@ export class MonitorWidget extends ReactWidget { protected readonly monitorManagerProxy: MonitorManagerProxyClient, @inject(BoardsServiceProvider) - protected readonly boardsServiceProvider: BoardsServiceProvider + protected readonly boardsServiceProvider: BoardsServiceProvider, + + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry ) { super(); this.id = MonitorWidget.ID; @@ -77,6 +84,19 @@ export class MonitorWidget extends ReactWidget { selectedBoard, selectedPort ); + + if ( + selectedBoard.fqbn !== + this.lastConnectedBoard?.selectedBoard?.fqbn || + selectedPort.id !== this.lastConnectedBoard?.selectedPort?.id + ) + await this.commandRegistry.executeCommand( + MonitorViewContribution.RESET_SERIAL_MONITOR + ); + this.lastConnectedBoard = { + selectedBoard, + selectedPort, + }; this.update(); } } From 97312fc8dcebdaad91ea2393db7e23bdc8f1733a Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 26 May 2022 15:45:00 +0200 Subject: [PATCH 43/66] fix serial plotter opening --- .../plotter/plotter-frontend-contribution.ts | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index 675fe00de..8928833a7 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -57,7 +57,7 @@ export class PlotterFrontendContribution extends Contribution { registerCommands(registry: CommandRegistry): void { registry.registerCommand(SerialPlotterContribution.Commands.OPEN, { - execute: this.connect.bind(this), + execute: this.startPlotter.bind(this), }); } @@ -69,7 +69,20 @@ export class PlotterFrontendContribution extends Contribution { }); } - async connect(): Promise { + async startPlotter(): Promise { + if ( + !this.boardsServiceProvider.boardsConfig.selectedBoard || + !this.boardsServiceProvider.boardsConfig.selectedPort + ) { + this.messageService.error( + `You need to select a connected board to start the serial plotter` + ); + return; + } + await this.monitorManagerProxy.startMonitor( + this.boardsServiceProvider.boardsConfig.selectedBoard, + this.boardsServiceProvider.boardsConfig.selectedPort + ); if (!!this.window) { this.window.focus(); return; @@ -104,8 +117,8 @@ export class PlotterFrontendContribution extends Contribution { darkTheme: this.themeService.getCurrentTheme().type === 'dark', wsPort, interpolate: this.model.interpolate, - connected: await this.monitorManagerProxy.isWSConnected(), - serialPort: this.boardsServiceProvider.boardsConfig.selectedPort?.address, + connected: this.model.connected, + serialPort: this.model.serialPort, }; const urlWithParams = queryString.stringifyUrl( { From 7fbcf338958901b2cf5e69b0e6c0d01fed767273 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 26 May 2022 18:18:49 +0200 Subject: [PATCH 44/66] refine monitor connection settings --- .../browser/arduino-ide-frontend-module.ts | 8 ++--- .../monitor-manager-proxy-client-impl.ts | 34 ++++++++++++------- .../browser/serial/monitor/monitor-widget.tsx | 23 ++----------- .../src/common/protocol/monitor-service.ts | 1 - 4 files changed, 26 insertions(+), 40 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 21cfeda9b..62eba7684 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -1,10 +1,7 @@ import '../../src/browser/style/index.css'; import { ContainerModule } from 'inversify'; import { WidgetFactory } from '@theia/core/lib/browser/widget-manager'; -import { - CommandContribution, - CommandRegistry, -} from '@theia/core/lib/common/command'; +import { CommandContribution } from '@theia/core/lib/common/command'; import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; import { TabBarToolbarContribution, @@ -424,8 +421,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { context.container.get( MonitorManagerProxyClient ), - context.container.get(BoardsServiceProvider), - context.container.get(CommandRegistry) + context.container.get(BoardsServiceProvider) ); }, })); diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 85fed2934..0ee9837c6 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -1,4 +1,4 @@ -import { Emitter, MessageService } from '@theia/core'; +import { CommandRegistry, Emitter, MessageService } from '@theia/core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { Board, Port } from '../common/protocol'; import { @@ -10,6 +10,8 @@ import { PluggableMonitorSettings, MonitorSettings, } from '../node/monitor-settings/monitor-settings-provider'; +import { BoardsConfig } from './boards/boards-config'; +import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; @injectable() export class MonitorManagerProxyClientImpl @@ -29,13 +31,11 @@ export class MonitorManagerProxyClientImpl readonly onMonitorSettingsDidChange = this.onMonitorSettingsDidChangeEmitter.event; - protected readonly onWSConnectionChangedEmitter = new Emitter(); - readonly onWSConnectionChanged = this.onWSConnectionChangedEmitter.event; - // WebSocket used to handle pluggable monitor communication between // frontend and backend. private webSocket?: WebSocket; private wsPort?: number; + private lastConnectedBoard: BoardsConfig.Config; getWebSocketPort(): number | undefined { return this.wsPort; @@ -47,20 +47,20 @@ export class MonitorManagerProxyClientImpl // This is necessary to call the backend methods from the frontend @inject(MonitorManagerProxyFactory) - protected server: MonitorManagerProxyFactory + protected server: MonitorManagerProxyFactory, + + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry ) {} /** * Connects a localhost WebSocket using the specified port. * @param addressPort port of the WebSocket */ - connect(addressPort: number): void { - if (this.webSocket) { - return; - } + async connect(addressPort: number): Promise { + if (!!this.webSocket && this.wsPort === addressPort) return; try { this.webSocket = new WebSocket(`ws://localhost:${addressPort}`); - this.onWSConnectionChangedEmitter.fire(true); } catch { this.messageService.error('Unable to connect to websocket'); return; @@ -87,7 +87,6 @@ export class MonitorManagerProxyClientImpl try { this.webSocket?.close(); this.webSocket = undefined; - this.onWSConnectionChangedEmitter.fire(false); } catch { this.messageService.error('Unable to close websocket'); } @@ -102,7 +101,18 @@ export class MonitorManagerProxyClientImpl port: Port, settings?: PluggableMonitorSettings ): Promise { - return this.server().startMonitor(board, port, settings); + await this.server().startMonitor(board, port, settings); + if ( + board.fqbn !== this.lastConnectedBoard?.selectedBoard?.fqbn || + port.id !== this.lastConnectedBoard?.selectedPort?.id + ) + await this.commandRegistry.executeCommand( + MonitorViewContribution.RESET_SERIAL_MONITOR + ); + this.lastConnectedBoard = { + selectedBoard: board, + selectedPort: port, + }; } getCurrentSettings(board: Board, port: Port): Promise { diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index 4b1eb982a..f5bf20493 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -13,12 +13,10 @@ import { ArduinoSelect } from '../../widgets/arduino-select'; import { SerialMonitorSendInput } from './serial-monitor-send-input'; import { SerialMonitorOutput } from './serial-monitor-send-output'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; -import { CommandRegistry, nls } from '@theia/core/lib/common'; +import { nls } from '@theia/core/lib/common'; import { MonitorManagerProxyClient } from '../../../common/protocol'; import { MonitorModel } from '../../monitor-model'; import { MonitorSettings } from '../../../node/monitor-settings/monitor-settings-provider'; -import { MonitorViewContribution } from './monitor-view-contribution'; -import { BoardsConfig } from '../../boards/boards-config'; @injectable() export class MonitorWidget extends ReactWidget { @@ -43,8 +41,6 @@ export class MonitorWidget extends ReactWidget { protected closing = false; protected readonly clearOutputEmitter = new Emitter(); - protected lastConnectedBoard: BoardsConfig.Config; - constructor( @inject(MonitorModel) protected readonly monitorModel: MonitorModel, @@ -53,10 +49,7 @@ export class MonitorWidget extends ReactWidget { protected readonly monitorManagerProxy: MonitorManagerProxyClient, @inject(BoardsServiceProvider) - protected readonly boardsServiceProvider: BoardsServiceProvider, - - @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry + protected readonly boardsServiceProvider: BoardsServiceProvider ) { super(); this.id = MonitorWidget.ID; @@ -85,18 +78,6 @@ export class MonitorWidget extends ReactWidget { selectedPort ); - if ( - selectedBoard.fqbn !== - this.lastConnectedBoard?.selectedBoard?.fqbn || - selectedPort.id !== this.lastConnectedBoard?.selectedPort?.id - ) - await this.commandRegistry.executeCommand( - MonitorViewContribution.RESET_SERIAL_MONITOR - ); - this.lastConnectedBoard = { - selectedBoard, - selectedPort, - }; this.update(); } } diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 4e03c1815..1a5d1d35f 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -30,7 +30,6 @@ export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); export interface MonitorManagerProxyClient { onMessagesReceived: Event<{ messages: string[] }>; onMonitorSettingsDidChange: Event; - onWSConnectionChanged: Event; connect(addressPort: number): void; disconnect(): void; getWebSocketPort(): number | undefined; From e4c7f0570bef88a06cff8cf58aeb20c5e0736527 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 27 May 2022 11:58:12 +0200 Subject: [PATCH 45/66] fix hanging web socket connections --- .../src/browser/monitor-manager-proxy-client-impl.ts | 6 +++++- .../browser/serial/monitor/monitor-view-contribution.tsx | 6 ++++-- arduino-ide-extension/src/node/monitor-service.ts | 3 +++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 0ee9837c6..86b069ead 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -58,7 +58,10 @@ export class MonitorManagerProxyClientImpl * @param addressPort port of the WebSocket */ async connect(addressPort: number): Promise { - if (!!this.webSocket && this.wsPort === addressPort) return; + if (!!this.webSocket) { + if (this.wsPort === addressPort) return; + else this.disconnect(); + } try { this.webSocket = new WebSocket(`ws://localhost:${addressPort}`); } catch { @@ -84,6 +87,7 @@ export class MonitorManagerProxyClientImpl * Disconnects the WebSocket if connected. */ disconnect(): void { + if (!this.webSocket) return; try { this.webSocket?.close(); this.webSocket = undefined; diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index d6ddc2369..ee919b162 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -137,8 +137,10 @@ export class MonitorViewContribution protected async reset(): Promise { const widget = this.tryGetWidget(); - if (widget) widget.dispose(); - await this.openView({ activate: true, reveal: true }); + if (widget) { + widget.dispose(); + await this.openView({ activate: true, reveal: true }); + } } protected renderAutoScrollButton(): React.ReactNode { diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 606f58bc6..a0d2a5acd 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -75,7 +75,9 @@ export class MonitorService extends CoreClientAware implements Disposable { // data from this monitor, we can freely close // and dispose it. this.dispose(); + return; } + this.updateClientsSettings(this.settings); }); this.portMonitorSettings(port.protocol, board.fqbn!).then( @@ -447,6 +449,7 @@ export class MonitorService extends CoreClientAware implements Disposable { } updateClientsSettings(settings: MonitorSettings): void { + this.settings = { ...this.settings, ...settings }; const command: Monitor.Message = { command: Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE, data: settings, From 5519a8258f657fe8acb872ce4479164deb007a0e Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 27 May 2022 12:30:15 +0200 Subject: [PATCH 46/66] add serial plotter reset command --- .../monitor-manager-proxy-client-impl.ts | 7 +++- .../plotter/plotter-frontend-contribution.ts | 34 ++++++++----------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 86b069ead..547b6b72c 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -12,6 +12,7 @@ import { } from '../node/monitor-settings/monitor-settings-provider'; import { BoardsConfig } from './boards/boards-config'; import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; +import { SerialPlotterContribution } from './serial/plotter/plotter-frontend-contribution'; @injectable() export class MonitorManagerProxyClientImpl @@ -109,10 +110,14 @@ export class MonitorManagerProxyClientImpl if ( board.fqbn !== this.lastConnectedBoard?.selectedBoard?.fqbn || port.id !== this.lastConnectedBoard?.selectedPort?.id - ) + ) { await this.commandRegistry.executeCommand( MonitorViewContribution.RESET_SERIAL_MONITOR ); + await this.commandRegistry.executeCommand( + SerialPlotterContribution.Commands.RESET.id + ); + } this.lastConnectedBoard = { selectedBoard: board, selectedPort: port, diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index 8928833a7..c10993126 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -23,6 +23,11 @@ export namespace SerialPlotterContribution { label: 'Serial Plotter', category: 'Arduino', }; + export const RESET: Command = { + id: 'serial-plotter-reset', + label: 'Reset Serial Plotter', + category: 'Arduino', + }; } } @@ -59,6 +64,9 @@ export class PlotterFrontendContribution extends Contribution { registry.registerCommand(SerialPlotterContribution.Commands.OPEN, { execute: this.startPlotter.bind(this), }); + registry.registerCommand(SerialPlotterContribution.Commands.RESET, { + execute: () => this.reset(), + }); } registerMenus(menus: MenuModelRegistry): void { @@ -96,28 +104,9 @@ export class PlotterFrontendContribution extends Contribution { } protected async open(wsPort: number): Promise { - const board = this.boardsServiceProvider.boardsConfig.selectedBoard; - const port = this.boardsServiceProvider.boardsConfig.selectedPort; - let baudrates: number[] = []; - let currentBaudrate = -1; - if (board && port) { - const { pluggableMonitorSettings } = - await this.monitorManagerProxy.getCurrentSettings(board, port); - if (pluggableMonitorSettings && 'baudrate' in pluggableMonitorSettings) { - // Convert from string to numbers - baudrates = pluggableMonitorSettings['baudrate'].values.map((b) => +b); - currentBaudrate = +pluggableMonitorSettings['baudrate'].selectedValue; - } - } - const initConfig: Partial = { - baudrates, - currentBaudrate, - currentLineEnding: this.model.lineEnding, darkTheme: this.themeService.getCurrentTheme().type === 'dark', wsPort, - interpolate: this.model.interpolate, - connected: this.model.connected, serialPort: this.model.serialPort, }; const urlWithParams = queryString.stringifyUrl( @@ -129,4 +118,11 @@ export class PlotterFrontendContribution extends Contribution { ); this.window = window.open(urlWithParams, 'serialPlotter'); } + + protected async reset(): Promise { + if (!!this.window) { + this.window.close(); + await this.startPlotter(); + } + } } From 9c1aba5fa539b669fd35f1d3bfbb89dc07a5c008 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 27 May 2022 12:38:57 +0200 Subject: [PATCH 47/66] send port to web socket clients --- arduino-ide-extension/src/node/monitor-service.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index a0d2a5acd..6cc297761 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -129,7 +129,9 @@ export class MonitorService extends CoreClientAware implements Disposable { */ async start(): Promise { if (this.duplex) { - this.updateClientsSettings({ monitorUISettings: { connected: true } }); + this.updateClientsSettings({ + monitorUISettings: { connected: true, serialPort: this.port.address }, + }); return Status.ALREADY_CONNECTED; } @@ -140,7 +142,9 @@ export class MonitorService extends CoreClientAware implements Disposable { } if (this.uploadInProgress) { - this.updateClientsSettings({ monitorUISettings: { connected: false } }); + this.updateClientsSettings({ + monitorUISettings: { connected: false, serialPort: this.port.address }, + }); return Status.UPLOAD_IN_PROGRESS; } @@ -230,7 +234,9 @@ export class MonitorService extends CoreClientAware implements Disposable { this.logger.info( `started monitor to ${this.port?.address} using ${this.port?.protocol}` ); - this.updateClientsSettings({ monitorUISettings: { connected: true } }); + this.updateClientsSettings({ + monitorUISettings: { connected: true, serialPort: this.port.address }, + }); resolve(Status.OK); return; } @@ -397,6 +403,7 @@ export class MonitorService extends CoreClientAware implements Disposable { monitorUISettings: { ...settings.monitorUISettings, connected: !!this.duplex, + serialPort: this.port.address, }, pluggableMonitorSettings: reconciledSettings, }); From 16e2423d22351311f425fde467771436a5f962dd Mon Sep 17 00:00:00 2001 From: David Simpson <45690499+davegarthsimpson@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:36:16 +0200 Subject: [PATCH 48/66] monitor service wait for success serial port open --- .../src/node/monitor-service.ts | 169 +++++++++++------- 1 file changed, 106 insertions(+), 63 deletions(-) diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 6cc297761..d81b518cd 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -21,6 +21,17 @@ import { import { Deferred } from '@theia/core/lib/common/promise-util'; export const MonitorServiceName = 'monitor-service'; +type DuplexHandlerKeys = + | 'close' + | 'end' + | 'error' + | 'data' + | 'status' + | 'metadata'; +interface DuplexHandler { + key: DuplexHandlerKeys; + callback: (...args: any) => void; +} export class MonitorService extends CoreClientAware implements Disposable { // Bidirectional gRPC stream used to receive and send data from the running @@ -121,6 +132,15 @@ export class MonitorService extends CoreClientAware implements Disposable { return !!this.duplex; } + setDuplexHandlers( + duplex: ClientDuplexStream, + handlers: DuplexHandler[] + ): void { + for (const handler of handlers) { + duplex.on(handler.key, handler.callback); + } + } + /** * Start and connects a monitor using currently set board and port. * If a monitor is already started or board fqbn, port address and/or protocol @@ -169,45 +189,8 @@ export class MonitorService extends CoreClientAware implements Disposable { await this.coreClientProvider.initialized; const coreClient = await this.coreClient(); - const { client, instance } = coreClient; - this.duplex = client.monitor(); - this.duplex - .on('close', () => { - this.duplex = null; - this.updateClientsSettings({ monitorUISettings: { connected: false } }); - this.logger.info( - `monitor to ${this.port?.address} using ${this.port?.protocol} closed by client` - ); - }) - .on('end', () => { - this.duplex = null; - this.updateClientsSettings({ monitorUISettings: { connected: false } }); - this.logger.info( - `monitor to ${this.port?.address} using ${this.port?.protocol} closed by server` - ); - }) - .on('error', (err: Error) => { - this.logger.error(err); - // TODO - // this.theiaFEClient?.notifyError() - }) - .on( - 'data', - ((res: MonitorResponse) => { - if (res.getError()) { - // TODO: Maybe disconnect - this.logger.error(res.getError()); - return; - } - const data = res.getRxData(); - const message = - typeof data === 'string' - ? data - : new TextDecoder('utf8').decode(data); - this.messages.push(...splitLines(message)); - }).bind(this) - ); + const { instance } = coreClient; const req = new MonitorRequest(); req.setInstance(instance); if (this.board?.fqbn) { @@ -228,34 +211,94 @@ export class MonitorService extends CoreClientAware implements Disposable { } req.setPortConfiguration(config); - const connect = new Promise((resolve) => { - if (this.duplex?.write(req)) { - this.startMessagesHandlers(); - this.logger.info( - `started monitor to ${this.port?.address} using ${this.port?.protocol}` - ); - this.updateClientsSettings({ - monitorUISettings: { connected: true, serialPort: this.port.address }, - }); - resolve(Status.OK); - return; - } + // Promise executor + const writeToStream = (resolve: (value: boolean) => void) => { + this.duplex = this.duplex || coreClient.client.monitor(); + + const duplexHandlers: DuplexHandler[] = [ + { + key: 'close', + callback: () => { + this.duplex = null; + this.updateClientsSettings({ + monitorUISettings: { connected: false }, + }); + this.logger.info( + `monitor to ${this.port?.address} using ${this.port?.protocol} closed by client` + ); + }, + }, + { + key: 'end', + callback: () => { + this.duplex = null; + this.updateClientsSettings({ + monitorUISettings: { connected: false }, + }); + this.logger.info( + `monitor to ${this.port?.address} using ${this.port?.protocol} closed by server` + ); + }, + }, + { + key: 'error', + callback: (err: Error) => { + this.logger.error(err); + resolve(false); + // TODO + // this.theiaFEClient?.notifyError() + }, + }, + { + key: 'data', + callback: (res: MonitorResponse) => { + if (res.getError()) { + // TODO: Maybe disconnect + this.logger.error(res.getError()); + return; + } + const data = res.getRxData(); + const message = + typeof data === 'string' + ? data + : new TextDecoder('utf8').decode(data); + this.messages.push(...splitLines(message)); + + // if (res.getSuccess()) { + // resolve(true); + // return; + // } + }, + }, + ]; + + this.setDuplexHandlers(this.duplex, duplexHandlers); + this.duplex.write(req); + }; + + let attemptsRemaining = 10; + let wroteToStreamWithoutError = false; + do { + await new Promise((r) => setTimeout(r, 10000)); + wroteToStreamWithoutError = await new Promise(writeToStream); + attemptsRemaining -= 1; + } while (!wroteToStreamWithoutError && attemptsRemaining > 0); + + if (wroteToStreamWithoutError) { + this.startMessagesHandlers(); + this.logger.info( + `started monitor to ${this.port?.address} using ${this.port?.protocol}` + ); + this.updateClientsSettings({ + monitorUISettings: { connected: true, serialPort: this.port.address }, + }); + return Status.OK; + } else { this.logger.warn( `failed starting monitor to ${this.port?.address} using ${this.port?.protocol}` ); - resolve(Status.NOT_CONNECTED); - }); - - const connectTimeout = new Promise((resolve) => { - setTimeout(async () => { - this.logger.warn( - `timeout starting monitor to ${this.port?.address} using ${this.port?.protocol}` - ); - resolve(Status.NOT_CONNECTED); - }, 1000); - }); - // Try opening a monitor connection with a timeout - return await Promise.race([connect, connectTimeout]); + return Status.NOT_CONNECTED; + } } /** From 959e2572a2241b1e0f41ca62a8c322f5b3eb7704 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Wed, 1 Jun 2022 11:37:03 +0200 Subject: [PATCH 49/66] fix reset loop --- .../monitor-manager-proxy-client-impl.ts | 78 +++++++++++++------ .../monitor/monitor-view-contribution.tsx | 11 ++- .../browser/serial/monitor/monitor-widget.tsx | 25 +----- .../monitor/serial-monitor-send-input.tsx | 2 - .../plotter/plotter-frontend-contribution.ts | 17 +--- .../src/common/protocol/monitor-service.ts | 7 +- .../src/node/monitor-service.ts | 44 +++++++---- 7 files changed, 98 insertions(+), 86 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 547b6b72c..f72b0974c 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -1,4 +1,9 @@ -import { CommandRegistry, Emitter, MessageService } from '@theia/core'; +import { + CommandRegistry, + Disposable, + Emitter, + MessageService, +} from '@theia/core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { Board, Port } from '../common/protocol'; import { @@ -11,8 +16,7 @@ import { MonitorSettings, } from '../node/monitor-settings/monitor-settings-provider'; import { BoardsConfig } from './boards/boards-config'; -import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; -import { SerialPlotterContribution } from './serial/plotter/plotter-frontend-contribution'; +import { BoardsServiceProvider } from './boards/boards-service-provider'; @injectable() export class MonitorManagerProxyClientImpl @@ -32,11 +36,15 @@ export class MonitorManagerProxyClientImpl readonly onMonitorSettingsDidChange = this.onMonitorSettingsDidChangeEmitter.event; + protected readonly onMonitorShouldResetEmitter = new Emitter(); + readonly onMonitorShouldReset = this.onMonitorShouldResetEmitter.event; + // WebSocket used to handle pluggable monitor communication between // frontend and backend. private webSocket?: WebSocket; private wsPort?: number; private lastConnectedBoard: BoardsConfig.Config; + private onBoardsConfigChanged: Disposable | undefined; getWebSocketPort(): number | undefined { return this.wsPort; @@ -51,7 +59,10 @@ export class MonitorManagerProxyClientImpl protected server: MonitorManagerProxyFactory, @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry + protected readonly commandRegistry: CommandRegistry, + + @inject(BoardsServiceProvider) + protected readonly boardsServiceProvider: BoardsServiceProvider ) {} /** @@ -89,6 +100,8 @@ export class MonitorManagerProxyClientImpl */ disconnect(): void { if (!this.webSocket) return; + this.onBoardsConfigChanged?.dispose(); + this.onBoardsConfigChanged = undefined; try { this.webSocket?.close(); this.webSocket = undefined; @@ -101,27 +114,46 @@ export class MonitorManagerProxyClientImpl return !!this.webSocket; } - async startMonitor( - board: Board, - port: Port, - settings?: PluggableMonitorSettings - ): Promise { - await this.server().startMonitor(board, port, settings); - if ( - board.fqbn !== this.lastConnectedBoard?.selectedBoard?.fqbn || - port.id !== this.lastConnectedBoard?.selectedPort?.id - ) { - await this.commandRegistry.executeCommand( - MonitorViewContribution.RESET_SERIAL_MONITOR - ); - await this.commandRegistry.executeCommand( - SerialPlotterContribution.Commands.RESET.id - ); - } + async startMonitor(settings?: PluggableMonitorSettings): Promise { this.lastConnectedBoard = { - selectedBoard: board, - selectedPort: port, + selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard, + selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort, }; + + if (!this.onBoardsConfigChanged) { + this.onBoardsConfigChanged = + this.boardsServiceProvider.onBoardsConfigChanged( + async ({ selectedBoard, selectedPort }) => { + if ( + typeof selectedBoard === 'undefined' || + typeof selectedPort === 'undefined' + ) + return; + + // a board is plugged and it's different from the old connected board + if ( + selectedBoard?.fqbn !== + this.lastConnectedBoard?.selectedBoard?.fqbn || + selectedPort?.id !== this.lastConnectedBoard?.selectedPort?.id + ) { + this.onMonitorShouldResetEmitter.fire(null); + this.lastConnectedBoard = { + selectedBoard: selectedBoard, + selectedPort: selectedPort, + }; + } else { + // a board is plugged and it's the same as prev, rerun "this.startMonitor" to + // recreate the listener callback + this.startMonitor(); + } + } + ); + } + + const { selectedBoard, selectedPort } = + this.boardsServiceProvider.boardsConfig; + if (!selectedBoard || !selectedBoard.fqbn || !selectedPort) return; + await this.server().startMonitor(selectedBoard, selectedPort, settings); } getCurrentSettings(board: Board, port: Port): Promise { diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index fdea93afe..002489c48 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -11,6 +11,7 @@ import { ArduinoToolbar } from '../../toolbar/arduino-toolbar'; import { ArduinoMenus } from '../../menu/arduino-menus'; import { nls } from '@theia/core/lib/common'; import { MonitorModel } from '../../monitor-model'; +import { MonitorManagerProxyClient } from '../../../common/protocol'; export namespace SerialMonitor { export namespace Commands { @@ -49,10 +50,13 @@ export class MonitorViewContribution MonitorWidget.ID + ':toggle-toolbar'; static readonly RESET_SERIAL_MONITOR = MonitorWidget.ID + ':reset'; - @inject(MonitorModel) - protected readonly model: MonitorModel; + constructor( + @inject(MonitorModel) + protected readonly model: MonitorModel, - constructor() { + @inject(MonitorManagerProxyClient) + protected readonly monitorManagerProxy: MonitorManagerProxyClient + ) { super({ widgetId: MonitorWidget.ID, widgetName: MonitorWidget.LABEL, @@ -62,6 +66,7 @@ export class MonitorViewContribution toggleCommandId: MonitorViewContribution.TOGGLE_SERIAL_MONITOR, toggleKeybinding: 'CtrlCmd+Shift+M', }); + this.monitorManagerProxy.onMonitorShouldReset(() => this.reset()); } registerMenus(menus: MenuModelRegistry): void { diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index ab4fd7f9a..9553bc168 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -61,28 +61,6 @@ export class MonitorWidget extends ReactWidget { this.toDispose.push( Disposable.create(() => this.monitorManagerProxy.disconnect()) ); - - // Start monitor right away if there is already a board/port combination selected - const { selectedBoard, selectedPort } = - this.boardsServiceProvider.boardsConfig; - if (selectedBoard && selectedBoard.fqbn && selectedPort) { - this.monitorManagerProxy.startMonitor(selectedBoard, selectedPort); - } - - this.toDispose.push( - this.boardsServiceProvider.onBoardsConfigChanged( - async ({ selectedBoard, selectedPort }) => { - if (selectedBoard && selectedBoard.fqbn && selectedPort) { - await this.monitorManagerProxy.startMonitor( - selectedBoard, - selectedPort - ); - - this.update(); - } - } - ) - ); } protected onBeforeAttach(msg: Message): void { @@ -92,6 +70,8 @@ export class MonitorWidget extends ReactWidget { this.monitorManagerProxy.onMonitorSettingsDidChange( this.onMonitorSettingsDidChange.bind(this) ); + + this.monitorManagerProxy.startMonitor(); } onMonitorSettingsDidChange(settings: MonitorSettings): void { @@ -207,7 +187,6 @@ export class MonitorWidget extends ReactWidget {
void; readonly resolveFocus: (element: HTMLElement | undefined) => void; diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index 24a3ce6cf..448c10e6a 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -14,6 +14,7 @@ import { MonitorManagerProxyClient } from '../../../common/protocol'; import { SerialPlotter } from './protocol'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { MonitorModel } from '../../monitor-model'; + const queryString = require('query-string'); export namespace SerialPlotterContribution { @@ -57,6 +58,8 @@ export class PlotterFrontendContribution extends Contribution { this.window = null; } }); + this.monitorManagerProxy.onMonitorShouldReset(() => this.reset()); + return super.onStart(app); } @@ -78,19 +81,7 @@ export class PlotterFrontendContribution extends Contribution { } async startPlotter(): Promise { - if ( - !this.boardsServiceProvider.boardsConfig.selectedBoard || - !this.boardsServiceProvider.boardsConfig.selectedPort - ) { - this.messageService.error( - `You need to select a connected board to start the serial plotter` - ); - return; - } - await this.monitorManagerProxy.startMonitor( - this.boardsServiceProvider.boardsConfig.selectedBoard, - this.boardsServiceProvider.boardsConfig.selectedPort - ); + await this.monitorManagerProxy.startMonitor(); if (!!this.window) { this.window.focus(); return; diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 1a5d1d35f..7374951db 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -30,15 +30,12 @@ export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); export interface MonitorManagerProxyClient { onMessagesReceived: Event<{ messages: string[] }>; onMonitorSettingsDidChange: Event; + onMonitorShouldReset: Event; connect(addressPort: number): void; disconnect(): void; getWebSocketPort(): number | undefined; isWSConnected(): Promise; - startMonitor( - board: Board, - port: Port, - settings?: PluggableMonitorSettings - ): Promise; + startMonitor(settings?: PluggableMonitorSettings): Promise; getCurrentSettings(board: Board, port: Port): Promise; send(message: string): void; changeSettings(settings: MonitorSettings): void; diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index d81b518cd..6a0a1f314 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -62,6 +62,7 @@ export class MonitorService extends CoreClientAware implements Disposable { protected uploadInProgress = false; protected _initialized = new Deferred(); + protected creating: Deferred; constructor( @inject(ILogger) @@ -121,6 +122,7 @@ export class MonitorService extends CoreClientAware implements Disposable { dispose(): void { this.stop(); this.onDisposeEmitter.fire(); + this.onWSClientsNumberChanged?.dispose(); } /** @@ -148,24 +150,30 @@ export class MonitorService extends CoreClientAware implements Disposable { * @returns a status to verify connection has been established. */ async start(): Promise { + if (this.creating?.state === 'unresolved') return this.creating.promise; + this.creating = new Deferred(); if (this.duplex) { this.updateClientsSettings({ monitorUISettings: { connected: true, serialPort: this.port.address }, }); - return Status.ALREADY_CONNECTED; + this.creating.resolve(Status.ALREADY_CONNECTED); + return this.creating.promise; } if (!this.board?.fqbn || !this.port?.address || !this.port?.protocol) { this.updateClientsSettings({ monitorUISettings: { connected: false } }); - return Status.CONFIG_MISSING; + this.creating.resolve(Status.CONFIG_MISSING); + return this.creating.promise; } if (this.uploadInProgress) { this.updateClientsSettings({ monitorUISettings: { connected: false, serialPort: this.port.address }, }); - return Status.UPLOAD_IN_PROGRESS; + + this.creating.resolve(Status.UPLOAD_IN_PROGRESS); + return this.creating.promise; } this.logger.info('starting monitor'); @@ -213,7 +221,7 @@ export class MonitorService extends CoreClientAware implements Disposable { // Promise executor const writeToStream = (resolve: (value: boolean) => void) => { - this.duplex = this.duplex || coreClient.client.monitor(); + this.duplex = coreClient.client.monitor(); const duplexHandlers: DuplexHandler[] = [ { @@ -257,17 +265,16 @@ export class MonitorService extends CoreClientAware implements Disposable { this.logger.error(res.getError()); return; } + if (res.getSuccess()) { + resolve(true); + return; + } const data = res.getRxData(); const message = typeof data === 'string' ? data : new TextDecoder('utf8').decode(data); this.messages.push(...splitLines(message)); - - // if (res.getSuccess()) { - // resolve(true); - // return; - // } }, }, ]; @@ -277,14 +284,15 @@ export class MonitorService extends CoreClientAware implements Disposable { }; let attemptsRemaining = 10; - let wroteToStreamWithoutError = false; - do { - await new Promise((r) => setTimeout(r, 10000)); - wroteToStreamWithoutError = await new Promise(writeToStream); + let wroteToStreamSuccessfully = false; + while (attemptsRemaining > 0) { + wroteToStreamSuccessfully = await new Promise(writeToStream); + if (wroteToStreamSuccessfully) break; attemptsRemaining -= 1; - } while (!wroteToStreamWithoutError && attemptsRemaining > 0); + await new Promise((r) => setTimeout(r, 2000)); + } - if (wroteToStreamWithoutError) { + if (wroteToStreamSuccessfully) { this.startMessagesHandlers(); this.logger.info( `started monitor to ${this.port?.address} using ${this.port?.protocol}` @@ -292,12 +300,14 @@ export class MonitorService extends CoreClientAware implements Disposable { this.updateClientsSettings({ monitorUISettings: { connected: true, serialPort: this.port.address }, }); - return Status.OK; + this.creating.resolve(Status.OK); + return this.creating.promise; } else { this.logger.warn( `failed starting monitor to ${this.port?.address} using ${this.port?.protocol}` ); - return Status.NOT_CONNECTED; + this.creating.resolve(Status.NOT_CONNECTED); + return this.creating.promise; } } From 1baefdf73057c09aa7752caca1062dff9e4bec61 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 3 Jun 2022 10:49:25 +0200 Subject: [PATCH 50/66] update serial plotter version --- arduino-ide-extension/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index bbfb73a3c..7843d8028 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -58,7 +58,7 @@ "@types/temp": "^0.8.34", "@types/which": "^1.3.1", "ajv": "^6.5.3", - "arduino-serial-plotter-webapp": "0.0.17", + "arduino-serial-plotter-webapp": "0.1.0", "async-mutex": "^0.3.0", "atob": "^2.1.2", "auth0-js": "^9.14.0", diff --git a/yarn.lock b/yarn.lock index 1ca796316..b0c642de0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4327,10 +4327,10 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -arduino-serial-plotter-webapp@0.0.17: - version "0.0.17" - resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.17.tgz#9a304df2a2fc95d9ec812b0d56288643292dd151" - integrity sha512-JGXFm2uJ+izzhk45ayq1ioXJOi5IZyK9De9fjCHCJKvc3BSGqBToZmRr3r1W5GPMfO88ySrGn9pfzZQtgI8Isg== +arduino-serial-plotter-webapp@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.1.0.tgz#fa631483a93a12acd89d7bbe0487a3c0e57fac9f" + integrity sha512-0gHDGDz6guIC7Y8JXHaUad0RoueG2A+ykKNY1yo59+hWGbkM37hdRy4GKLsOkn0NMqU1TjnWmQHaSmYJjD1cAQ== are-we-there-yet@^2.0.0: version "2.0.0" From 4e4ed970a36dae121423f2ddecca1688361612d6 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 3 Jun 2022 11:06:11 +0200 Subject: [PATCH 51/66] update arduino-cli version to 0.23.0-rc1 and regenerate grpc protocol --- arduino-ide-extension/package.json | 2 +- .../arduino/cli/commands/v1/commands_pb.d.ts | 17 + .../cc/arduino/cli/commands/v1/commands_pb.js | 120 ++++++- .../cc/arduino/cli/commands/v1/common_pb.d.ts | 51 ++- .../cc/arduino/cli/commands/v1/common_pb.js | 294 ++++++++++++++++-- .../arduino/cli/commands/v1/compile_pb.d.ts | 24 +- .../cc/arduino/cli/commands/v1/compile_pb.js | 128 ++++++-- .../arduino/cli/commands/v1/monitor_pb.d.ts | 4 + .../cc/arduino/cli/commands/v1/monitor_pb.js | 32 +- .../cc/arduino/cli/commands/v1/upload_pb.d.ts | 4 - .../cc/arduino/cli/commands/v1/upload_pb.js | 32 +- 11 files changed, 607 insertions(+), 101 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 7843d8028..f185231b3 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -157,7 +157,7 @@ ], "arduino": { "cli": { - "version": "0.22.0" + "version": "0.23.0-rc1" }, "fwuploader": { "version": "2.0.0" diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts index 94ceab14f..a15ce4719 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts @@ -62,6 +62,12 @@ export class InitRequest extends jspb.Message { getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): InitRequest; + getProfile(): string; + setProfile(value: string): InitRequest; + + getSketchPath(): string; + setSketchPath(value: string): InitRequest; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): InitRequest.AsObject; @@ -76,6 +82,8 @@ export class InitRequest extends jspb.Message { export namespace InitRequest { export type AsObject = { instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, + profile: string, + sketchPath: string, } } @@ -93,6 +101,12 @@ export class InitResponse extends jspb.Message { setError(value?: google_rpc_status_pb.Status): InitResponse; + hasProfile(): boolean; + clearProfile(): void; + getProfile(): cc_arduino_cli_commands_v1_common_pb.Profile | undefined; + setProfile(value?: cc_arduino_cli_commands_v1_common_pb.Profile): InitResponse; + + getMessageCase(): InitResponse.MessageCase; serializeBinary(): Uint8Array; @@ -109,6 +123,7 @@ export namespace InitResponse { export type AsObject = { initProgress?: InitResponse.Progress.AsObject, error?: google_rpc_status_pb.Status.AsObject, + profile?: cc_arduino_cli_commands_v1_common_pb.Profile.AsObject, } @@ -151,6 +166,8 @@ export namespace InitResponse { ERROR = 2, + PROFILE = 3, + } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js index 1937aa229..820634067 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js @@ -866,7 +866,9 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.toObject = function(opt_i */ proto.cc.arduino.cli.commands.v1.InitRequest.toObject = function(includeInstance, msg) { var f, obj = { - instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f) + instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), + profile: jspb.Message.getFieldWithDefault(msg, 2, ""), + sketchPath: jspb.Message.getFieldWithDefault(msg, 3, "") }; if (includeInstance) { @@ -908,6 +910,14 @@ proto.cc.arduino.cli.commands.v1.InitRequest.deserializeBinaryFromReader = funct reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader); msg.setInstance(value); break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setProfile(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setSketchPath(value); + break; default: reader.skipField(); break; @@ -945,6 +955,20 @@ proto.cc.arduino.cli.commands.v1.InitRequest.serializeBinaryToWriter = function( cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter ); } + f = message.getProfile(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getSketchPath(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } }; @@ -985,6 +1009,42 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.hasInstance = function() }; +/** + * optional string profile = 2; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.getProfile = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InitRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.setProfile = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string sketch_path = 3; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.getSketchPath = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InitRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.setSketchPath = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + /** * Oneof group definitions for this message. Each group defines the field @@ -994,7 +1054,7 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.hasInstance = function() * @private {!Array>} * @const */ -proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2]]; +proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2,3]]; /** * @enum {number} @@ -1002,7 +1062,8 @@ proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2]]; proto.cc.arduino.cli.commands.v1.InitResponse.MessageCase = { MESSAGE_NOT_SET: 0, INIT_PROGRESS: 1, - ERROR: 2 + ERROR: 2, + PROFILE: 3 }; /** @@ -1044,7 +1105,8 @@ proto.cc.arduino.cli.commands.v1.InitResponse.prototype.toObject = function(opt_ proto.cc.arduino.cli.commands.v1.InitResponse.toObject = function(includeInstance, msg) { var f, obj = { initProgress: (f = msg.getInitProgress()) && proto.cc.arduino.cli.commands.v1.InitResponse.Progress.toObject(includeInstance, f), - error: (f = msg.getError()) && google_rpc_status_pb.Status.toObject(includeInstance, f) + error: (f = msg.getError()) && google_rpc_status_pb.Status.toObject(includeInstance, f), + profile: (f = msg.getProfile()) && cc_arduino_cli_commands_v1_common_pb.Profile.toObject(includeInstance, f) }; if (includeInstance) { @@ -1091,6 +1153,11 @@ proto.cc.arduino.cli.commands.v1.InitResponse.deserializeBinaryFromReader = func reader.readMessage(value,google_rpc_status_pb.Status.deserializeBinaryFromReader); msg.setError(value); break; + case 3: + var value = new cc_arduino_cli_commands_v1_common_pb.Profile; + reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Profile.deserializeBinaryFromReader); + msg.setProfile(value); + break; default: reader.skipField(); break; @@ -1136,6 +1203,14 @@ proto.cc.arduino.cli.commands.v1.InitResponse.serializeBinaryToWriter = function google_rpc_status_pb.Status.serializeBinaryToWriter ); } + f = message.getProfile(); + if (f != null) { + writer.writeMessage( + 3, + f, + cc_arduino_cli_commands_v1_common_pb.Profile.serializeBinaryToWriter + ); + } }; @@ -1415,6 +1490,43 @@ proto.cc.arduino.cli.commands.v1.InitResponse.prototype.hasError = function() { }; +/** + * optional Profile profile = 3; + * @return {?proto.cc.arduino.cli.commands.v1.Profile} + */ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.getProfile = function() { + return /** @type{?proto.cc.arduino.cli.commands.v1.Profile} */ ( + jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.Profile, 3)); +}; + + +/** + * @param {?proto.cc.arduino.cli.commands.v1.Profile|undefined} value + * @return {!proto.cc.arduino.cli.commands.v1.InitResponse} returns this +*/ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.setProfile = function(value) { + return jspb.Message.setOneofWrapperField(this, 3, proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.cc.arduino.cli.commands.v1.InitResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.clearProfile = function() { + return this.setProfile(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.hasProfile = function() { + return jspb.Message.getField(this, 3) != null; +}; + + diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts index 571b58afe..a7bd5e06c 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts @@ -185,28 +185,36 @@ export namespace Platform { } } -export class PlatformReference extends jspb.Message { +export class InstalledPlatformReference extends jspb.Message { getId(): string; - setId(value: string): PlatformReference; + setId(value: string): InstalledPlatformReference; getVersion(): string; - setVersion(value: string): PlatformReference; + setVersion(value: string): InstalledPlatformReference; + + getInstallDir(): string; + setInstallDir(value: string): InstalledPlatformReference; + + getPackageUrl(): string; + setPackageUrl(value: string): InstalledPlatformReference; serializeBinary(): Uint8Array; - toObject(includeInstance?: boolean): PlatformReference.AsObject; - static toObject(includeInstance: boolean, msg: PlatformReference): PlatformReference.AsObject; + toObject(includeInstance?: boolean): InstalledPlatformReference.AsObject; + static toObject(includeInstance: boolean, msg: InstalledPlatformReference): InstalledPlatformReference.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; - static serializeBinaryToWriter(message: PlatformReference, writer: jspb.BinaryWriter): void; - static deserializeBinary(bytes: Uint8Array): PlatformReference; - static deserializeBinaryFromReader(message: PlatformReference, reader: jspb.BinaryReader): PlatformReference; + static serializeBinaryToWriter(message: InstalledPlatformReference, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): InstalledPlatformReference; + static deserializeBinaryFromReader(message: InstalledPlatformReference, reader: jspb.BinaryReader): InstalledPlatformReference; } -export namespace PlatformReference { +export namespace InstalledPlatformReference { export type AsObject = { id: string, version: string, + installDir: string, + packageUrl: string, } } @@ -234,3 +242,28 @@ export namespace Board { fqbn: string, } } + +export class Profile extends jspb.Message { + getName(): string; + setName(value: string): Profile; + + getFqbn(): string; + setFqbn(value: string): Profile; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): Profile.AsObject; + static toObject(includeInstance: boolean, msg: Profile): Profile.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: Profile, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): Profile; + static deserializeBinaryFromReader(message: Profile, reader: jspb.BinaryReader): Profile; +} + +export namespace Profile { + export type AsObject = { + name: string, + fqbn: string, + } +} diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js index efd86871e..8b3916047 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js @@ -17,9 +17,10 @@ var global = Function('return this')(); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Board', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgress', null, global); +goog.exportSymbol('proto.cc.arduino.cli.commands.v1.InstalledPlatformReference', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Instance', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Platform', null, global); -goog.exportSymbol('proto.cc.arduino.cli.commands.v1.PlatformReference', null, global); +goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Profile', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Programmer', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.TaskProgress', null, global); /** @@ -137,16 +138,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.cc.arduino.cli.commands.v1.PlatformReference = function(opt_data) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference = function(opt_data) { jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; -goog.inherits(proto.cc.arduino.cli.commands.v1.PlatformReference, jspb.Message); +goog.inherits(proto.cc.arduino.cli.commands.v1.InstalledPlatformReference, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.cc.arduino.cli.commands.v1.PlatformReference.displayName = 'proto.cc.arduino.cli.commands.v1.PlatformReference'; + proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.displayName = 'proto.cc.arduino.cli.commands.v1.InstalledPlatformReference'; } /** * Generated by JsPbCodeGenerator. @@ -169,6 +170,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.cc.arduino.cli.commands.v1.Board.displayName = 'proto.cc.arduino.cli.commands.v1.Board'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.commands.v1.Profile = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.commands.v1.Profile, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.cc.arduino.cli.commands.v1.Profile.displayName = 'proto.cc.arduino.cli.commands.v1.Profile'; +} @@ -1405,8 +1427,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.toObject = function(opt_includeInstance) { - return proto.cc.arduino.cli.commands.v1.PlatformReference.toObject(opt_includeInstance, this); +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.toObject(opt_includeInstance, this); }; @@ -1415,14 +1437,16 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.toObject = function * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} msg The msg instance to transform. + * @param {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.cc.arduino.cli.commands.v1.PlatformReference.toObject = function(includeInstance, msg) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.toObject = function(includeInstance, msg) { var f, obj = { id: jspb.Message.getFieldWithDefault(msg, 1, ""), - version: jspb.Message.getFieldWithDefault(msg, 2, "") + version: jspb.Message.getFieldWithDefault(msg, 2, ""), + installDir: jspb.Message.getFieldWithDefault(msg, 3, ""), + packageUrl: jspb.Message.getFieldWithDefault(msg, 4, "") }; if (includeInstance) { @@ -1436,23 +1460,23 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.toObject = function(includeIn /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinary = function(bytes) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.cc.arduino.cli.commands.v1.PlatformReference; - return proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader(msg, reader); + var msg = new proto.cc.arduino.cli.commands.v1.InstalledPlatformReference; + return proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} msg The message object to deserialize into. + * @param {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader = function(msg, reader) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -1467,6 +1491,14 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader = var value = /** @type {string} */ (reader.readString()); msg.setVersion(value); break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setInstallDir(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setPackageUrl(value); + break; default: reader.skipField(); break; @@ -1480,9 +1512,9 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader = * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.serializeBinary = function() { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter(this, writer); + proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -1490,11 +1522,11 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.serializeBinary = f /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} message + * @param {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = function(message, writer) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.serializeBinaryToWriter = function(message, writer) { var f = undefined; f = message.getId(); if (f.length > 0) { @@ -1510,6 +1542,20 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = fun f ); } + f = message.getInstallDir(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getPackageUrl(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } }; @@ -1517,16 +1563,16 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = fun * optional string id = 1; * @return {string} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.getId = function() { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getId = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); }; /** * @param {string} value - * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} returns this + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setId = function(value) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setId = function(value) { return jspb.Message.setProto3StringField(this, 1, value); }; @@ -1535,20 +1581,56 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setId = function(va * optional string version = 2; * @return {string} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.getVersion = function() { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getVersion = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); }; /** * @param {string} value - * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} returns this + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setVersion = function(value) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setVersion = function(value) { return jspb.Message.setProto3StringField(this, 2, value); }; +/** + * optional string install_dir = 3; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getInstallDir = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this + */ +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setInstallDir = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string package_url = 4; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getPackageUrl = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this + */ +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setPackageUrl = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + @@ -1709,4 +1791,164 @@ proto.cc.arduino.cli.commands.v1.Board.prototype.setFqbn = function(value) { }; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.commands.v1.Profile.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.commands.v1.Profile} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.Profile.toObject = function(includeInstance, msg) { + var f, obj = { + name: jspb.Message.getFieldWithDefault(msg, 1, ""), + fqbn: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.commands.v1.Profile} + */ +proto.cc.arduino.cli.commands.v1.Profile.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.commands.v1.Profile; + return proto.cc.arduino.cli.commands.v1.Profile.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.commands.v1.Profile} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.commands.v1.Profile} + */ +proto.cc.arduino.cli.commands.v1.Profile.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setName(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setFqbn(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.commands.v1.Profile.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.commands.v1.Profile} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.Profile.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getName(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getFqbn(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional string name = 1; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.getName = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.Profile} returns this + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.setName = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string fqbn = 2; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.getFqbn = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.Profile} returns this + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.setFqbn = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + goog.object.extend(exports, proto.cc.arduino.cli.commands.v1); diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts index 7613a69fd..db947e130 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts @@ -86,6 +86,15 @@ export class CompileRequest extends jspb.Message { setLibraryList(value: Array): CompileRequest; addLibrary(value: string, index?: number): string; + getKeysKeychain(): string; + setKeysKeychain(value: string): CompileRequest; + + getSignKey(): string; + setSignKey(value: string): CompileRequest; + + getEncryptKey(): string; + setEncryptKey(value: string): CompileRequest; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): CompileRequest.AsObject; @@ -121,6 +130,9 @@ export namespace CompileRequest { sourceOverrideMap: Array<[string, string]>, exportBinaries?: google_protobuf_wrappers_pb.BoolValue.AsObject, libraryList: Array, + keysKeychain: string, + signKey: string, + encryptKey: string, } } @@ -151,14 +163,14 @@ export class CompileResponse extends jspb.Message { hasBoardPlatform(): boolean; clearBoardPlatform(): void; - getBoardPlatform(): cc_arduino_cli_commands_v1_common_pb.PlatformReference | undefined; - setBoardPlatform(value?: cc_arduino_cli_commands_v1_common_pb.PlatformReference): CompileResponse; + getBoardPlatform(): cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference | undefined; + setBoardPlatform(value?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference): CompileResponse; hasBuildPlatform(): boolean; clearBuildPlatform(): void; - getBuildPlatform(): cc_arduino_cli_commands_v1_common_pb.PlatformReference | undefined; - setBuildPlatform(value?: cc_arduino_cli_commands_v1_common_pb.PlatformReference): CompileResponse; + getBuildPlatform(): cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference | undefined; + setBuildPlatform(value?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference): CompileResponse; hasProgress(): boolean; @@ -184,8 +196,8 @@ export namespace CompileResponse { buildPath: string, usedLibrariesList: Array, executableSectionsSizeList: Array, - boardPlatform?: cc_arduino_cli_commands_v1_common_pb.PlatformReference.AsObject, - buildPlatform?: cc_arduino_cli_commands_v1_common_pb.PlatformReference.AsObject, + boardPlatform?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.AsObject, + buildPlatform?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.AsObject, progress?: cc_arduino_cli_commands_v1_common_pb.TaskProgress.AsObject, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js index 1d2bd8977..69aafe386 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js @@ -146,7 +146,10 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.toObject = function(includeInsta createCompilationDatabaseOnly: jspb.Message.getBooleanFieldWithDefault(msg, 21, false), sourceOverrideMap: (f = msg.getSourceOverrideMap()) ? f.toObject(includeInstance, undefined) : [], exportBinaries: (f = msg.getExportBinaries()) && google_protobuf_wrappers_pb.BoolValue.toObject(includeInstance, f), - libraryList: (f = jspb.Message.getRepeatedField(msg, 24)) == null ? undefined : f + libraryList: (f = jspb.Message.getRepeatedField(msg, 24)) == null ? undefined : f, + keysKeychain: jspb.Message.getFieldWithDefault(msg, 25, ""), + signKey: jspb.Message.getFieldWithDefault(msg, 26, ""), + encryptKey: jspb.Message.getFieldWithDefault(msg, 27, "") }; if (includeInstance) { @@ -271,6 +274,18 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.deserializeBinaryFromReader = fu var value = /** @type {string} */ (reader.readString()); msg.addLibrary(value); break; + case 25: + var value = /** @type {string} */ (reader.readString()); + msg.setKeysKeychain(value); + break; + case 26: + var value = /** @type {string} */ (reader.readString()); + msg.setSignKey(value); + break; + case 27: + var value = /** @type {string} */ (reader.readString()); + msg.setEncryptKey(value); + break; default: reader.skipField(); break; @@ -446,6 +461,27 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.serializeBinaryToWriter = functi f ); } + f = message.getKeysKeychain(); + if (f.length > 0) { + writer.writeString( + 25, + f + ); + } + f = message.getSignKey(); + if (f.length > 0) { + writer.writeString( + 26, + f + ); + } + f = message.getEncryptKey(); + if (f.length > 0) { + writer.writeString( + 27, + f + ); + } }; @@ -926,6 +962,60 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.clearLibraryList = fun }; +/** + * optional string keys_keychain = 25; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getKeysKeychain = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 25, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setKeysKeychain = function(value) { + return jspb.Message.setProto3StringField(this, 25, value); +}; + + +/** + * optional string sign_key = 26; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getSignKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 26, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setSignKey = function(value) { + return jspb.Message.setProto3StringField(this, 26, value); +}; + + +/** + * optional string encrypt_key = 27; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getEncryptKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 27, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setEncryptKey = function(value) { + return jspb.Message.setProto3StringField(this, 27, value); +}; + + /** * List of repeated fields within this message type. @@ -972,8 +1062,8 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.toObject = function(includeInst cc_arduino_cli_commands_v1_lib_pb.Library.toObject, includeInstance), executableSectionsSizeList: jspb.Message.toObjectList(msg.getExecutableSectionsSizeList(), proto.cc.arduino.cli.commands.v1.ExecutableSectionSize.toObject, includeInstance), - boardPlatform: (f = msg.getBoardPlatform()) && cc_arduino_cli_commands_v1_common_pb.PlatformReference.toObject(includeInstance, f), - buildPlatform: (f = msg.getBuildPlatform()) && cc_arduino_cli_commands_v1_common_pb.PlatformReference.toObject(includeInstance, f), + boardPlatform: (f = msg.getBoardPlatform()) && cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.toObject(includeInstance, f), + buildPlatform: (f = msg.getBuildPlatform()) && cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.toObject(includeInstance, f), progress: (f = msg.getProgress()) && cc_arduino_cli_commands_v1_common_pb.TaskProgress.toObject(includeInstance, f) }; @@ -1034,13 +1124,13 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.deserializeBinaryFromReader = f msg.addExecutableSectionsSize(value); break; case 6: - var value = new cc_arduino_cli_commands_v1_common_pb.PlatformReference; - reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.PlatformReference.deserializeBinaryFromReader); + var value = new cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference; + reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.deserializeBinaryFromReader); msg.setBoardPlatform(value); break; case 7: - var value = new cc_arduino_cli_commands_v1_common_pb.PlatformReference; - reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.PlatformReference.deserializeBinaryFromReader); + var value = new cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference; + reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.deserializeBinaryFromReader); msg.setBuildPlatform(value); break; case 8: @@ -1119,7 +1209,7 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.serializeBinaryToWriter = funct writer.writeMessage( 6, f, - cc_arduino_cli_commands_v1_common_pb.PlatformReference.serializeBinaryToWriter + cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.serializeBinaryToWriter ); } f = message.getBuildPlatform(); @@ -1127,7 +1217,7 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.serializeBinaryToWriter = funct writer.writeMessage( 7, f, - cc_arduino_cli_commands_v1_common_pb.PlatformReference.serializeBinaryToWriter + cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.serializeBinaryToWriter ); } f = message.getProgress(); @@ -1320,17 +1410,17 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.clearExecutableSectio /** - * optional PlatformReference board_platform = 6; - * @return {?proto.cc.arduino.cli.commands.v1.PlatformReference} + * optional InstalledPlatformReference board_platform = 6; + * @return {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.getBoardPlatform = function() { - return /** @type{?proto.cc.arduino.cli.commands.v1.PlatformReference} */ ( - jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.PlatformReference, 6)); + return /** @type{?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ ( + jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference, 6)); }; /** - * @param {?proto.cc.arduino.cli.commands.v1.PlatformReference|undefined} value + * @param {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference|undefined} value * @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this */ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.setBoardPlatform = function(value) { @@ -1357,17 +1447,17 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.hasBoardPlatform = fu /** - * optional PlatformReference build_platform = 7; - * @return {?proto.cc.arduino.cli.commands.v1.PlatformReference} + * optional InstalledPlatformReference build_platform = 7; + * @return {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.getBuildPlatform = function() { - return /** @type{?proto.cc.arduino.cli.commands.v1.PlatformReference} */ ( - jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.PlatformReference, 7)); + return /** @type{?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ ( + jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference, 7)); }; /** - * @param {?proto.cc.arduino.cli.commands.v1.PlatformReference|undefined} value + * @param {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference|undefined} value * @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this */ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.setBuildPlatform = function(value) { diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts index f19526ab3..4d65f4723 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts @@ -93,6 +93,9 @@ export class MonitorResponse extends jspb.Message { setAppliedSettingsList(value: Array): MonitorResponse; addAppliedSettings(value?: MonitorPortSetting, index?: number): MonitorPortSetting; + getSuccess(): boolean; + setSuccess(value: boolean): MonitorResponse; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): MonitorResponse.AsObject; @@ -109,6 +112,7 @@ export namespace MonitorResponse { error: string, rxData: Uint8Array | string, appliedSettingsList: Array, + success: boolean, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js index 8583f3388..0a2889c04 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js @@ -712,7 +712,8 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.toObject = function(includeInst error: jspb.Message.getFieldWithDefault(msg, 1, ""), rxData: msg.getRxData_asB64(), appliedSettingsList: jspb.Message.toObjectList(msg.getAppliedSettingsList(), - proto.cc.arduino.cli.commands.v1.MonitorPortSetting.toObject, includeInstance) + proto.cc.arduino.cli.commands.v1.MonitorPortSetting.toObject, includeInstance), + success: jspb.Message.getBooleanFieldWithDefault(msg, 4, false) }; if (includeInstance) { @@ -762,6 +763,10 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.deserializeBinaryFromReader = f reader.readMessage(value,proto.cc.arduino.cli.commands.v1.MonitorPortSetting.deserializeBinaryFromReader); msg.addAppliedSettings(value); break; + case 4: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setSuccess(value); + break; default: reader.skipField(); break; @@ -813,6 +818,13 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.serializeBinaryToWriter = funct proto.cc.arduino.cli.commands.v1.MonitorPortSetting.serializeBinaryToWriter ); } + f = message.getSuccess(); + if (f) { + writer.writeBool( + 4, + f + ); + } }; @@ -914,6 +926,24 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.prototype.clearAppliedSettingsL }; +/** + * optional bool success = 4; + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.MonitorResponse.prototype.getSuccess = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.cc.arduino.cli.commands.v1.MonitorResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.MonitorResponse.prototype.setSuccess = function(value) { + return jspb.Message.setProto3BooleanField(this, 4, value); +}; + + diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts index 1c1ca38ca..4427473a6 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts @@ -371,9 +371,6 @@ export class SupportedUserFieldsRequest extends jspb.Message { getProtocol(): string; setProtocol(value: string): SupportedUserFieldsRequest; - getAddress(): string; - setAddress(value: string): SupportedUserFieldsRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SupportedUserFieldsRequest.AsObject; @@ -390,7 +387,6 @@ export namespace SupportedUserFieldsRequest { instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, fqbn: string, protocol: string, - address: string, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js index 87914c167..8841575ad 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js @@ -2718,8 +2718,7 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.toObject = function( var f, obj = { instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), fqbn: jspb.Message.getFieldWithDefault(msg, 2, ""), - protocol: jspb.Message.getFieldWithDefault(msg, 3, ""), - address: jspb.Message.getFieldWithDefault(msg, 4, "") + protocol: jspb.Message.getFieldWithDefault(msg, 3, "") }; if (includeInstance) { @@ -2769,10 +2768,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.deserializeBinaryFro var value = /** @type {string} */ (reader.readString()); msg.setProtocol(value); break; - case 4: - var value = /** @type {string} */ (reader.readString()); - msg.setAddress(value); - break; default: reader.skipField(); break; @@ -2824,13 +2819,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.serializeBinaryToWri f ); } - f = message.getAddress(); - if (f.length > 0) { - writer.writeString( - 4, - f - ); - } }; @@ -2907,24 +2895,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setProtoco }; -/** - * optional string address = 4; - * @return {string} - */ -proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.getAddress = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); -}; - - -/** - * @param {string} value - * @return {!proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest} returns this - */ -proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setAddress = function(value) { - return jspb.Message.setProto3StringField(this, 4, value); -}; - - From 900a5f9be868591913cf205e598b74291e16191b Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 3 Jun 2022 11:12:19 +0200 Subject: [PATCH 52/66] remove useless plotter protocol file --- .../plotter/plotter-frontend-contribution.ts | 3 +-- .../src/browser/serial/plotter/protocol.ts | 26 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 arduino-ide-extension/src/browser/serial/plotter/protocol.ts diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index 448c10e6a..a711280a9 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -11,7 +11,6 @@ import { Contribution } from '../../contributions/contribution'; import { Endpoint, FrontendApplication } from '@theia/core/lib/browser'; import { ipcRenderer } from '@theia/electron/shared/electron'; import { MonitorManagerProxyClient } from '../../../common/protocol'; -import { SerialPlotter } from './protocol'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { MonitorModel } from '../../monitor-model'; @@ -95,7 +94,7 @@ export class PlotterFrontendContribution extends Contribution { } protected async open(wsPort: number): Promise { - const initConfig: Partial = { + const initConfig = { darkTheme: this.themeService.getCurrentTheme().type === 'dark', wsPort, serialPort: this.model.serialPort, diff --git a/arduino-ide-extension/src/browser/serial/plotter/protocol.ts b/arduino-ide-extension/src/browser/serial/plotter/protocol.ts deleted file mode 100644 index c38c9fcb9..000000000 --- a/arduino-ide-extension/src/browser/serial/plotter/protocol.ts +++ /dev/null @@ -1,26 +0,0 @@ -export namespace SerialPlotter { - export type Config = { - currentBaudrate: number; - baudrates: number[]; - currentLineEnding: string; - darkTheme: boolean; - wsPort: number; - interpolate: boolean; - serialPort: string; - connected: boolean; - generate?: boolean; - }; - export namespace Protocol { - export enum Command { - PLOTTER_SET_BAUDRATE = 'PLOTTER_SET_BAUDRATE', - PLOTTER_SET_LINE_ENDING = 'PLOTTER_SET_LINE_ENDING', - PLOTTER_SET_INTERPOLATE = 'PLOTTER_SET_INTERPOLATE', - PLOTTER_SEND_MESSAGE = 'PLOTTER_SEND_MESSAGE', - MIDDLEWARE_CONFIG_CHANGED = 'MIDDLEWARE_CONFIG_CHANGED', - } - export type Message = { - command: SerialPlotter.Protocol.Command; - data?: any; - }; - } -} From dac12aa75bf6a74b8bbe61547572e3a54993a5a1 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 3 Jun 2022 11:41:12 +0200 Subject: [PATCH 53/66] localize web socket errors --- .../browser/monitor-manager-proxy-client-impl.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index f72b0974c..200507d74 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -3,6 +3,7 @@ import { Disposable, Emitter, MessageService, + nls, } from '@theia/core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { Board, Port } from '../common/protocol'; @@ -77,7 +78,12 @@ export class MonitorManagerProxyClientImpl try { this.webSocket = new WebSocket(`ws://localhost:${addressPort}`); } catch { - this.messageService.error('Unable to connect to websocket'); + this.messageService.error( + nls.localize( + 'arduino/monitor/unableToConnectToWebSocket', + 'Unable to connect to websocket' + ) + ); return; } @@ -106,7 +112,12 @@ export class MonitorManagerProxyClientImpl this.webSocket?.close(); this.webSocket = undefined; } catch { - this.messageService.error('Unable to close websocket'); + this.messageService.error( + nls.localize( + 'arduino/monitor/unableToConnectToWebSocket', + 'Unable to close websocket' + ) + ); } } From 038114ff364d70b08df9597992da0bfa96193079 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 3 Jun 2022 11:41:32 +0200 Subject: [PATCH 54/66] clean-up code --- .../monitor-settings/monitor-settings-provider-impl.ts | 8 +++++--- .../src/test/node/monitor-settings-utils.test.ts | 2 -- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts index ef8050691..cde81cfd6 100644 --- a/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts +++ b/arduino-ide-extension/src/node/monitor-settings/monitor-settings-provider-impl.ts @@ -13,6 +13,7 @@ import { longestPrefixMatch, reconcileSettings, } from './monitor-settings-utils'; +import { ILogger } from '@theia/core'; const MONITOR_SETTINGS_FILE = 'pluggable-monitor-settings.json'; @@ -21,6 +22,9 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { @inject(EnvVariablesServer) protected readonly envVariablesServer: EnvVariablesServer; + @inject(ILogger) + protected logger: ILogger; + // deferred used to guarantee file operations are performed after the service is initialized protected ready = new Deferred(); @@ -43,8 +47,6 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { // read existing settings await this.readSettingsFromFS(); - console.log(this.monitorSettings); - // init is done, resolve the deferred and unblock any call that was waiting for it this.ready.resolve(); } @@ -105,7 +107,7 @@ export class MonitorSettingsProviderImpl implements MonitorSettingsProvider { try { this.monitorSettings = JSON.parse(rawJson); } catch (error) { - console.error( + this.logger.error( 'Could not parse the pluggable monitor settings file. Using empty file.' ); this.monitorSettings = {}; diff --git a/arduino-ide-extension/src/test/node/monitor-settings-utils.test.ts b/arduino-ide-extension/src/test/node/monitor-settings-utils.test.ts index f3a8c56b4..dcaf1cbca 100644 --- a/arduino-ide-extension/src/test/node/monitor-settings-utils.test.ts +++ b/arduino-ide-extension/src/test/node/monitor-settings-utils.test.ts @@ -7,8 +7,6 @@ import { PluggableMonitorSettings } from '../../node/monitor-settings/monitor-se type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; -// use(require('chai-string')); - describe('longestPrefixMatch', () => { const settings = { 'arduino:avr:uno-port1-protocol1': { From 750c534655cb90f9de2f3ad8cbd24316a9352f3e Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 3 Jun 2022 11:53:31 +0200 Subject: [PATCH 55/66] update translation file --- i18n/en.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index ccda42fcf..7b5231d33 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -266,15 +266,11 @@ "serial": { "autoscroll": "Autoscroll", "carriageReturn": "Carriage Return", - "connectionBusy": "Connection failed. Serial port is busy: {0}", - "disconnected": "Disconnected {0} from {1}.", - "failedReconnect": "Failed to reconnect {0} to serial port after 10 consecutive attempts. The {1} serial port is busy.", "message": "Message ({0} + Enter to send message to '{1}' on '{2}')", "newLine": "New Line", "newLineCarriageReturn": "Both NL & CR", "noLineEndings": "No Line Ending", "notConnected": "Not connected. Select a board and a port to connect automatically.", - "reconnect": "Reconnecting {0} to {1} in {2} seconds...", "timestamp": "Timestamp", "toggleTimestamp": "Toggle Timestamp", "unexpectedError": "Unexpected error. Reconnecting {0} on port {1}." @@ -284,7 +280,6 @@ "cantOpen": "A folder named \"{0}\" already exists. Can't open sketch.", "close": "Are you sure you want to close the sketch?", "configureAndUpload": "Configure And Upload", - "couldNotConnectToSerial": "Could not reconnect to serial port. {0}", "createdArchive": "Created archive '{0}'.", "doneCompiling": "Done compiling.", "doneUploading": "Done uploading.", From 3f472f0b9e93d610dcc3700ecbf7578edad8b0f9 Mon Sep 17 00:00:00 2001 From: Mark Sujew Date: Tue, 31 May 2022 11:33:07 +0200 Subject: [PATCH 56/66] Fix duplicated editor tabs (#1012) --- .../src/browser/arduino-frontend-contribution.tsx | 6 +++++- .../src/browser/arduino-ide-frontend-module.ts | 4 ++++ .../src/browser/theia/editor/editor-manager.ts | 9 +++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 arduino-ide-extension/src/browser/theia/editor/editor-manager.ts diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index a62094efc..1a227d1b2 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -579,7 +579,11 @@ export class ArduinoFrontendContribution (widget) => widget.editor.uri.toString() === uri ); if (!widget || forceOpen) { - return this.editorManager.open(new URI(uri), options); + return this.editorManager.open(new URI(uri), options ?? { + mode: 'reveal', + preview: false, + counter: 0 + }); } } diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index a6f20c1bf..9b4f49052 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -276,6 +276,8 @@ import { import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider'; import { MonitorModel } from './monitor-model'; import { MonitorManagerProxyClientImpl } from './monitor-manager-proxy-client-impl'; +import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser/editor-manager'; +import { EditorManager } from './theia/editor/editor-manager'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -525,6 +527,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(SearchInWorkspaceWidget).toSelf(); rebind(TheiaSearchInWorkspaceWidget).toService(SearchInWorkspaceWidget); + rebind(TheiaEditorManager).to(EditorManager); + // replace search icon rebind(TheiaSearchInWorkspaceFactory) .to(SearchInWorkspaceFactory) diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts b/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts new file mode 100644 index 000000000..6c4f7aa8c --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts @@ -0,0 +1,9 @@ +import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser/editor-manager'; + +export class EditorManager extends TheiaEditorManager { + + protected getOrCreateCounterForUri(): number { + return 0; + } + +} From bee7830682dac39a9f7ab4fa1c881c1e4235c4ea Mon Sep 17 00:00:00 2001 From: Mark Sujew Date: Wed, 1 Jun 2022 10:55:08 +0200 Subject: [PATCH 57/66] Save dialog for closing temporary sketch and unsaved files (#893) * Use normal `OnWillStop` event * Align `CLOSE` command to rest of app * Fixed FS path vs encoded URL comparision when handling stop request. Ref: https://github.com/eclipse-theia/theia/issues/11226 Signed-off-by: Akos Kitta * Fixed the translations. Signed-off-by: Akos Kitta * Fixed the translations again. Removed `electron` from the `nls-extract`. It does not contain app code. Signed-off-by: Akos Kitta * Aligned the stop handler code to Theia. Signed-off-by: Akos Kitta Co-authored-by: Akos Kitta --- .../browser/arduino-frontend-contribution.tsx | 49 +++++++++++++ .../src/browser/contributions/close.ts | 73 +------------------ .../theia/core/electron-menu-module.ts | 23 ++++++ .../arduino-electron-main-module.ts | 5 ++ .../theia/theia-electron-window.ts | 36 +++++++++ i18n/en.json | 7 +- package.json | 2 +- 7 files changed, 119 insertions(+), 76 deletions(-) create mode 100644 arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 1a227d1b2..de7f968dd 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -17,9 +17,11 @@ import { DisposableCollection, } from '@theia/core'; import { + Dialog, FrontendApplication, FrontendApplicationContribution, LocalStorageService, + OnWillStopAction, SaveableWidget, StatusBar, StatusBarAlignment, @@ -667,4 +669,51 @@ export class ArduinoFrontendContribution } ); } + + onWillStop(): OnWillStopAction { + return { + reason: 'temp-sketch', + action: () => { + return this.showTempSketchDialog(); + } + } + } + + private async showTempSketchDialog(): Promise { + const sketch = await this.sketchServiceClient.currentSketch(); + if (!sketch) { + return true; + } + const isTemp = await this.sketchService.isTemp(sketch); + if (!isTemp) { + return true; + } + const messageBoxResult = await remote.dialog.showMessageBox( + remote.getCurrentWindow(), + { + message: nls.localize('arduino/sketch/saveTempSketch', 'Save your sketch to open it again later.'), + title: nls.localize('theia/core/quitTitle', 'Are you sure you want to quit?'), + type: 'question', + buttons: [ + Dialog.CANCEL, + nls.localizeByDefault('Save As...'), + nls.localizeByDefault("Don't Save"), + ], + } + ) + const result = messageBoxResult.response; + if (result === 2) { + return true; + } else if (result === 1) { + return !!(await this.commandRegistry.executeCommand( + SaveAsSketch.Commands.SAVE_AS_SKETCH.id, + { + execOnlyIfTemp: false, + openAfterMove: false, + wipeOriginal: true + } + )); + } + return false + } } diff --git a/arduino-ide-extension/src/browser/contributions/close.ts b/arduino-ide-extension/src/browser/contributions/close.ts index f27b832cf..f801b5b47 100644 --- a/arduino-ide-extension/src/browser/contributions/close.ts +++ b/arduino-ide-extension/src/browser/contributions/close.ts @@ -1,12 +1,10 @@ import { inject, injectable } from '@theia/core/shared/inversify'; -import { toArray } from '@theia/core/shared/@phosphor/algorithm'; import * as remote from '@theia/core/electron-shared/@electron/remote'; import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor'; import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell'; import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; import { ArduinoMenus } from '../menu/arduino-menus'; -import { SaveAsSketch } from './save-as-sketch'; import { SketchContribution, Command, @@ -33,76 +31,7 @@ export class Close extends SketchContribution { registerCommands(registry: CommandRegistry): void { registry.registerCommand(Close.Commands.CLOSE, { - execute: async () => { - // Close current editor if closeable. - const { currentEditor } = this.editorManager; - if (currentEditor && currentEditor.title.closable) { - currentEditor.close(); - return; - } - - // Close current widget from the main area if possible. - const { currentWidget } = this.shell; - if (currentWidget) { - const currentWidgetInMain = toArray( - this.shell.mainPanel.widgets() - ).find((widget) => widget === currentWidget); - if (currentWidgetInMain && currentWidgetInMain.title.closable) { - return currentWidgetInMain.close(); - } - } - - // Close the sketch (window). - const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { - return; - } - const isTemp = await this.sketchService.isTemp(sketch); - const uri = await this.sketchServiceClient.currentSketchFile(); - if (!uri) { - return; - } - if (isTemp && (await this.wasTouched(uri))) { - const { response } = await remote.dialog.showMessageBox({ - type: 'question', - buttons: [ - nls.localize( - 'vscode/abstractTaskService/saveBeforeRun.dontSave', - "Don't Save" - ), - nls.localize('vscode/issueMainService/cancel', 'Cancel'), - nls.localize( - 'vscode/abstractTaskService/saveBeforeRun.save', - 'Save' - ), - ], - message: nls.localize( - 'arduino/common/saveChangesToSketch', - 'Do you want to save changes to this sketch before closing?' - ), - detail: nls.localize( - 'arduino/common/loseChanges', - "If you don't save, your changes will be lost." - ), - }); - if (response === 1) { - // Cancel - return; - } - if (response === 2) { - // Save - const saved = await this.commandService.executeCommand( - SaveAsSketch.Commands.SAVE_AS_SKETCH.id, - { openAfterMove: false, execOnlyIfTemp: true } - ); - if (!saved) { - // If it was not saved, do bail the close. - return; - } - } - } - window.close(); - }, + execute: () => remote.getCurrentWindow().close() }); } diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts index 1e3b86e60..fd6a27aa5 100644 --- a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts @@ -11,6 +11,29 @@ import { MainMenuManager } from '../../../common/main-menu-manager'; import { ElectronWindowService } from '../../electron-window-service'; import { ElectronMainMenuFactory } from './electron-main-menu-factory'; import { ElectronMenuContribution } from './electron-menu-contribution'; +import { nls } from '@theia/core/lib/common/nls'; + +import * as remote from '@theia/core/electron-shared/@electron/remote'; +import * as dialogs from '@theia/core/lib/browser/dialogs'; + + +Object.assign(dialogs, { + confirmExit: async () => { + const messageBoxResult = await remote.dialog.showMessageBox( + remote.getCurrentWindow(), + { + message: nls.localize('theia/core/quitMessage', 'Any unsaved changes will not be saved.'), + title: nls.localize('theia/core/quitTitle', 'Are you sure you want to quit?'), + type: 'question', + buttons: [ + dialogs.Dialog.CANCEL, + dialogs.Dialog.YES, + ], + } + ) + return messageBoxResult.response === 1; + } +}); export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ElectronMenuContribution).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts b/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts index 6ba05e283..7df38d649 100644 --- a/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts +++ b/arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts @@ -19,6 +19,8 @@ import { IDEUpdaterPath, } from '../common/protocol/ide-updater'; import { IDEUpdaterImpl } from './ide-updater/ide-updater-impl'; +import { TheiaElectronWindow } from './theia/theia-electron-window'; +import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ElectronMainApplication).toSelf().inSingletonScope(); @@ -56,4 +58,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { ) ) .inSingletonScope(); + + bind(TheiaElectronWindow).toSelf(); + rebind(DefaultTheiaElectronWindow).toService(TheiaElectronWindow); }); diff --git a/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts b/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts new file mode 100644 index 000000000..073ba5f48 --- /dev/null +++ b/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts @@ -0,0 +1,36 @@ +import { injectable } from '@theia/core/shared/inversify'; +import { StopReason } from '@theia/core/lib/electron-common/messaging/electron-messages'; +import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window'; +import { FileUri } from '@theia/core/lib/node'; +import URI from '@theia/core/lib/common/uri'; + +@injectable() +export class TheiaElectronWindow extends DefaultTheiaElectronWindow { + protected async handleStopRequest( + onSafeCallback: () => unknown, + reason: StopReason + ): Promise { + // Only confirm close to windows that have loaded our frontend. + // Both the windows's URL and the FS path of the `index.html` should be converted to the "same" format to be able to compare them. (#11226) + // Notes: + // - Windows: file:///C:/path/to/somewhere vs file:///c%3A/path/to/somewhere + // - macOS: file:///Applications/App%20Name.app/Contents vs /Applications/App Name.app/Contents + // This URL string comes from electron, we can expect that this is properly encoded URL. For example, a space is `%20` + const currentUrl = new URI(this.window.webContents.getURL()).toString(); + // THEIA_FRONTEND_HTML_PATH is an FS path, we have to covert to an encoded URI string. + const frontendUri = FileUri.create( + this.globals.THEIA_FRONTEND_HTML_PATH + ).toString(); + const safeToClose = + !currentUrl.includes(frontendUri) || (await this.checkSafeToStop(reason)); + if (safeToClose) { + try { + await onSafeCallback(); + return true; + } catch (e) { + console.warn(`Request ${StopReason[reason]} failed.`, e); + } + } + return false; + } +} diff --git a/i18n/en.json b/i18n/en.json index 7b5231d33..0cd31b668 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -93,13 +93,11 @@ }, "common": { "later": "Later", - "loseChanges": "If you don't save, your changes will be lost.", "noBoardSelected": "No board selected", "notConnected": "[not connected]", "offlineIndicator": "You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.", "oldFormat": "The '{0}' still uses the old `.pde` format. Do you want to switch to the new `.ino` extension?", "processing": "Processing", - "saveChangesToSketch": "Do you want to save changes to this sketch before closing?", "selectBoard": "Select Board", "selectedOn": "on {0}", "serialMonitor": "Serial Monitor", @@ -292,6 +290,7 @@ "openSketchInNewWindow": "Open Sketch in New Window", "saveFolderAs": "Save sketch folder as...", "saveSketchAs": "Save sketch folder as...", + "saveTempSketch": "Save your sketch to open it again later.", "showFolder": "Show Sketch Folder", "sketch": "Sketch", "sketchbook": "Sketchbook", @@ -320,7 +319,9 @@ "cannotConnectDaemon": "Cannot connect to the CLI daemon.", "couldNotSave": "Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.", "daemonOffline": "CLI Daemon Offline", - "offline": "Offline" + "offline": "Offline", + "quitMessage": "Any unsaved changes will not be saved.", + "quitTitle": "Are you sure you want to quit?" }, "debug": { "start": "Start...", diff --git a/package.json b/package.json index 5ad471310..71534a9c9 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "test": "lerna run test", "download:plugins": "theia download:plugins", "update:version": "node ./scripts/update-version.js", - "i18n:generate": "theia nls-extract -e vscode -f \"+(arduino-ide-extension|browser-app|electron|electron-app|plugins)/**/*.ts?(x)\" -o ./i18n/en.json", + "i18n:generate": "theia nls-extract -e vscode -f \"+(arduino-ide-extension|browser-app|electron-app|plugins)/**/*.ts?(x)\" -o ./i18n/en.json", "i18n:check": "yarn i18n:generate && git add -N ./i18n && git diff --exit-code ./i18n", "i18n:push": "node ./scripts/i18n/transifex-push.js ./i18n/en.json", "i18n:pull": "node ./scripts/i18n/transifex-pull.js ./i18n/", From f019664bac65e29668ec0da215457380d0378ee2 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 3 Jun 2022 12:43:09 +0200 Subject: [PATCH 58/66] fix serial monitor send line ending --- .../src/browser/serial/monitor/serial-monitor-send-input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx index f13176f74..ed5b7826c 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx @@ -102,7 +102,7 @@ export class SerialMonitorSendInput extends React.Component< } protected onSend(): void { - this.props.onSend(this.state.text); + this.props.onSend(this.state.text + this.props.monitorModel.lineEnding); this.setState({ text: '' }); } From 761efd05cd90f3a0385b1dcce79cdda851515396 Mon Sep 17 00:00:00 2001 From: David Simpson <45690499+davegarthsimpson@users.noreply.github.com> Date: Fri, 3 Jun 2022 12:57:33 +0200 Subject: [PATCH 59/66] refactor monitor-service poll for test/readability --- .../src/node/monitor-service.ts | 236 +++++++++++------- 1 file changed, 149 insertions(+), 87 deletions(-) diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index 6a0a1f314..df2b3c8b5 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -64,6 +64,9 @@ export class MonitorService extends CoreClientAware implements Disposable { protected _initialized = new Deferred(); protected creating: Deferred; + MAX_WRITE_TO_STREAM_TRIES = 10; + WRITE_TO_STREAM_TIMEOUT_MS = 30000; + constructor( @inject(ILogger) @named(MonitorServiceName) @@ -134,15 +137,6 @@ export class MonitorService extends CoreClientAware implements Disposable { return !!this.duplex; } - setDuplexHandlers( - duplex: ClientDuplexStream, - handlers: DuplexHandler[] - ): void { - for (const handler of handlers) { - duplex.on(handler.key, handler.callback); - } - } - /** * Start and connects a monitor using currently set board and port. * If a monitor is already started or board fqbn, port address and/or protocol @@ -199,16 +193,16 @@ export class MonitorService extends CoreClientAware implements Disposable { const coreClient = await this.coreClient(); const { instance } = coreClient; - const req = new MonitorRequest(); - req.setInstance(instance); + const monitorRequest = new MonitorRequest(); + monitorRequest.setInstance(instance); if (this.board?.fqbn) { - req.setFqbn(this.board.fqbn); + monitorRequest.setFqbn(this.board.fqbn); } if (this.port?.address && this.port?.protocol) { const port = new gRPCPort(); port.setAddress(this.port.address); port.setProtocol(this.port.protocol); - req.setPort(port); + monitorRequest.setPort(port); } const config = new MonitorPortConfiguration(); for (const id in this.settings.pluggableMonitorSettings) { @@ -217,81 +211,11 @@ export class MonitorService extends CoreClientAware implements Disposable { s.setValue(this.settings.pluggableMonitorSettings[id].selectedValue); config.addSettings(s); } - req.setPortConfiguration(config); - - // Promise executor - const writeToStream = (resolve: (value: boolean) => void) => { - this.duplex = coreClient.client.monitor(); - - const duplexHandlers: DuplexHandler[] = [ - { - key: 'close', - callback: () => { - this.duplex = null; - this.updateClientsSettings({ - monitorUISettings: { connected: false }, - }); - this.logger.info( - `monitor to ${this.port?.address} using ${this.port?.protocol} closed by client` - ); - }, - }, - { - key: 'end', - callback: () => { - this.duplex = null; - this.updateClientsSettings({ - monitorUISettings: { connected: false }, - }); - this.logger.info( - `monitor to ${this.port?.address} using ${this.port?.protocol} closed by server` - ); - }, - }, - { - key: 'error', - callback: (err: Error) => { - this.logger.error(err); - resolve(false); - // TODO - // this.theiaFEClient?.notifyError() - }, - }, - { - key: 'data', - callback: (res: MonitorResponse) => { - if (res.getError()) { - // TODO: Maybe disconnect - this.logger.error(res.getError()); - return; - } - if (res.getSuccess()) { - resolve(true); - return; - } - const data = res.getRxData(); - const message = - typeof data === 'string' - ? data - : new TextDecoder('utf8').decode(data); - this.messages.push(...splitLines(message)); - }, - }, - ]; - - this.setDuplexHandlers(this.duplex, duplexHandlers); - this.duplex.write(req); - }; - - let attemptsRemaining = 10; - let wroteToStreamSuccessfully = false; - while (attemptsRemaining > 0) { - wroteToStreamSuccessfully = await new Promise(writeToStream); - if (wroteToStreamSuccessfully) break; - attemptsRemaining -= 1; - await new Promise((r) => setTimeout(r, 2000)); - } + monitorRequest.setPortConfiguration(config); + const wroteToStreamSuccessfully = await this.pollWriteToStream( + monitorRequest + ); if (wroteToStreamSuccessfully) { this.startMessagesHandlers(); this.logger.info( @@ -311,6 +235,144 @@ export class MonitorService extends CoreClientAware implements Disposable { } } + async createDuplex(): Promise< + ClientDuplexStream + > { + const coreClient = await this.coreClient(); + return coreClient.client.monitor(); + } + + setDuplexHandlers( + duplex: ClientDuplexStream, + additionalHandlers: DuplexHandler[] + ): void { + // default handlers + duplex + .on('close', () => { + this.duplex = null; + this.updateClientsSettings({ + monitorUISettings: { connected: false }, + }); + this.logger.info( + `monitor to ${this.port?.address} using ${this.port?.protocol} closed by client` + ); + }) + .on('end', () => { + this.duplex = null; + this.updateClientsSettings({ + monitorUISettings: { connected: false }, + }); + this.logger.info( + `monitor to ${this.port?.address} using ${this.port?.protocol} closed by server` + ); + }); + + for (const handler of additionalHandlers) { + duplex.on(handler.key, handler.callback); + } + } + + pollWriteToStream(request: MonitorRequest): Promise { + let attemptsRemaining = this.MAX_WRITE_TO_STREAM_TRIES; + const writeTimeoutMs = this.WRITE_TO_STREAM_TIMEOUT_MS; + + const createWriteToStreamExecutor = + (duplex: ClientDuplexStream) => + (resolve: (value: boolean) => void, reject: () => void) => { + const resolvingDuplexHandlers: DuplexHandler[] = [ + { + key: 'error', + callback: async (err: Error) => { + this.logger.error(err); + resolve(false); + // TODO + // this.theiaFEClient?.notifyError() + }, + }, + { + key: 'data', + callback: async (monitorResponse: MonitorResponse) => { + if (monitorResponse.getError()) { + // TODO: Maybe disconnect + this.logger.error(monitorResponse.getError()); + return; + } + if (monitorResponse.getSuccess()) { + resolve(true); + return; + } + const data = monitorResponse.getRxData(); + const message = + typeof data === 'string' + ? data + : new TextDecoder('utf8').decode(data); + this.messages.push(...splitLines(message)); + }, + }, + ]; + + this.setDuplexHandlers(duplex, resolvingDuplexHandlers); + + setTimeout(() => { + reject(); + }, writeTimeoutMs); + duplex.write(request); + }; + + const pollWriteToStream = new Promise((resolve) => { + const startPolling = async () => { + // here we create a new duplex but we don't yet + // set "this.duplex", nor do we use "this.duplex" in our poll + // as duplex 'end' / 'close' events (which we do not "await") + // will set "this.duplex" to null + const createdDuplex = await this.createDuplex(); + + let pollingIsSuccessful; + // attempt a "writeToStream" and "await" CLI response: success (true) or error (false) + // if we get neither within WRITE_TO_STREAM_TIMEOUT_MS or an error we get undefined + try { + const writeToStream = createWriteToStreamExecutor(createdDuplex); + pollingIsSuccessful = await new Promise(writeToStream); + } catch (error) { + this.logger.error(error); + } + + // CLI confirmed port opened successfully + if (pollingIsSuccessful) { + this.duplex = createdDuplex; + resolve(true); + return; + } + + // if "pollingIsSuccessful" is false + // the CLI gave us an error, lets try again + // after waiting 2 seconds if we've not already + // reached MAX_WRITE_TO_STREAM_TRIES + if (pollingIsSuccessful === false) { + attemptsRemaining -= 1; + if (attemptsRemaining > 0) { + setTimeout(startPolling, 2000); + return; + } else { + resolve(false); + return; + } + } + + // "pollingIsSuccessful" remains undefined: + // we got no response from the CLI within 30 seconds + // resolve to false and end the duplex connection + resolve(false); + createdDuplex.end(); + return; + }; + + startPolling(); + }); + + return pollWriteToStream; + } + /** * Pauses the currently running monitor, it still closes the gRPC connection * with the underlying monitor process but it doesn't stop the message handlers From ee416858981cf7c540000dd21b913bcc9144ff55 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 3 Jun 2022 11:41:12 +0200 Subject: [PATCH 60/66] localize web socket errors --- .../src/browser/monitor-manager-proxy-client-impl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 200507d74..5519057aa 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -114,7 +114,7 @@ export class MonitorManagerProxyClientImpl } catch { this.messageService.error( nls.localize( - 'arduino/monitor/unableToConnectToWebSocket', + 'arduino/monitor/unableToCloseWebSocket', 'Unable to close websocket' ) ); From efdc86b80bbe7c88daf075f06dbea89223812e6e Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 3 Jun 2022 11:53:31 +0200 Subject: [PATCH 61/66] update translation file --- i18n/en.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i18n/en.json b/i18n/en.json index 0cd31b668..b8365d681 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -215,6 +215,10 @@ "sketch": "Sketch", "tools": "Tools" }, + "monitor": { + "unableToCloseWebSocket": "Unable to close websocket", + "unableToConnectToWebSocket": "Unable to close websocket" + }, "preferences": { "additionalManagerURLs": "Additional Boards Manager URLs", "auth.audience": "The OAuth2 audience.", From 980673ad3a4fa569a05298a5f4a7039f1f247493 Mon Sep 17 00:00:00 2001 From: Mark Sujew Date: Tue, 31 May 2022 11:33:07 +0200 Subject: [PATCH 62/66] Fix duplicated editor tabs (#1012) --- .../src/browser/arduino-ide-frontend-module.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 9b4f49052..f00fa55af 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -529,6 +529,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TheiaEditorManager).to(EditorManager); + rebind(TheiaEditorManager).to(EditorManager); + // replace search icon rebind(TheiaSearchInWorkspaceFactory) .to(SearchInWorkspaceFactory) From 7b15a445aaedcdcd93f8a7e34f573650d6587fc7 Mon Sep 17 00:00:00 2001 From: David Simpson <45690499+davegarthsimpson@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:56:30 +0200 Subject: [PATCH 63/66] i18n:check rerun --- i18n/en.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index b8365d681..d7f0b6e98 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -217,7 +217,7 @@ }, "monitor": { "unableToCloseWebSocket": "Unable to close websocket", - "unableToConnectToWebSocket": "Unable to close websocket" + "unableToConnectToWebSocket": "Unable to connect to websocket" }, "preferences": { "additionalManagerURLs": "Additional Boards Manager URLs", @@ -274,8 +274,7 @@ "noLineEndings": "No Line Ending", "notConnected": "Not connected. Select a board and a port to connect automatically.", "timestamp": "Timestamp", - "toggleTimestamp": "Toggle Timestamp", - "unexpectedError": "Unexpected error. Reconnecting {0} on port {1}." + "toggleTimestamp": "Toggle Timestamp" }, "sketch": { "archiveSketch": "Archive Sketch", From 5b60cb480e097f6e3bc23c13d19158b801a2aab6 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Fri, 20 May 2022 12:11:23 +0200 Subject: [PATCH 64/66] Speed up IDE startup time. Signed-off-by: Akos Kitta --- .gitignore | 2 + .prettierrc | 3 +- .vscode/launch.json | 40 +- arduino-ide-extension/package.json | 16 +- .../scripts/download-examples.js | 105 ++++-- .../browser/arduino-frontend-contribution.tsx | 182 ++++----- .../browser/arduino-ide-frontend-module.ts | 42 ++- .../boards/boards-config-dialog-widget.tsx | 3 +- .../browser/boards/boards-config-dialog.ts | 14 +- .../src/browser/boards/boards-config.tsx | 28 +- .../boards/boards-data-menu-updater.ts | 12 +- .../src/browser/boards/boards-list-widget.ts | 6 +- .../browser/boards/boards-toolbar-item.tsx | 8 +- .../boards-widget-frontend-contribution.ts | 2 +- .../src/browser/contributions/about.ts | 4 +- .../src/browser/contributions/add-file.ts | 7 +- .../browser/contributions/add-zip-library.ts | 4 +- .../browser/contributions/archive-sketch.ts | 7 +- .../browser/contributions/board-selection.ts | 25 +- .../browser/contributions/burn-bootloader.ts | 6 +- .../src/browser/contributions/close.ts | 10 +- .../src/browser/contributions/contribution.ts | 33 +- .../src/browser/contributions/debug.ts | 103 ++--- .../contributions/edit-contributions.ts | 6 +- .../src/browser/contributions/examples.ts | 25 +- .../src/browser/contributions/help.ts | 6 +- .../browser/contributions/include-library.ts | 16 +- .../src/browser/contributions/new-sketch.ts | 8 +- .../contributions/open-recent-sketch.ts | 22 +- .../contributions/open-sketch-external.ts | 6 +- .../src/browser/contributions/open-sketch.ts | 8 +- .../src/browser/contributions/quit-app.ts | 6 +- .../browser/contributions/save-as-sketch.ts | 11 +- .../src/browser/contributions/save-sketch.ts | 11 +- .../src/browser/contributions/settings.ts | 6 +- .../browser/contributions/sketch-control.ts | 44 +-- .../src/browser/contributions/sketchbook.ts | 21 +- .../contributions/upload-certificate.ts | 4 +- .../browser/contributions/upload-firmware.ts | 4 +- .../browser/contributions/upload-sketch.ts | 17 +- .../browser/contributions/verify-sketch.ts | 11 +- .../src/browser/create/create-api.ts | 4 +- .../certificate-uploader-dialog.tsx | 12 +- .../dialogs/cloud-share-sketch-dialog.tsx | 8 +- .../dialogs/do-not-ask-again-dialog.ts | 6 +- .../firmware-uploader-dialog.tsx | 21 +- .../ide-updater/ide-updater-dialog.tsx | 14 +- .../dialogs/settings/settings-component.tsx | 8 +- .../dialogs/settings/settings-dialog.tsx | 16 +- .../user-fields/user-fields-dialog.tsx | 12 +- .../browser/library/library-list-widget.ts | 10 +- .../library-widget-frontend-contribution.ts | 2 +- .../src/browser/notification-center.ts | 27 +- .../monitor/monitor-view-contribution.tsx | 4 +- .../browser/serial/monitor/monitor-widget.tsx | 12 +- .../monitor/serial-monitor-send-input.tsx | 6 +- .../monitor/serial-monitor-send-output.tsx | 8 +- .../plotter/plotter-frontend-contribution.ts | 6 +- .../src/browser/theia/core/about-dialog.ts | 10 + .../browser/theia/core/application-shell.ts | 16 +- .../theia/core/browser-main-menu-factory.ts | 4 +- .../browser/theia/core/browser-menu-plugin.ts | 2 +- .../core/common-frontend-contribution.ts | 4 +- .../theia/core/connection-status-service.ts | 44 ++- .../theia/core/frontend-application.ts | 16 +- .../browser/theia/core/json-schema-store.ts | 11 + .../src/browser/theia/core/keybindings.ts | 30 -- .../theia/core/shell-layout-restorer.ts | 56 --- .../browser/theia/core/tab-bar-decorator.ts | 2 +- .../browser/theia/core/tab-bar-toolbar.tsx | 2 +- .../src/browser/theia/core/tab-bars.ts | 2 +- .../debug/debug-configuration-manager.ts | 11 +- .../theia/debug/debug-configuration-model.ts | 6 +- ...debug-frontend-application-contribution.ts | 2 +- .../browser/theia/debug/debug-hover-source.ts | 21 -- .../browser/theia/debug/debug-hover-widget.ts | 119 ------ .../theia/debug/debug-session-manager.ts | 4 +- .../src/browser/theia/dialogs/dialogs.ts | 2 +- .../browser/theia/editor/editor-command.ts | 2 +- .../browser/theia/editor/editor-manager.ts | 4 +- .../editor/editor-navigation-contribution.ts | 11 + .../theia/editor/editor-widget-factory.ts | 11 +- .../keymaps/keymaps-frontend-contribution.ts | 2 +- .../theia/markers/problem-contribution.ts | 6 +- .../browser/theia/markers/problem-manager.ts | 4 +- .../notification-center-component.tsx | 2 +- .../theia/messages/notification-component.tsx | 2 +- .../notification-toasts-component.tsx | 2 +- .../theia/messages/notifications-manager.ts | 4 +- .../theia/messages/notifications-renderer.tsx | 2 +- .../theia/monaco/monaco-editor-provider.ts | 2 +- .../monaco/monaco-status-bar-contribution.ts | 4 +- .../theia/monaco/monaco-text-model-service.ts | 18 +- .../theia/navigator/navigator-contribution.ts | 14 +- .../navigator/navigator-tab-bar-decorator.ts | 4 +- .../theia/outline/outline-contribution.ts | 2 +- .../browser/theia/output/output-channel.ts | 4 +- .../output/output-toolbar-contribution.ts | 2 +- .../src/browser/theia/output/output-widget.ts | 2 +- .../output-channel-registry-main.ts | 12 +- .../preferences/preference-tree-generator.ts | 17 + .../preferences/preferences-contribution.ts | 4 +- .../src/browser/theia/scm/scm-contribution.ts | 4 +- .../search-in-workspace-factory.ts | 2 +- ...arch-in-workspace-frontend-contribution.ts | 4 +- .../search-in-workspace-result-tree-widget.ts | 2 +- .../search-in-workspace-widget.tsx | 4 +- .../theia/workspace/workspace-commands.ts | 15 +- .../workspace/workspace-delete-handler.ts | 9 +- .../workspace-frontend-contribution.ts | 8 +- .../theia/workspace/workspace-input-dialog.ts | 10 +- .../theia/workspace/workspace-service.ts | 91 ++--- .../workspace-variable-contribution.ts | 24 +- .../toolbar/arduino-toolbar-contribution.ts | 2 +- .../src/browser/toolbar/arduino-toolbar.tsx | 2 +- .../src/browser/widgets/arduino-select.tsx | 2 +- .../cloud-sketchbook-composite-widget.tsx | 4 +- .../cloud-sketchbook-contributions.ts | 9 +- .../cloud-sketchbook-tree-model.ts | 12 +- .../cloud-sketchbook-tree-widget.tsx | 16 +- .../cloud-sketchbook/cloud-sketchbook-tree.ts | 20 +- .../cloud-sketchbook-widget.ts | 8 +- .../cloud-sketchbook/cloud-user-status.tsx | 6 +- .../component-list/component-list-item.tsx | 2 +- .../widgets/component-list/component-list.tsx | 4 +- .../filterable-list-container.tsx | 6 +- .../list-widget-frontend-contribution.ts | 2 +- .../widgets/component-list/list-widget.tsx | 29 +- .../widgets/component-list/search-bar.tsx | 2 +- .../sketchbook/sketchbook-tree-model.ts | 21 +- .../sketchbook/sketchbook-tree-widget.tsx | 27 +- .../widgets/sketchbook/sketchbook-tree.ts | 2 +- .../sketchbook-widget-contribution.ts | 14 +- .../widgets/sketchbook/sketchbook-widget.tsx | 8 +- .../src/common/decorators.ts | 64 ++++ .../src/common/protocol/arduino-daemon.ts | 11 +- .../src/common/protocol/config-service.ts | 5 - .../common/protocol/notification-service.ts | 2 +- .../protocol/sketches-service-client-impl.ts | 48 ++- .../src/common/protocol/sketches-service.ts | 28 +- .../electron-window-service.ts | 2 +- .../theia/core/electron-main-menu-factory.ts | 39 +- .../theia/core/electron-menu-contribution.ts | 117 +++++- .../theia/core/electron-menu-module.ts | 2 +- .../theia/electron-main-application.ts | 127 ++++++- .../theia/electron-main-window-service.ts | 4 +- .../theia/theia-electron-window.ts | 33 +- .../src/node/arduino-daemon-impl.ts | 42 +-- .../src/node/arduino-ide-backend-module.ts | 12 +- .../src/node/board-discovery.ts | 4 +- arduino-ide-extension/src/node/cli-config.ts | 35 +- .../arduino/cli/commands/v1/commands_pb.d.ts | 17 + .../cc/arduino/cli/commands/v1/commands_pb.js | 120 +++++- .../cc/arduino/cli/commands/v1/common_pb.d.ts | 51 ++- .../cc/arduino/cli/commands/v1/common_pb.js | 294 +++++++++++++-- .../arduino/cli/commands/v1/compile_pb.d.ts | 24 +- .../cc/arduino/cli/commands/v1/compile_pb.js | 128 ++++++- .../arduino/cli/commands/v1/monitor_pb.d.ts | 4 + .../cc/arduino/cli/commands/v1/monitor_pb.js | 32 +- .../cc/arduino/cli/commands/v1/upload_pb.d.ts | 4 - .../cc/arduino/cli/commands/v1/upload_pb.js | 32 +- .../src/node/config-service-impl.ts | 129 +++---- .../src/node/core-client-provider.ts | 169 +++++---- arduino-ide-extension/src/node/daemon-log.ts | 153 -------- .../src/node/examples-service-impl.ts | 250 +++++++++---- .../src/node/grpc-client-provider.ts | 43 ++- .../src/node/notification-service-server.ts | 4 +- .../src/node/sketches-service-impl.ts | 301 +++++++++------ .../node/theia/core/backend-application.ts | 4 +- .../env-variables/env-variables-server.ts | 2 +- .../src/node/theia/git/git-init.ts | 56 --- .../workspace/default-workspace-server.ts | 51 ++- .../src/test/node/arduino-daemon-impl.test.ts | 6 +- .../src/test/node/cli-config.test.ts | 5 - arduino-ide-extension/tsconfig.json | 1 + electron/build/template-package.json | 4 +- i18n/en.json | 3 +- package.json | 2 - yarn.lock | 354 ++---------------- 179 files changed, 2684 insertions(+), 2076 deletions(-) create mode 100644 arduino-ide-extension/src/browser/theia/core/about-dialog.ts create mode 100644 arduino-ide-extension/src/browser/theia/core/json-schema-store.ts delete mode 100644 arduino-ide-extension/src/browser/theia/core/keybindings.ts delete mode 100644 arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts delete mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts delete mode 100644 arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts create mode 100644 arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts create mode 100644 arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts create mode 100644 arduino-ide-extension/src/common/decorators.ts delete mode 100644 arduino-ide-extension/src/node/daemon-log.ts delete mode 100644 arduino-ide-extension/src/node/theia/git/git-init.ts diff --git a/.gitignore b/.gitignore index 858ecbdea..4380cce54 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ arduino-ide-extension/data/cli/config scripts/themes/tokens # environment variables .env +# content trace files for electron +electron-app/traces diff --git a/.prettierrc b/.prettierrc index b20f01f1c..47e5c04d7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,6 @@ "singleQuote": true, "tabWidth": 2, "useTabs": false, - "printWidth": 80 + "printWidth": 80, + "endOfLine": "auto" } diff --git a/.vscode/launch.json b/.vscode/launch.json index d6ed25954..802b130d6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,44 @@ { "version": "0.2.0", "configurations": [ + { + "type": "node", + "request": "launch", + "name": "App (Electron) [Dev]", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd", + }, + "cwd": "${workspaceFolder}/electron-app", + "args": [ + ".", + "--log-level=debug", + "--hostname=localhost", + "--no-cluster", + "--app-project-path=${workspaceRoot}/electron-app", + "--remote-debugging-port=9222", + "--no-app-auto-install", + "--plugins=local-dir:../plugins", + "--hosted-plugin-inspect=9339", + "--nosplash", + "--content-trace", + "--open-devtools" + ], + "env": { + "NODE_ENV": "development" + }, + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/electron-app/src-gen/backend/*.js", + "${workspaceRoot}/electron-app/src-gen/frontend/*.js", + "${workspaceRoot}/electron-app/lib/**/*.js", + "${workspaceRoot}/arduino-ide-extension/lib/**/*.js", + "${workspaceRoot}/node_modules/@theia/**/*.js" + ], + "smartStep": true, + "internalConsoleOptions": "openOnSessionStart", + "outputCapture": "std" + }, { "type": "node", "request": "launch", @@ -10,7 +48,6 @@ "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd", }, "cwd": "${workspaceFolder}/electron-app", - "protocol": "inspector", "args": [ ".", "--log-level=debug", @@ -78,7 +115,6 @@ { "type": "node", "request": "launch", - "protocol": "inspector", "name": "Run Test [current]", "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", "args": [ diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index c2dd38c98..59060eb68 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -21,14 +21,13 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "@grpc/grpc-js": "^1.3.7", + "@grpc/grpc-js": "^1.6.7", "@theia/application-package": "1.25.0", "@theia/core": "1.25.0", "@theia/editor": "1.25.0", "@theia/editor-preview": "1.25.0", "@theia/electron": "1.25.0", "@theia/filesystem": "1.25.0", - "@theia/git": "1.25.0", "@theia/keymaps": "1.25.0", "@theia/markers": "1.25.0", "@theia/monaco": "1.25.0", @@ -45,7 +44,7 @@ "@types/btoa": "^1.2.3", "@types/dateformat": "^3.0.1", "@types/deepmerge": "^2.2.0", - "@types/glob": "^5.0.35", + "@types/glob": "^7.2.0", "@types/google-protobuf": "^3.7.2", "@types/js-yaml": "^3.12.2", "@types/keytar": "^4.4.0", @@ -63,14 +62,12 @@ "atob": "^2.1.2", "auth0-js": "^9.14.0", "btoa": "^1.2.1", - "css-element-queries": "^1.2.0", "dateformat": "^3.0.3", "deepmerge": "2.0.1", "electron-updater": "^4.6.5", - "fuzzy": "^0.1.3", + "fast-safe-stringify": "^2.1.1", "glob": "^7.1.6", - "google-protobuf": "^3.11.4", - "grpc": "^1.24.11", + "google-protobuf": "^3.20.1", "hash.js": "^1.1.7", "is-valid-path": "^0.1.1", "js-yaml": "^3.13.1", @@ -91,6 +88,7 @@ "semver": "^7.3.2", "string-natural-compare": "^2.0.3", "temp": "^0.9.1", + "temp-dir": "^2.0.0", "tree-kill": "^1.2.1", "upath": "^1.1.2", "url": "^0.11.0", @@ -157,10 +155,10 @@ ], "arduino": { "cli": { - "version": "0.21.0" + "version": "0.23.0" }, "fwuploader": { - "version": "2.0.0" + "version": "2.2.0" }, "clangd": { "version": "14.0.0" diff --git a/arduino-ide-extension/scripts/download-examples.js b/arduino-ide-extension/scripts/download-examples.js index a393c5552..c7bcf9865 100644 --- a/arduino-ide-extension/scripts/download-examples.js +++ b/arduino-ide-extension/scripts/download-examples.js @@ -4,30 +4,93 @@ const version = '1.9.1'; (async () => { + const os = require('os'); + const { promises: fs } = require('fs'); + const path = require('path'); + const shell = require('shelljs'); + const { v4 } = require('uuid'); - const os = require('os'); - const path = require('path'); - const shell = require('shelljs'); - const { v4 } = require('uuid'); + const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`); + if (shell.mkdir('-p', repository).code !== 0) { + shell.exit(1); + } - const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`); - if (shell.mkdir('-p', repository).code !== 0) { - shell.exit(1); - process.exit(1); - } - - if (shell.exec(`git clone https://github.com/arduino/arduino-examples.git ${repository}`).code !== 0) { - shell.exit(1); - process.exit(1); - } + if ( + shell.exec( + `git clone https://github.com/arduino/arduino-examples.git ${repository}` + ).code !== 0 + ) { + shell.exit(1); + } - if (shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`).code !== 0) { - shell.exit(1); - process.exit(1); - } + if ( + shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`) + .code !== 0 + ) { + shell.exit(1); + } - const destination = path.join(__dirname, '..', 'Examples'); - shell.mkdir('-p', destination); - shell.cp('-fR', path.join(repository, 'examples', '*'), destination); + const destination = path.join(__dirname, '..', 'Examples'); + shell.mkdir('-p', destination); + shell.cp('-fR', path.join(repository, 'examples', '*'), destination); + const isSketch = async (pathLike) => { + try { + const names = await fs.readdir(pathLike); + const dirName = path.basename(pathLike); + return names.indexOf(`${dirName}.ino`) !== -1; + } catch (e) { + if (e.code === 'ENOTDIR') { + return false; + } + throw e; + } + }; + const examples = []; + const categories = await fs.readdir(destination); + const visit = async (pathLike, container) => { + const stat = await fs.lstat(pathLike); + if (stat.isDirectory()) { + if (await isSketch(pathLike)) { + container.sketches.push({ + name: path.basename(pathLike), + relativePath: path.relative(destination, pathLike), + }); + } else { + const names = await fs.readdir(pathLike); + for (const name of names) { + const childPath = path.join(pathLike, name); + if (await isSketch(childPath)) { + container.sketches.push({ + name, + relativePath: path.relative(destination, childPath), + }); + } else { + const child = { + label: name, + children: [], + sketches: [], + }; + container.children.push(child); + await visit(childPath, child); + } + } + } + } + }; + for (const category of categories) { + const example = { + label: category, + children: [], + sketches: [], + }; + await visit(path.join(destination, category), example); + examples.push(example); + } + await fs.writeFile( + path.join(destination, 'examples.json'), + JSON.stringify(examples, null, 2), + { encoding: 'utf8' } + ); + shell.echo(`Generated output to ${path.join(destination, 'examples.json')}`); })(); diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index d70f2a596..eeb25f737 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -1,4 +1,8 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import * as React from '@theia/core/shared/react'; import * as remote from '@theia/core/electron-shared/@electron/remote'; import { @@ -7,6 +11,7 @@ import { ExecutableService, Sketch, LibraryService, + ArduinoDaemon, } from '../common/protocol'; import { Mutex } from 'async-mutex'; import { @@ -46,18 +51,12 @@ import { EditorManager, EditorOpenerOptions, } from '@theia/editor/lib/browser'; -import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution'; import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu'; -import { FileNavigatorCommands, FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution'; -import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution'; -import { OutputContribution } from '@theia/output/lib/browser/output-contribution'; -import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution'; -import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; +import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution'; import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { FileChangeType } from '@theia/filesystem/lib/browser'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; -import { ConfigService } from '../common/protocol/config-service'; import { ArduinoCommands } from './arduino-commands'; import { BoardsConfig } from './boards/boards-config'; import { BoardsConfigDialog } from './boards/boards-config-dialog'; @@ -68,9 +67,11 @@ import { ArduinoMenus } from './menu/arduino-menus'; import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; import { ArduinoToolbar } from './toolbar/arduino-toolbar'; import { ArduinoPreferences } from './arduino-preferences'; -import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../common/protocol/sketches-service-client-impl'; import { SaveAsSketch } from './contributions/save-as-sketch'; -import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution'; import { IDEUpdaterDialog } from './dialogs/ide-updater/ide-updater-dialog'; import { IDEUpdater } from '../common/protocol/ide-updater'; import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution'; @@ -86,93 +87,73 @@ export class ArduinoFrontendContribution TabBarToolbarContribution, CommandContribution, MenuContribution, - ColorContribution { + ColorContribution +{ @inject(ILogger) - protected logger: ILogger; + private readonly logger: ILogger; @inject(MessageService) - protected readonly messageService: MessageService; + private readonly messageService: MessageService; @inject(BoardsService) - protected readonly boardsService: BoardsService; + private readonly boardsService: BoardsService; @inject(LibraryService) - protected readonly libraryService: LibraryService; + private readonly libraryService: LibraryService; @inject(BoardsServiceProvider) - protected readonly boardsServiceClientImpl: BoardsServiceProvider; + private readonly boardsServiceClientImpl: BoardsServiceProvider; @inject(EditorManager) - protected readonly editorManager: EditorManager; + private readonly editorManager: EditorManager; @inject(FileService) - protected readonly fileService: FileService; + private readonly fileService: FileService; @inject(SketchesService) - protected readonly sketchService: SketchesService; + private readonly sketchService: SketchesService; @inject(BoardsConfigDialog) - protected readonly boardsConfigDialog: BoardsConfigDialog; + private readonly boardsConfigDialog: BoardsConfigDialog; @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; + private readonly commandRegistry: CommandRegistry; @inject(StatusBar) - protected readonly statusBar: StatusBar; - - @inject(FileNavigatorContribution) - protected readonly fileNavigatorContributions: FileNavigatorContribution; - - @inject(OutputContribution) - protected readonly outputContribution: OutputContribution; - - @inject(OutlineViewContribution) - protected readonly outlineContribution: OutlineViewContribution; - - @inject(ProblemContribution) - protected readonly problemContribution: ProblemContribution; - - @inject(ScmContribution) - protected readonly scmContribution: ScmContribution; - - @inject(SearchInWorkspaceFrontendContribution) - protected readonly siwContribution: SearchInWorkspaceFrontendContribution; - - @inject(SketchbookWidgetContribution) - protected readonly sketchbookWidgetContribution: SketchbookWidgetContribution; + private readonly statusBar: StatusBar; @inject(EditorMode) - protected readonly editorMode: EditorMode; - - @inject(ConfigService) - protected readonly configService: ConfigService; + private readonly editorMode: EditorMode; @inject(HostedPluginEvents) - protected hostedPluginEvents: HostedPluginEvents; + private readonly hostedPluginEvents: HostedPluginEvents; @inject(ExecutableService) - protected executableService: ExecutableService; + private readonly executableService: ExecutableService; @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; + private readonly arduinoPreferences: ArduinoPreferences; @inject(SketchesServiceClientImpl) - protected readonly sketchServiceClient: SketchesServiceClientImpl; + private readonly sketchServiceClient: SketchesServiceClientImpl; @inject(FrontendApplicationStateService) - protected readonly appStateService: FrontendApplicationStateService; + private readonly appStateService: FrontendApplicationStateService; @inject(LocalStorageService) - protected readonly localStorageService: LocalStorageService; + private readonly localStorageService: LocalStorageService; @inject(FileSystemFrontendContribution) - protected readonly fileSystemFrontendContribution: FileSystemFrontendContribution; + private readonly fileSystemFrontendContribution: FileSystemFrontendContribution; @inject(IDEUpdater) - protected readonly updater: IDEUpdater; + private readonly updater: IDEUpdater; @inject(IDEUpdaterDialog) - protected readonly updaterDialog: IDEUpdaterDialog; + private readonly updaterDialog: IDEUpdaterDialog; + + @inject(ArduinoDaemon) + private readonly daemon: ArduinoDaemon; protected invalidConfigPopup: | Promise @@ -243,7 +224,10 @@ export class ArduinoFrontendContribution updateStatusBar(this.boardsServiceClientImpl.boardsConfig); this.appStateService.reachedState('ready').then(async () => { const sketch = await this.sketchServiceClient.currentSketch(); - if (sketch && !(await this.sketchService.isTemp(sketch))) { + if ( + CurrentSketch.isValid(sketch) && + !(await this.sketchService.isTemp(sketch)) + ) { this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri))); this.toDisposeOnStop.push( this.fileService.onDidFilesChange(async (event) => { @@ -269,21 +253,6 @@ export class ArduinoFrontendContribution } async onStart(app: FrontendApplication): Promise { - // Initialize all `pro-mode` widgets. This is a NOOP if in normal mode. - for (const viewContribution of [ - this.fileNavigatorContributions, - this.outputContribution, - this.outlineContribution, - this.problemContribution, - this.scmContribution, - this.siwContribution, - this.sketchbookWidgetContribution, - ] as Array) { - if (viewContribution.initializeLayout) { - viewContribution.initializeLayout(app); - } - } - this.updater .init( this.arduinoPreferences.get('arduino.ide.updateChannel'), @@ -353,16 +322,18 @@ export class ArduinoFrontendContribution app.shell.leftPanelHandler.removeBottomMenu('settings-menu'); - this.fileSystemFrontendContribution.onDidChangeEditorFile(e => { - if (e.type === FileChangeType.DELETED) { - const editorWidget = e.editor; - if (SaveableWidget.is(editorWidget)) { - editorWidget.closeWithoutSaving(); - } else { - editorWidget.close(); + this.fileSystemFrontendContribution.onDidChangeEditorFile( + ({ type, editor }) => { + if (type === FileChangeType.DELETED) { + const editorWidget = editor; + if (SaveableWidget.is(editorWidget)) { + editorWidget.closeWithoutSaving(); + } else { + editorWidget.close(); + } } } - }); + ); } onStop(): void { @@ -375,6 +346,10 @@ export class ArduinoFrontendContribution fqbn: string, name: string | undefined ): Promise { + const port = await this.daemon.tryGetPort(); + if (!port) { + return; + } const release = await this.languageServerStartMutex.acquire(); try { await this.hostedPluginEvents.didStart; @@ -412,7 +387,7 @@ export class ArduinoFrontendContribution let currentSketchPath: string | undefined = undefined; if (log) { const currentSketch = await this.sketchServiceClient.currentSketch(); - if (currentSketch) { + if (CurrentSketch.isValid(currentSketch)) { currentSketchPath = await this.fileService.fsPath( new URI(currentSketch.uri) ); @@ -424,8 +399,6 @@ export class ArduinoFrontendContribution this.fileService.fsPath(new URI(lsUri)), ]); - const config = await this.configService.getConfiguration(); - this.languageServerFqbn = await Promise.race([ new Promise((_, reject) => setTimeout( @@ -437,7 +410,7 @@ export class ArduinoFrontendContribution 'arduino.languageserver.start', { lsPath, - cliDaemonAddr: `localhost:${config.daemon.port}`, // TODO: verify if this port is coming from the BE + cliDaemonAddr: `localhost:${port}`, clangdPath, log: currentSketchPath ? currentSketchPath : log, cliDaemonInstance: '1', @@ -503,13 +476,13 @@ export class ArduinoFrontendContribution EditorCommands.SPLIT_EDITOR_UP, EditorCommands.SPLIT_EDITOR_VERTICAL, EditorCommands.SPLIT_EDITOR_HORIZONTAL, - FileNavigatorCommands.REVEAL_IN_NAVIGATOR + FileNavigatorCommands.REVEAL_IN_NAVIGATOR, ]) { registry.unregisterCommand(command); } } - registerMenus(registry: MenuModelRegistry) { + registerMenus(registry: MenuModelRegistry): void { const menuId = (menuPath: string[]): string => { const index = menuPath.length - 1; const menuId = menuPath[index]; @@ -578,16 +551,19 @@ export class ArduinoFrontendContribution uri: string, forceOpen = false, options?: EditorOpenerOptions | undefined - ): Promise { + ): Promise { const widget = this.editorManager.all.find( (widget) => widget.editor.uri.toString() === uri ); if (!widget || forceOpen) { - return this.editorManager.open(new URI(uri), options ?? { - mode: 'reveal', - preview: false, - counter: 0 - }); + return this.editorManager.open( + new URI(uri), + options ?? { + mode: 'reveal', + preview: false, + counter: 0, + } + ); } } @@ -677,13 +653,13 @@ export class ArduinoFrontendContribution reason: 'temp-sketch', action: () => { return this.showTempSketchDialog(); - } - } + }, + }; } private async showTempSketchDialog(): Promise { const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return true; } const isTemp = await this.sketchService.isTemp(sketch); @@ -693,8 +669,14 @@ export class ArduinoFrontendContribution const messageBoxResult = await remote.dialog.showMessageBox( remote.getCurrentWindow(), { - message: nls.localize('arduino/sketch/saveTempSketch', 'Save your sketch to open it again later.'), - title: nls.localize('theia/core/quitTitle', 'Are you sure you want to quit?'), + message: nls.localize( + 'arduino/sketch/saveTempSketch', + 'Save your sketch to open it again later.' + ), + title: nls.localize( + 'theia/core/quitTitle', + 'Are you sure you want to quit?' + ), type: 'question', buttons: [ Dialog.CANCEL, @@ -702,7 +684,7 @@ export class ArduinoFrontendContribution nls.localizeByDefault("Don't Save"), ], } - ) + ); const result = messageBoxResult.response; if (result === 2) { return true; @@ -712,10 +694,10 @@ export class ArduinoFrontendContribution { execOnlyIfTemp: false, openAfterMove: false, - wipeOriginal: true + wipeOriginal: true, } )); } - return false + return false; } } diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index bf5f9455e..c520000b6 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -48,9 +48,7 @@ import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from import { MonacoStatusBarContribution } from './theia/monaco/monaco-status-bar-contribution'; import { ApplicationShell as TheiaApplicationShell, - ShellLayoutRestorer as TheiaShellLayoutRestorer, CommonFrontendContribution as TheiaCommonFrontendContribution, - KeybindingRegistry as TheiaKeybindingRegistry, TabBarRendererFactory, ContextMenuRenderer, createTreeContainer, @@ -88,7 +86,6 @@ import { TabBarDecoratorService } from './theia/core/tab-bar-decorator'; import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser'; import { ProblemManager } from './theia/markers/problem-manager'; import { BoardsAutoInstaller } from './boards/boards-auto-installer'; -import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer'; import { EditorMode } from './editor-mode'; import { ListItemRenderer } from './widgets/component-list/list-item-renderer'; import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution'; @@ -138,7 +135,6 @@ import { PreferencesContribution } from './theia/preferences/preferences-contrib import { QuitApp } from './contributions/quit-app'; import { SketchControl } from './contributions/sketch-control'; import { Settings } from './contributions/settings'; -import { KeybindingRegistry } from './theia/core/keybindings'; import { WorkspaceCommandContribution } from './theia/workspace/workspace-commands'; import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler'; import { WorkspaceDeleteHandler } from './theia/workspace/workspace-delete-handler'; @@ -284,8 +280,14 @@ import { Formatter, FormatterPath } from '../common/protocol/formatter'; import { Format } from './contributions/format'; import { MonacoFormattingConflictsContribution } from './theia/monaco/monaco-formatting-conflicts'; import { MonacoFormattingConflictsContribution as TheiaMonacoFormattingConflictsContribution } from '@theia/monaco/lib/browser/monaco-formatting-conflicts'; - -const ElementQueries = require('css-element-queries/src/ElementQueries'); +import { DefaultJsonSchemaContribution } from './theia/core/json-schema-store'; +import { DefaultJsonSchemaContribution as TheiaDefaultJsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store'; +import { EditorNavigationContribution } from './theia/editor/editor-navigation-contribution'; +import { EditorNavigationContribution as TheiaEditorNavigationContribution } from '@theia/editor/lib/browser/editor-navigation-contribution'; +import { PreferenceTreeGenerator } from './theia/preferences/preference-tree-generator'; +import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator'; +import { AboutDialog } from './theia/core/about-dialog'; +import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog'; MonacoThemingService.register({ id: 'arduino-theme', @@ -302,9 +304,6 @@ MonacoThemingService.register({ }); export default new ContainerModule((bind, unbind, isBound, rebind) => { - ElementQueries.listen(); - ElementQueries.init(); - // Commands and toolbar items bind(ArduinoFrontendContribution).toSelf().inSingletonScope(); bind(CommandContribution).toService(ArduinoFrontendContribution); @@ -493,7 +492,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TheiaPreferencesContribution) .to(PreferencesContribution) .inSingletonScope(); - rebind(TheiaKeybindingRegistry).to(KeybindingRegistry).inSingletonScope(); rebind(TheiaWorkspaceCommandContribution) .to(WorkspaceCommandContribution) .inSingletonScope(); @@ -560,10 +558,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ProblemManager).toSelf().inSingletonScope(); rebind(TheiaProblemManager).toService(ProblemManager); - // Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579 - bind(ShellLayoutRestorer).toSelf().inSingletonScope(); - rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer); - // No dropdown for the _Output_ view. bind(OutputToolbarContribution).toSelf().inSingletonScope(); rebind(TheiaOutputToolbarContribution).toService(OutputToolbarContribution); @@ -714,6 +708,26 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(NavigatorTabBarDecorator).toSelf().inSingletonScope(); rebind(TheiaNavigatorTabBarDecorator).toService(NavigatorTabBarDecorator); + // Do not fetch the `catalog.json` from Azure on FE load. + bind(DefaultJsonSchemaContribution).toSelf().inSingletonScope(); + rebind(TheiaDefaultJsonSchemaContribution).toService( + DefaultJsonSchemaContribution + ); + + // Do not block the app startup when initializing the editor navigation history. + bind(EditorNavigationContribution).toSelf().inSingletonScope(); + rebind(TheiaEditorNavigationContribution).toService( + EditorNavigationContribution + ); + + // IDE2 does not use the Theia preferences widget, no need to create and sync the underlying tree model. + bind(PreferenceTreeGenerator).toSelf().inSingletonScope(); + rebind(TheiaPreferenceTreeGenerator).toService(PreferenceTreeGenerator); + + // IDE2 has a custom about dialog, so there is no need to load the Theia extensions on FE load + bind(AboutDialog).toSelf().inSingletonScope(); + rebind(TheiaAboutDialog).toService(AboutDialog); + // To avoid running `Save All` when there are no dirty editors before starting the debug session. bind(DebugSessionManager).toSelf().inSingletonScope(); rebind(TheiaDebugSessionManager).toService(DebugSessionManager); diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx b/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx index 80cbf49fe..7ad65697a 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx @@ -55,12 +55,13 @@ export class BoardsConfigDialogWidget extends ReactWidget { onConfigChange={this.fireConfigChanged} onFocusNodeSet={this.setFocusNode} onFilteredTextDidChangeEvent={this.onFilterTextDidChangeEmitter.event} + onAppStateDidChange={this.notificationCenter.onAppStateDidChange} />
); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); if (this.focusNode instanceof HTMLInputElement) { this.focusNode.select(); diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts index 8bf143ab2..d5db717c8 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts +++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts @@ -26,7 +26,7 @@ export class BoardsConfigDialog extends AbstractDialog { constructor( @inject(BoardsConfigDialogProps) - protected readonly props: BoardsConfigDialogProps + protected override readonly props: BoardsConfigDialogProps ) { super(props); @@ -52,7 +52,7 @@ export class BoardsConfigDialog extends AbstractDialog { /** * Pass in an empty string if you want to reset the search term. Using `undefined` has no effect. */ - async open( + override async open( query: string | undefined = undefined ): Promise { if (typeof query === 'string') { @@ -95,7 +95,7 @@ export class BoardsConfigDialog extends AbstractDialog { return head; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -110,23 +110,23 @@ export class BoardsConfigDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message) { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { if (event.target instanceof HTMLTextAreaElement) { return false; } } - protected isValid(value: BoardsConfig.Config): DialogError { + protected override isValid(value: BoardsConfig.Config): DialogError { if (!value.selectedBoard) { if (value.selectedPort) { return nls.localize( diff --git a/arduino-ide-extension/src/browser/boards/boards-config.tsx b/arduino-ide-extension/src/browser/boards/boards-config.tsx index cd8ae9110..1a80ced5d 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-config.tsx @@ -16,6 +16,7 @@ import { } from './boards-service-provider'; import { naturalCompare } from '../../common/utils'; import { nls } from '@theia/core/lib/common'; +import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state'; export namespace BoardsConfig { export interface Config { @@ -29,6 +30,7 @@ export namespace BoardsConfig { readonly onConfigChange: (config: Config) => void; readonly onFocusNodeSet: (element: HTMLElement | undefined) => void; readonly onFilteredTextDidChangeEvent: Event; + readonly onAppStateDidChange: Event; } export interface State extends Config { @@ -47,7 +49,7 @@ export abstract class Item extends React.Component<{ missing?: boolean; details?: string; }> { - render(): React.ReactNode { + override render(): React.ReactNode { const { selected, label, missing, details } = this.props; const classNames = ['item']; if (selected) { @@ -99,14 +101,18 @@ export class BoardsConfig extends React.Component< }; } - componentDidMount() { - this.updateBoards(); - this.updatePorts( - this.props.boardsServiceProvider.availableBoards - .map(({ port }) => port) - .filter(notEmpty) - ); + override componentDidMount(): void { this.toDispose.pushAll([ + this.props.onAppStateDidChange((state) => { + if (state === 'ready') { + this.updateBoards(); + this.updatePorts( + this.props.boardsServiceProvider.availableBoards + .map(({ port }) => port) + .filter(notEmpty) + ); + } + }), this.props.notificationCenter.onAttachedBoardsChanged((event) => this.updatePorts( event.newState.ports, @@ -141,11 +147,11 @@ export class BoardsConfig extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { this.toDispose.dispose(); } - protected fireConfigChanged() { + protected fireConfigChanged(): void { const { selectedBoard, selectedPort } = this.state; this.props.onConfigChange({ selectedBoard, selectedPort }); } @@ -250,7 +256,7 @@ export class BoardsConfig extends React.Component< this.props.onFocusNodeSet(element || undefined); }; - render(): React.ReactNode { + override render(): React.ReactNode { return (
{this.renderContainer('boards', this.renderBoards.bind(this))} diff --git a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts b/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts index a3f2ec208..5f1c42e51 100644 --- a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts +++ b/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts @@ -13,6 +13,7 @@ import { BoardsDataStore } from './boards-data-store'; import { MainMenuManager } from '../../common/main-menu-manager'; import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus'; import { nls } from '@theia/core/lib/common'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @injectable() export class BoardsDataMenuUpdater implements FrontendApplicationContribution { @@ -31,11 +32,20 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution { @inject(BoardsServiceProvider) protected readonly boardsServiceClient: BoardsServiceProvider; + @inject(FrontendApplicationStateService) + private readonly appStateService: FrontendApplicationStateService; + protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); protected readonly toDisposeOnBoardChange = new DisposableCollection(); async onStart(): Promise { - this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard); + this.appStateService + .reachedState('ready') + .then(() => + this.updateMenuActions( + this.boardsServiceClient.boardsConfig.selectedBoard + ) + ); this.boardsDataStore.onChanged(() => this.updateMenuActions( this.boardsServiceClient.boardsConfig.selectedBoard diff --git a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts index da8a03875..ca2508fb9 100644 --- a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts +++ b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts @@ -30,7 +30,7 @@ export class BoardsListWidget extends ListWidget { } @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.toDispose.pushAll([ this.notificationCenter.onPlatformInstalled(() => @@ -42,7 +42,7 @@ export class BoardsListWidget extends ListWidget { ]); } - protected async install({ + protected override async install({ item, progressId, version, @@ -63,7 +63,7 @@ export class BoardsListWidget extends ListWidget { ); } - protected async uninstall({ + protected override async uninstall({ item, progressId, }: { diff --git a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx index b94e2a620..cc7cd24da 100644 --- a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx @@ -41,7 +41,7 @@ export class BoardsDropDown extends React.Component { } } - render(): React.ReactNode { + override render(): React.ReactNode { return ReactDOM.createPortal(this.renderNode(), this.dropdownElement); } @@ -130,13 +130,13 @@ export class BoardsToolBarItem extends React.Component< }); } - componentDidMount() { + override componentDidMount(): void { this.props.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => this.setState({ availableBoards }) ); } - componentWillUnmount(): void { + override componentWillUnmount(): void { this.toDispose.dispose(); } @@ -161,7 +161,7 @@ export class BoardsToolBarItem extends React.Component< event.nativeEvent.stopImmediatePropagation(); }; - render(): React.ReactNode { + override render(): React.ReactNode { const { coords, availableBoards } = this.state; const boardsConfig = this.props.boardsServiceClient.boardsConfig; const title = BoardsConfig.Config.toString(boardsConfig, { diff --git a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts index 815148714..af31aff6e 100644 --- a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts @@ -18,7 +18,7 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont }); } - async initializeLayout(): Promise { + override async initializeLayout(): Promise { this.openView(); } } diff --git a/arduino-ide-extension/src/browser/contributions/about.ts b/arduino-ide-extension/src/browser/contributions/about.ts index 761352e55..f3a50fc54 100644 --- a/arduino-ide-extension/src/browser/contributions/about.ts +++ b/arduino-ide-extension/src/browser/contributions/about.ts @@ -22,13 +22,13 @@ export class About extends Contribution { @inject(ConfigService) protected readonly configService: ConfigService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(About.Commands.ABOUT_APP, { execute: () => this.showAbout(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, { commandId: About.Commands.ABOUT_APP.id, label: nls.localize( diff --git a/arduino-ide-extension/src/browser/contributions/add-file.ts b/arduino-ide-extension/src/browser/contributions/add-file.ts index a6abaf78b..b7cb48f73 100644 --- a/arduino-ide-extension/src/browser/contributions/add-file.ts +++ b/arduino-ide-extension/src/browser/contributions/add-file.ts @@ -10,19 +10,20 @@ import { } from './contribution'; import { FileDialogService } from '@theia/filesystem/lib/browser'; import { nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class AddFile extends SketchContribution { @inject(FileDialogService) protected readonly fileDialogService: FileDialogService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(AddFile.Commands.ADD_FILE, { execute: () => this.addFile(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { commandId: AddFile.Commands.ADD_FILE.id, label: nls.localize('arduino/contributions/addFile', 'Add File') + '...', @@ -32,7 +33,7 @@ export class AddFile extends SketchContribution { protected async addFile(): Promise { const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } const toAddUri = await this.fileDialogService.showOpenDialog({ diff --git a/arduino-ide-extension/src/browser/contributions/add-zip-library.ts b/arduino-ide-extension/src/browser/contributions/add-zip-library.ts index d3f9a6e93..927af4868 100644 --- a/arduino-ide-extension/src/browser/contributions/add-zip-library.ts +++ b/arduino-ide-extension/src/browser/contributions/add-zip-library.ts @@ -28,13 +28,13 @@ export class AddZipLibrary extends SketchContribution { @inject(LibraryService) protected readonly libraryService: LibraryService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(AddZipLibrary.Commands.ADD_ZIP_LIBRARY, { execute: () => this.addZipLibrary(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { const includeLibMenuPath = [ ...ArduinoMenus.SKETCH__UTILS_GROUP, '0_include', diff --git a/arduino-ide-extension/src/browser/contributions/archive-sketch.ts b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts index 698264fe7..abe22d77f 100644 --- a/arduino-ide-extension/src/browser/contributions/archive-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts @@ -10,16 +10,17 @@ import { MenuModelRegistry, } from './contribution'; import { nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class ArchiveSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, { execute: () => this.archiveSketch(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id, label: nls.localize('arduino/sketch/archiveSketch', 'Archive Sketch'), @@ -32,7 +33,7 @@ export class ArchiveSketch extends SketchContribution { this.sketchServiceClient.currentSketch(), this.configService.getConfiguration(), ]); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } const archiveBasename = `${sketch.name}-${dateFormat( diff --git a/arduino-ide-extension/src/browser/contributions/board-selection.ts b/arduino-ide-extension/src/browser/contributions/board-selection.ts index 0a4669955..16b025662 100644 --- a/arduino-ide-extension/src/browser/contributions/board-selection.ts +++ b/arduino-ide-extension/src/browser/contributions/board-selection.ts @@ -47,7 +47,7 @@ export class BoardSelection extends SketchContribution { protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection(); - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, { execute: async () => { const { selectedBoard, selectedPort } = @@ -100,21 +100,22 @@ PID: ${PID}`; }); } - onStart(): void { - this.updateMenus(); - this.notificationCenter.onPlatformInstalled(this.updateMenus.bind(this)); - this.notificationCenter.onPlatformUninstalled(this.updateMenus.bind(this)); - this.boardsServiceProvider.onBoardsConfigChanged( - this.updateMenus.bind(this) - ); - this.boardsServiceProvider.onAvailableBoardsChanged( - this.updateMenus.bind(this) + override onStart(): void { + this.notificationCenter.onPlatformInstalled(() => this.updateMenus()); + this.notificationCenter.onPlatformUninstalled(() => this.updateMenus()); + this.boardsServiceProvider.onBoardsConfigChanged(() => this.updateMenus()); + this.boardsServiceProvider.onAvailableBoardsChanged(() => + this.updateMenus() ); - this.boardsServiceProvider.onAvailablePortsChanged( - this.updateMenus.bind(this) + this.boardsServiceProvider.onAvailablePortsChanged(() => + this.updateMenus() ); } + override async onReady(): Promise { + this.updateMenus(); + } + protected async updateMenus(): Promise { const [installedBoards, availablePorts, config] = await Promise.all([ this.installedBoards(), diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts index e7267ae34..1acda7d15 100644 --- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -28,15 +28,15 @@ export class BurnBootloader extends SketchContribution { protected readonly boardsServiceClientImpl: BoardsServiceProvider; @inject(OutputChannelManager) - protected readonly outputChannelManager: OutputChannelManager; + protected override readonly outputChannelManager: OutputChannelManager; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, { execute: () => this.burnBootloader(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, { commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id, label: nls.localize( diff --git a/arduino-ide-extension/src/browser/contributions/close.ts b/arduino-ide-extension/src/browser/contributions/close.ts index f801b5b47..45597613c 100644 --- a/arduino-ide-extension/src/browser/contributions/close.ts +++ b/arduino-ide-extension/src/browser/contributions/close.ts @@ -21,21 +21,21 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class Close extends SketchContribution { @inject(EditorManager) - protected readonly editorManager: EditorManager; + protected override readonly editorManager: EditorManager; protected shell: ApplicationShell; - onStart(app: FrontendApplication): void { + override onStart(app: FrontendApplication): void { this.shell = app.shell; } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(Close.Commands.CLOSE, { execute: () => remote.getCurrentWindow().close() }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: Close.Commands.CLOSE.id, label: nls.localize('vscode/editor.contribution/close', 'Close'), @@ -43,7 +43,7 @@ export class Close extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: Close.Commands.CLOSE.id, keybinding: 'CtrlCmd+W', diff --git a/arduino-ide-extension/src/browser/contributions/contribution.ts b/arduino-ide-extension/src/browser/contributions/contribution.ts index 8f7ff5ba7..1597cac28 100644 --- a/arduino-ide-extension/src/browser/contributions/contribution.ts +++ b/arduino-ide-extension/src/browser/contributions/contribution.ts @@ -1,4 +1,9 @@ -import { inject, injectable, interfaces } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + interfaces, + postConstruct, +} from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; import { ILogger } from '@theia/core/lib/common/logger'; import { Saveable } from '@theia/core/lib/browser/saveable'; @@ -34,7 +39,10 @@ import { } from '@theia/core/lib/common/command'; import { EditorMode } from '../editor-mode'; import { SettingsService } from '../dialogs/settings/settings'; -import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../common/protocol/sketches-service-client-impl'; import { SketchesService, ConfigService, @@ -42,6 +50,7 @@ import { Sketch, } from '../../common/protocol'; import { ArduinoPreferences } from '../arduino-preferences'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; export { Command, @@ -84,15 +93,31 @@ export abstract class Contribution @inject(SettingsService) protected readonly settingsService: SettingsService; + @inject(FrontendApplicationStateService) + protected readonly appStateService: FrontendApplicationStateService; + + @postConstruct() + protected init(): void { + this.appStateService.reachedState('ready').then(() => this.onReady()); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars onStart(app: FrontendApplication): MaybePromise {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars registerCommands(registry: CommandRegistry): void {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars registerMenus(registry: MenuModelRegistry): void {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars registerKeybindings(registry: KeybindingRegistry): void {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars registerToolbarItems(registry: TabBarToolbarRegistry): void {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + onReady(): MaybePromise {} } @injectable() @@ -127,7 +152,7 @@ export abstract class SketchContribution extends Contribution { protected async sourceOverride(): Promise> { const override: Record = {}; const sketch = await this.sketchServiceClient.currentSketch(); - if (sketch) { + if (CurrentSketch.isValid(sketch)) { for (const editor of this.editorManager.all) { const uri = editor.editor.uri; if (Saveable.isDirty(editor) && Sketch.isInSketch(uri, sketch)) { @@ -140,7 +165,7 @@ export abstract class SketchContribution extends Contribution { } export namespace Contribution { - export function configure( + export function configure( bind: interfaces.Bind, serviceIdentifier: typeof Contribution ): void { diff --git a/arduino-ide-extension/src/browser/contributions/debug.ts b/arduino-ide-extension/src/browser/contributions/debug.ts index 0a2614ad4..b1550ce32 100644 --- a/arduino-ide-extension/src/browser/contributions/debug.ts +++ b/arduino-ide-extension/src/browser/contributions/debug.ts @@ -12,7 +12,8 @@ import { SketchContribution, TabBarToolbarRegistry, } from './contribution'; -import { nls } from '@theia/core/lib/common'; +import { MaybePromise, nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class Debug extends SketchContribution { @@ -66,7 +67,7 @@ export class Debug extends SketchContribution { onDidChange: this.onDisabledMessageDidChange as Event, }; - onStart(): void { + override onStart(): void { this.onDisabledMessageDidChange( () => (this.debugToolbarItem.tooltip = `${ @@ -79,55 +80,18 @@ export class Debug extends SketchContribution { : Debug.Commands.START_DEBUGGING.label }`) ); - const refreshState = async ( - board: Board | undefined = this.boardsServiceProvider.boardsConfig - .selectedBoard - ) => { - if (!board) { - this.disabledMessage = nls.localize( - 'arduino/common/noBoardSelected', - 'No board selected' - ); - return; - } - const fqbn = board.fqbn; - if (!fqbn) { - this.disabledMessage = nls.localize( - 'arduino/debug/noPlatformInstalledFor', - "Platform is not installed for '{0}'", - board.name - ); - return; - } - const details = await this.boardService.getBoardDetails({ fqbn }); - if (!details) { - this.disabledMessage = nls.localize( - 'arduino/debug/noPlatformInstalledFor', - "Platform is not installed for '{0}'", - board.name - ); - return; - } - const { debuggingSupported } = details; - if (!debuggingSupported) { - this.disabledMessage = nls.localize( - 'arduino/debug/debuggingNotSupported', - "Debugging is not supported by '{0}'", - board.name - ); - } else { - this.disabledMessage = undefined; - } - }; this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => - refreshState(selectedBoard) + this.refreshState(selectedBoard) ); - this.notificationCenter.onPlatformInstalled(() => refreshState()); - this.notificationCenter.onPlatformUninstalled(() => refreshState()); - refreshState(); + this.notificationCenter.onPlatformInstalled(() => this.refreshState()); + this.notificationCenter.onPlatformUninstalled(() => this.refreshState()); + } + + override onReady(): MaybePromise { + this.refreshState(); } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(Debug.Commands.START_DEBUGGING, { execute: () => this.startDebug(), isVisible: (widget) => @@ -136,10 +100,51 @@ export class Debug extends SketchContribution { }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem(this.debugToolbarItem); } + private async refreshState( + board: Board | undefined = this.boardsServiceProvider.boardsConfig + .selectedBoard + ): Promise { + if (!board) { + this.disabledMessage = nls.localize( + 'arduino/common/noBoardSelected', + 'No board selected' + ); + return; + } + const fqbn = board.fqbn; + if (!fqbn) { + this.disabledMessage = nls.localize( + 'arduino/debug/noPlatformInstalledFor', + "Platform is not installed for '{0}'", + board.name + ); + return; + } + const details = await this.boardService.getBoardDetails({ fqbn }); + if (!details) { + this.disabledMessage = nls.localize( + 'arduino/debug/noPlatformInstalledFor', + "Platform is not installed for '{0}'", + board.name + ); + return; + } + const { debuggingSupported } = details; + if (!debuggingSupported) { + this.disabledMessage = nls.localize( + 'arduino/debug/debuggingNotSupported', + "Debugging is not supported by '{0}'", + board.name + ); + } else { + this.disabledMessage = undefined; + } + } + protected async startDebug( board: Board | undefined = this.boardsServiceProvider.boardsConfig .selectedBoard @@ -156,7 +161,7 @@ export class Debug extends SketchContribution { this.sketchServiceClient.currentSketch(), this.executableService.list(), ]); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri( diff --git a/arduino-ide-extension/src/browser/contributions/edit-contributions.ts b/arduino-ide-extension/src/browser/contributions/edit-contributions.ts index 3fee31490..6b77d5163 100644 --- a/arduino-ide-extension/src/browser/contributions/edit-contributions.ts +++ b/arduino-ide-extension/src/browser/contributions/edit-contributions.ts @@ -28,7 +28,7 @@ export class EditContributions extends Contribution { @inject(PreferenceService) protected readonly preferences: PreferenceService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(EditContributions.Commands.GO_TO_LINE, { execute: () => this.run('editor.action.gotoLine'), }); @@ -93,7 +93,7 @@ ${value} }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { commandId: CommonCommands.CUT.id, order: '0', @@ -201,7 +201,7 @@ ${value} }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: EditContributions.Commands.COPY_FOR_FORUM.id, keybinding: 'CtrlCmd+Shift+C', diff --git a/arduino-ide-extension/src/browser/contributions/examples.ts b/arduino-ide-extension/src/browser/contributions/examples.ts index 7a0e04daa..17368feab 100644 --- a/arduino-ide-extension/src/browser/contributions/examples.ts +++ b/arduino-ide-extension/src/browser/contributions/examples.ts @@ -1,5 +1,5 @@ import * as PQueue from 'p-queue'; -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { inject, injectable } from '@theia/core/shared/inversify'; import { CommandHandler } from '@theia/core/lib/common/command'; import { MenuPath, @@ -21,7 +21,7 @@ import { MenuModelRegistry, } from './contribution'; import { NotificationCenter } from '../notification-center'; -import { Board, Sketch, SketchContainer } from '../../common/protocol'; +import { Board, SketchRef, SketchContainer } from '../../common/protocol'; import { nls } from '@theia/core/lib/common'; @injectable() @@ -43,8 +43,8 @@ export abstract class Examples extends SketchContribution { protected readonly toDispose = new DisposableCollection(); - @postConstruct() - init(): void { + protected override init(): void { + super.init(); this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.handleBoardChanged(selectedBoard) ); @@ -54,7 +54,7 @@ export abstract class Examples extends SketchContribution { // NOOP } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { try { // This is a hack the ensures the desired menu ordering! We cannot use https://github.com/eclipse-theia/theia/pull/8377 due to ATL-222. const index = ArduinoMenus.FILE__EXAMPLES_SUBMENU.length - 1; @@ -82,7 +82,7 @@ export abstract class Examples extends SketchContribution { registerRecursively( sketchContainerOrPlaceholder: | SketchContainer - | (Sketch | SketchContainer)[] + | (SketchRef | SketchContainer)[] | string, menuPath: MenuPath, pushToDispose: DisposableCollection = new DisposableCollection(), @@ -100,7 +100,7 @@ export abstract class Examples extends SketchContribution { ) ); } else { - const sketches: Sketch[] = []; + const sketches: SketchRef[] = []; const children: SketchContainer[] = []; let submenuPath = menuPath; @@ -161,7 +161,7 @@ export abstract class Examples extends SketchContribution { @injectable() export class BuiltInExamples extends Examples { - onStart(): void { + override async onReady(): Promise { this.register(); // no `await` } @@ -201,13 +201,16 @@ export class LibraryExamples extends Examples { protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); - onStart(): void { - this.register(); // no `await` + override onStart(): void { this.notificationCenter.onLibraryInstalled(() => this.register()); this.notificationCenter.onLibraryUninstalled(() => this.register()); } - protected handleBoardChanged(board: Board | undefined): void { + override async onReady(): Promise { + this.register(); // no `await` + } + + protected override handleBoardChanged(board: Board | undefined): void { this.register(board); } diff --git a/arduino-ide-extension/src/browser/contributions/help.ts b/arduino-ide-extension/src/browser/contributions/help.ts index 63c3da323..36e09f52e 100644 --- a/arduino-ide-extension/src/browser/contributions/help.ts +++ b/arduino-ide-extension/src/browser/contributions/help.ts @@ -28,7 +28,7 @@ export class Help extends Contribution { @inject(QuickInputService) protected readonly quickInputService: QuickInputService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { const open = (url: string) => this.windowService.openNewWindow(url, { external: true }); const createOpenHandler = (url: string) => @@ -92,7 +92,7 @@ export class Help extends Contribution { ); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.unregisterMenuAction({ commandId: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id, }); @@ -136,7 +136,7 @@ export class Help extends Contribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: Help.Commands.FIND_IN_REFERENCE.id, keybinding: 'CtrlCmd+Shift+F', diff --git a/arduino-ide-extension/src/browser/contributions/include-library.ts b/arduino-ide-extension/src/browser/contributions/include-library.ts index f8f07e7a9..7347c3fa9 100644 --- a/arduino-ide-extension/src/browser/contributions/include-library.ts +++ b/arduino-ide-extension/src/browser/contributions/include-library.ts @@ -17,6 +17,7 @@ import { SketchContribution, Command, CommandRegistry } from './contribution'; import { NotificationCenter } from '../notification-center'; import { nls } from '@theia/core/lib/common'; import * as monaco from '@theia/monaco-editor-core'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class IncludeLibrary extends SketchContribution { @@ -30,7 +31,7 @@ export class IncludeLibrary extends SketchContribution { protected readonly mainMenuManager: MainMenuManager; @inject(EditorManager) - protected readonly editorManager: EditorManager; + protected override readonly editorManager: EditorManager; @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; @@ -44,8 +45,7 @@ export class IncludeLibrary extends SketchContribution { protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); protected readonly toDispose = new DisposableCollection(); - onStart(): void { - this.updateMenuActions(); + override onStart(): void { this.boardsServiceClient.onBoardsConfigChanged(() => this.updateMenuActions() ); @@ -55,7 +55,11 @@ export class IncludeLibrary extends SketchContribution { ); } - registerMenus(registry: MenuModelRegistry): void { + override async onReady(): Promise { + this.updateMenuActions(); + } + + override registerMenus(registry: MenuModelRegistry): void { // `Include Library` submenu const includeLibMenuPath = [ ...ArduinoMenus.SKETCH__UTILS_GROUP, @@ -78,7 +82,7 @@ export class IncludeLibrary extends SketchContribution { }); } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, { execute: async (arg) => { if (LibraryPackage.is(arg)) { @@ -169,7 +173,7 @@ export class IncludeLibrary extends SketchContribution { protected async includeLibrary(library: LibraryPackage): Promise { const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } // If the current editor is one of the additional files from the sketch, we use that. diff --git a/arduino-ide-extension/src/browser/contributions/new-sketch.ts b/arduino-ide-extension/src/browser/contributions/new-sketch.ts index bc6cdcf14..685ae7e2b 100644 --- a/arduino-ide-extension/src/browser/contributions/new-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/new-sketch.ts @@ -14,7 +14,7 @@ import { @injectable() export class NewSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(NewSketch.Commands.NEW_SKETCH, { execute: () => this.newSketch(), }); @@ -25,7 +25,7 @@ export class NewSketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: NewSketch.Commands.NEW_SKETCH.id, label: nls.localize('arduino/sketch/new', 'New'), @@ -33,14 +33,14 @@ export class NewSketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: NewSketch.Commands.NEW_SKETCH.id, keybinding: 'CtrlCmd+N', }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, diff --git a/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts b/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts index d5f4ddbcc..dfedf5d8c 100644 --- a/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts @@ -35,18 +35,19 @@ export class OpenRecentSketch extends SketchContribution { protected toDisposeBeforeRegister = new Map(); - onStart(): void { - const refreshMenu = (sketches: Sketch[]) => { - this.register(sketches); - this.mainMenuManager.update(); - }; + override onStart(): void { this.notificationCenter.onRecentSketchesChanged(({ sketches }) => - refreshMenu(sketches) + this.refreshMenu(sketches) ); - this.sketchService.recentlyOpenedSketches().then(refreshMenu); } - registerMenus(registry: MenuModelRegistry): void { + override async onReady(): Promise { + this.sketchService + .recentlyOpenedSketches() + .then((sketches) => this.refreshMenu(sketches)); + } + + override registerMenus(registry: MenuModelRegistry): void { registry.registerSubmenu( ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, nls.localize('arduino/sketch/openRecent', 'Open Recent'), @@ -54,6 +55,11 @@ export class OpenRecentSketch extends SketchContribution { ); } + private refreshMenu(sketches: Sketch[]): void { + this.register(sketches); + this.mainMenuManager.update(); + } + protected register(sketches: Sketch[]): void { const order = 0; for (const sketch of sketches) { diff --git a/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts b/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts index 4a753082a..03207126f 100644 --- a/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts +++ b/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts @@ -13,13 +13,13 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class OpenSketchExternal extends SketchContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(OpenSketchExternal.Commands.OPEN_EXTERNAL, { execute: () => this.openExternal(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, label: nls.localize('arduino/sketch/showFolder', 'Show Sketch Folder'), @@ -27,7 +27,7 @@ export class OpenSketchExternal extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, keybinding: 'CtrlCmd+Alt+K', diff --git a/arduino-ide-extension/src/browser/contributions/open-sketch.ts b/arduino-ide-extension/src/browser/contributions/open-sketch.ts index fefbbbb6c..f110addc3 100644 --- a/arduino-ide-extension/src/browser/contributions/open-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/open-sketch.ts @@ -43,7 +43,7 @@ export class OpenSketch extends SketchContribution { protected readonly toDispose = new DisposableCollection(); - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, { execute: (arg) => Sketch.is(arg) ? this.openSketch(arg) : this.openSketch(), @@ -116,7 +116,7 @@ export class OpenSketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: OpenSketch.Commands.OPEN_SKETCH.id, label: nls.localize('vscode/workspaceActions/openFileFolder', 'Open...'), @@ -124,14 +124,14 @@ export class OpenSketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: OpenSketch.Commands.OPEN_SKETCH.id, keybinding: 'CtrlCmd+O', }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, diff --git a/arduino-ide-extension/src/browser/contributions/quit-app.ts b/arduino-ide-extension/src/browser/contributions/quit-app.ts index d12e5fdfa..17a7874dd 100644 --- a/arduino-ide-extension/src/browser/contributions/quit-app.ts +++ b/arduino-ide-extension/src/browser/contributions/quit-app.ts @@ -13,7 +13,7 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class QuitApp extends Contribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { if (!isOSX) { registry.registerCommand(QuitApp.Commands.QUIT_APP, { execute: () => remote.app.quit(), @@ -21,7 +21,7 @@ export class QuitApp extends Contribution { } } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { // On macOS we will get the `Quit ${YOUR_APP_NAME}` menu item natively, no need to duplicate it. if (!isOSX) { registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, { @@ -32,7 +32,7 @@ export class QuitApp extends Contribution { } } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { if (!isOSX) { registry.registerKeybinding({ command: QuitApp.Commands.QUIT_APP.id, diff --git a/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts b/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts index cafb51b29..6aa63f30e 100644 --- a/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts @@ -14,6 +14,7 @@ import { nls } from '@theia/core/lib/common'; import { ApplicationShell, NavigatableWidget, Saveable } from '@theia/core/lib/browser'; import { EditorManager } from '@theia/editor/lib/browser'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class SaveAsSketch extends SketchContribution { @@ -22,18 +23,18 @@ export class SaveAsSketch extends SketchContribution { protected readonly applicationShell: ApplicationShell; @inject(EditorManager) - protected readonly editorManager: EditorManager; + protected override readonly editorManager: EditorManager; @inject(WindowService) protected readonly windowService: WindowService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, { execute: (args) => this.saveAs(args), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, label: nls.localize('vscode/fileCommands/saveAs', 'Save As...'), @@ -41,7 +42,7 @@ export class SaveAsSketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, keybinding: 'CtrlCmd+Shift+S', @@ -59,7 +60,7 @@ export class SaveAsSketch extends SketchContribution { }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT ): Promise { const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return false; } diff --git a/arduino-ide-extension/src/browser/contributions/save-sketch.ts b/arduino-ide-extension/src/browser/contributions/save-sketch.ts index 2792fd8eb..2c1ab550e 100644 --- a/arduino-ide-extension/src/browser/contributions/save-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/save-sketch.ts @@ -12,10 +12,11 @@ import { TabBarToolbarRegistry, } from './contribution'; import { nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class SaveSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH, { execute: () => this.saveSketch(), }); @@ -27,7 +28,7 @@ export class SaveSketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: SaveSketch.Commands.SAVE_SKETCH.id, label: nls.localize('vscode/fileCommands/save', 'Save'), @@ -35,14 +36,14 @@ export class SaveSketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: SaveSketch.Commands.SAVE_SKETCH.id, keybinding: 'CtrlCmd+S', }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, @@ -53,7 +54,7 @@ export class SaveSketch extends SketchContribution { async saveSketch(): Promise { const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } const isTemp = await this.sketchService.isTemp(sketch); diff --git a/arduino-ide-extension/src/browser/contributions/settings.ts b/arduino-ide-extension/src/browser/contributions/settings.ts index a6321c511..32030809e 100644 --- a/arduino-ide-extension/src/browser/contributions/settings.ts +++ b/arduino-ide-extension/src/browser/contributions/settings.ts @@ -18,7 +18,7 @@ export class Settings extends SketchContribution { protected settingsOpened = false; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(Settings.Commands.OPEN, { execute: async () => { let settings: Preferences | undefined = undefined; @@ -39,7 +39,7 @@ export class Settings extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__PREFERENCES_GROUP, { commandId: Settings.Commands.OPEN.id, label: @@ -52,7 +52,7 @@ export class Settings extends SketchContribution { registry.registerSubmenu(ArduinoMenus.FILE__ADVANCED_SUBMENU, 'Advanced'); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: Settings.Commands.OPEN.id, keybinding: 'CtrlCmd+,', diff --git a/arduino-ide-extension/src/browser/contributions/sketch-control.ts b/arduino-ide-extension/src/browser/contributions/sketch-control.ts index fa4974ce3..ea376fea1 100644 --- a/arduino-ide-extension/src/browser/contributions/sketch-control.ts +++ b/arduino-ide-extension/src/browser/contributions/sketch-control.ts @@ -19,7 +19,10 @@ import { } from './contribution'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; -import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../common/protocol/sketches-service-client-impl'; import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider'; import { nls } from '@theia/core/lib/common'; @@ -35,7 +38,7 @@ export class SketchControl extends SketchContribution { protected readonly contextMenuRenderer: ContextMenuRenderer; @inject(EditorManager) - protected readonly editorManager: EditorManager; + protected override readonly editorManager: EditorManager; @inject(SketchesServiceClientImpl) protected readonly sketchesServiceClient: SketchesServiceClientImpl; @@ -46,7 +49,7 @@ export class SketchControl extends SketchContribution { protected readonly toDisposeBeforeCreateNewContextMenu = new DisposableCollection(); - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand( SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR, { @@ -55,7 +58,7 @@ export class SketchControl extends SketchContribution { execute: async () => { this.toDisposeBeforeCreateNewContextMenu.dispose(); const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } @@ -70,25 +73,22 @@ export class SketchControl extends SketchContribution { return; } - const { mainFileUri, rootFolderFileUris } = - await this.sketchService.loadSketch(sketch.uri); + const { mainFileUri, rootFolderFileUris } = sketch; const uris = [mainFileUri, ...rootFolderFileUris]; - const currentSketch = - await this.sketchesServiceClient.currentSketch(); - const parentsketchUri = this.editorManager.currentEditor + const parentSketchUri = this.editorManager.currentEditor ?.getResourceUri() ?.toString(); - const parentsketch = await this.sketchService.getSketchFolder( - parentsketchUri || '' + const parentSketch = await this.sketchService.getSketchFolder( + parentSketchUri || '' ); // if the current file is in the current opened sketch, show extra menus if ( - currentSketch && - parentsketch && - parentsketch.uri === currentSketch.uri && - this.allowRename(parentsketch.uri) + sketch && + parentSketch && + parentSketch.uri === sketch.uri && + this.allowRename(parentSketch.uri) ) { this.menuRegistry.registerMenuAction( ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, @@ -122,10 +122,10 @@ export class SketchControl extends SketchContribution { } if ( - currentSketch && - parentsketch && - parentsketch.uri === currentSketch.uri && - this.allowDelete(parentsketch.uri) + sketch && + parentSketch && + parentSketch.uri === sketch.uri && + this.allowDelete(parentSketch.uri) ) { this.menuRegistry.registerMenuAction( ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, @@ -200,7 +200,7 @@ export class SketchControl extends SketchContribution { ); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction( ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, { @@ -228,7 +228,7 @@ export class SketchControl extends SketchContribution { ); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: WorkspaceCommands.NEW_FILE.id, keybinding: 'CtrlCmd+Shift+N', @@ -243,7 +243,7 @@ export class SketchControl extends SketchContribution { }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, command: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, diff --git a/arduino-ide-extension/src/browser/contributions/sketchbook.ts b/arduino-ide-extension/src/browser/contributions/sketchbook.ts index 2b57cb171..80dc99065 100644 --- a/arduino-ide-extension/src/browser/contributions/sketchbook.ts +++ b/arduino-ide-extension/src/browser/contributions/sketchbook.ts @@ -12,10 +12,10 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class Sketchbook extends Examples { @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; + protected override readonly commandRegistry: CommandRegistry; @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; + protected override readonly menuRegistry: MenuModelRegistry; @inject(MainMenuManager) protected readonly mainMenuManager: MainMenuManager; @@ -23,11 +23,7 @@ export class Sketchbook extends Examples { @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; - onStart(): void { - this.sketchService.getSketches({}).then((container) => { - this.register(container); - this.mainMenuManager.update(); - }); + override onStart(): void { this.sketchServiceClient.onSketchbookDidChange(() => { this.sketchService.getSketches({}).then((container) => { this.register(container); @@ -36,7 +32,14 @@ export class Sketchbook extends Examples { }); } - registerMenus(registry: MenuModelRegistry): void { + override async onReady(): Promise { + this.sketchService.getSketches({}).then((container) => { + this.register(container); + this.mainMenuManager.update(); + }); + } + + override registerMenus(registry: MenuModelRegistry): void { registry.registerSubmenu( ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, nls.localize('arduino/sketch/sketchbook', 'Sketchbook'), @@ -53,7 +56,7 @@ export class Sketchbook extends Examples { ); } - protected createHandler(uri: string): CommandHandler { + protected override createHandler(uri: string): CommandHandler { return { execute: async () => { const sketch = await this.sketchService.loadSketch(uri); diff --git a/arduino-ide-extension/src/browser/contributions/upload-certificate.ts b/arduino-ide-extension/src/browser/contributions/upload-certificate.ts index b57f8d036..91292eb49 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-certificate.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-certificate.ts @@ -39,7 +39,7 @@ export class UploadCertificate extends Contribution { protected dialogOpened = false; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(UploadCertificate.Commands.OPEN, { execute: async () => { try { @@ -93,7 +93,7 @@ export class UploadCertificate extends Contribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, { commandId: UploadCertificate.Commands.OPEN.id, label: UploadCertificate.Commands.OPEN.label, diff --git a/arduino-ide-extension/src/browser/contributions/upload-firmware.ts b/arduino-ide-extension/src/browser/contributions/upload-firmware.ts index 8b3f4f94f..6fc566904 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-firmware.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-firmware.ts @@ -16,7 +16,7 @@ export class UploadFirmware extends Contribution { protected dialogOpened = false; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(UploadFirmware.Commands.OPEN, { execute: async () => { try { @@ -30,7 +30,7 @@ export class UploadFirmware extends Contribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, { commandId: UploadFirmware.Commands.OPEN.id, label: UploadFirmware.Commands.OPEN.label, diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 1a198d4b1..f3478b1ea 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -1,4 +1,4 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { inject, injectable } from '@theia/core/shared/inversify'; import { Emitter } from '@theia/core/lib/common/event'; import { BoardUserField, CoreService } from '../../common/protocol'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; @@ -16,6 +16,7 @@ import { } from './contribution'; import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog'; import { DisposableCollection, nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class UploadSketch extends SketchContribution { @@ -47,8 +48,8 @@ export class UploadSketch extends SketchContribution { protected readonly menuActionsDisposables = new DisposableCollection(); - @postConstruct() - protected init(): void { + protected override init(): void { + super.init(); this.boardsServiceClientImpl.onBoardsConfigChanged(async () => { const userFields = await this.boardsServiceClientImpl.selectedBoardUserFields(); @@ -72,7 +73,7 @@ export class UploadSketch extends SketchContribution { return fqbn + '|' + address; } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, { execute: async () => { const key = this.selectedFqbnAddress(); @@ -134,7 +135,7 @@ export class UploadSketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { this.menuActionsDisposables.dispose(); this.menuActionsDisposables.push( @@ -177,7 +178,7 @@ export class UploadSketch extends SketchContribution { ); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: UploadSketch.Commands.UPLOAD_SKETCH.id, keybinding: 'CtrlCmd+U', @@ -188,7 +189,7 @@ export class UploadSketch extends SketchContribution { }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, @@ -209,7 +210,7 @@ export class UploadSketch extends SketchContribution { this.uploadInProgress = true; this.onDidChangeEmitter.fire(); const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } diff --git a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts index 4ebc5cff0..b22f5998a 100644 --- a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts @@ -14,6 +14,7 @@ import { TabBarToolbarRegistry, } from './contribution'; import { nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class VerifySketch extends SketchContribution { @@ -31,7 +32,7 @@ export class VerifySketch extends SketchContribution { protected verifyInProgress = false; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, { execute: () => this.verifySketch(), isEnabled: () => !this.verifyInProgress, @@ -50,7 +51,7 @@ export class VerifySketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { commandId: VerifySketch.Commands.VERIFY_SKETCH.id, label: nls.localize('arduino/sketch/verifyOrCompile', 'Verify/Compile'), @@ -66,7 +67,7 @@ export class VerifySketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: VerifySketch.Commands.VERIFY_SKETCH.id, keybinding: 'CtrlCmd+R', @@ -77,7 +78,7 @@ export class VerifySketch extends SketchContribution { }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, @@ -99,7 +100,7 @@ export class VerifySketch extends SketchContribution { this.onDidChangeEmitter.fire(); const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } try { diff --git a/arduino-ide-extension/src/browser/create/create-api.ts b/arduino-ide-extension/src/browser/create/create-api.ts index 19fa98ddc..1e8740a96 100644 --- a/arduino-ide-extension/src/browser/create/create-api.ts +++ b/arduino-ide-extension/src/browser/create/create-api.ts @@ -117,11 +117,11 @@ export class CreateApi { headers, }) ).sketches; - if (partialSketches.length != 0) { + if (partialSketches.length !== 0) { result.sketches = result.sketches.concat(partialSketches); } currentOffset = currentOffset + limit; - } while (partialSketches.length != 0); + } while (partialSketches.length !== 0); result.sketches.forEach((sketch) => this.sketchCache.addSketch(sketch)); return result.sketches; diff --git a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx index 77bc89240..4a9d2f4da 100644 --- a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx @@ -139,7 +139,7 @@ export class UploadCertificateDialog extends AbstractDialog { constructor( @inject(UploadCertificateDialogProps) - protected readonly props: UploadCertificateDialogProps + protected override readonly props: UploadCertificateDialogProps ) { super({ title: nls.localize( @@ -155,7 +155,7 @@ export class UploadCertificateDialog extends AbstractDialog { return; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -165,21 +165,21 @@ export class UploadCertificateDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { return false; } - close(): void { + override close(): void { if (this.busy) { return; } diff --git a/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx index 9e5b678af..dbbad77e6 100644 --- a/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx @@ -149,7 +149,7 @@ export class ShareSketchDialog extends AbstractDialog { constructor( @inject(ShareSketchDialogProps) - protected readonly props: ShareSketchDialogProps + protected override readonly props: ShareSketchDialogProps ) { super({ title: props.title }); this.contentNode.classList.add('arduino-share-sketch-dialog'); @@ -159,7 +159,7 @@ export class ShareSketchDialog extends AbstractDialog { get value(): void { return; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -168,12 +168,12 @@ export class ShareSketchDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } diff --git a/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts b/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts index 474573ff8..a7982aca5 100644 --- a/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts +++ b/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts @@ -19,7 +19,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog { constructor( @inject(DoNotAskAgainDialogProps) - protected readonly props: DoNotAskAgainDialogProps + protected override readonly props: DoNotAskAgainDialogProps ) { super(props); this.controlPanel.removeChild(this.errorMessageNode); @@ -42,7 +42,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog { this.doNotAskAgainCheckbox.type = 'checkbox'; } - protected async accept(): Promise { + protected override async accept(): Promise { if (!this.resolve) { return; } @@ -65,7 +65,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog { } } - protected setErrorMessage(error: DialogError): void { + protected override setErrorMessage(error: DialogError): void { if (this.acceptButton) { this.acceptButton.disabled = !DialogError.getResult(error); } diff --git a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx index 9dd8a9b6b..dd966422e 100644 --- a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx @@ -15,6 +15,7 @@ import { } from '../../../common/protocol/arduino-firmware-uploader'; import { FirmwareUploaderComponent } from './firmware-uploader-component'; import { UploadFirmware } from '../../contributions/upload-firmware'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @injectable() export class UploadFirmwareDialogWidget extends ReactWidget { @@ -24,6 +25,9 @@ export class UploadFirmwareDialogWidget extends ReactWidget { @inject(ArduinoFirmwareUploader) protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader; + @inject(FrontendApplicationStateService) + private readonly appStatusService: FrontendApplicationStateService; + protected updatableFqbns: string[] = []; protected availableBoards: AvailableBoard[] = []; protected isOpen = new Object(); @@ -38,7 +42,8 @@ export class UploadFirmwareDialogWidget extends ReactWidget { @postConstruct() protected init(): void { - this.arduinoFirmwareUploader.updatableBoards().then((fqbns) => { + this.appStatusService.reachedState('ready').then(async () => { + const fqbns = await this.arduinoFirmwareUploader.updatableBoards(); this.updatableFqbns = fqbns; this.update(); }); @@ -56,7 +61,7 @@ export class UploadFirmwareDialogWidget extends ReactWidget { .finally(() => this.busyCallback(false)); } - onCloseRequest(msg: Message): void { + protected override onCloseRequest(msg: Message): void { super.onCloseRequest(msg); this.isOpen = new Object(); } @@ -88,7 +93,7 @@ export class UploadFirmwareDialog extends AbstractDialog { constructor( @inject(UploadFirmwareDialogProps) - protected readonly props: UploadFirmwareDialogProps + protected override readonly props: UploadFirmwareDialogProps ) { super({ title: UploadFirmware.Commands.OPEN.label || '' }); this.contentNode.classList.add('firmware-uploader-dialog'); @@ -99,7 +104,7 @@ export class UploadFirmwareDialog extends AbstractDialog { return; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -109,21 +114,21 @@ export class UploadFirmwareDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { return false; } - close(): void { + override close(): void { if (this.busy) { return; } diff --git a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx index 215e00f31..2b0b952bd 100644 --- a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx @@ -70,7 +70,7 @@ export class IDEUpdaterDialogWidget extends ReactWidget { this.close(); } - close(): void { + override close(): void { super.close(); this.onClose(); } @@ -122,7 +122,7 @@ export class IDEUpdaterDialog extends AbstractDialog { constructor( @inject(IDEUpdaterDialogProps) - protected readonly props: IDEUpdaterDialogProps + protected override readonly props: IDEUpdaterDialogProps ) { super({ title: nls.localize( @@ -138,7 +138,7 @@ export class IDEUpdaterDialog extends AbstractDialog { return this.widget.updateInfo; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -147,7 +147,7 @@ export class IDEUpdaterDialog extends AbstractDialog { this.update(); } - async open( + override async open( data: UpdateInfo | undefined = undefined ): Promise { if (data && data.version) { @@ -156,17 +156,17 @@ export class IDEUpdaterDialog extends AbstractDialog { } } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - close(): void { + override close(): void { this.widget.dispose(); super.close(); } diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx index 33272a32f..86d0d6847 100644 --- a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx +++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx @@ -32,7 +32,7 @@ export class SettingsComponent extends React.Component< super(props); } - componentDidUpdate( + override componentDidUpdate( _: SettingsComponent.Props, prevState: SettingsComponent.State ): void { @@ -49,7 +49,7 @@ export class SettingsComponent extends React.Component< } } - componentDidMount(): void { + override componentDidMount(): void { this.props.settingsService .settings() .then((settings) => @@ -67,11 +67,11 @@ export class SettingsComponent extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { this.toDispose.dispose(); } - render(): React.ReactNode { + override render(): React.ReactNode { if (!this.state) { return
; } diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx index 498de0cfa..9c51479c9 100644 --- a/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx @@ -56,7 +56,7 @@ export class SettingsDialog extends AbstractDialog> { constructor( @inject(SettingsDialogProps) - protected readonly props: SettingsDialogProps + protected override readonly props: SettingsDialogProps ) { super(props); this.contentNode.classList.add('arduino-settings-dialog'); @@ -73,7 +73,7 @@ export class SettingsDialog extends AbstractDialog> { ); } - protected async isValid(settings: Promise): Promise { + protected override async isValid(settings: Promise): Promise { const result = await this.settingsService.validate(settings); if (typeof result === 'string') { return result; @@ -85,7 +85,7 @@ export class SettingsDialog extends AbstractDialog> { return this.settingsService.settings(); } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -97,12 +97,12 @@ export class SettingsDialog extends AbstractDialog> { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); // calling settingsService.reset() in order to reload the settings from the preferenceService @@ -172,17 +172,17 @@ export class AdditionalUrlsDialog extends AbstractDialog { return AdditionalUrls.parse(this.textArea.value, 'newline'); } - protected onAfterAttach(message: Message): void { + protected override onAfterAttach(message: Message): void { super.onAfterAttach(message); this.addUpdateListener(this.textArea, 'input'); } - protected onActivateRequest(message: Message): void { + protected override onActivateRequest(message: Message): void { super.onActivateRequest(message); this.textArea.focus(); } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { if (event.target instanceof HTMLInputElement) { return super.handleEnter(event); } diff --git a/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx index 19a7aee37..8835fd355 100644 --- a/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx @@ -61,7 +61,7 @@ export class UserFieldsDialog extends AbstractDialog { constructor( @inject(UserFieldsDialogProps) - protected readonly props: UserFieldsDialogProps + protected override readonly props: UserFieldsDialogProps ) { super({ title: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label || '', @@ -83,7 +83,7 @@ export class UserFieldsDialog extends AbstractDialog { return this.widget.currentUserFields; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -92,17 +92,17 @@ export class UserFieldsDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - protected async accept(): Promise { + protected override async accept(): Promise { // If the user presses enter and at least // a field is empty don't accept the input for (const field of this.value) { @@ -113,7 +113,7 @@ export class UserFieldsDialog extends AbstractDialog { return super.accept(); } - close(): void { + override close(): void { this.widget.resetUserFieldsValue(); this.widget.close(); super.close(); diff --git a/arduino-ide-extension/src/browser/library/library-list-widget.ts b/arduino-ide-extension/src/browser/library/library-list-widget.ts index 033e1802c..dc82199e8 100644 --- a/arduino-ide-extension/src/browser/library/library-list-widget.ts +++ b/arduino-ide-extension/src/browser/library/library-list-widget.ts @@ -38,7 +38,7 @@ export class LibraryListWidget extends ListWidget { } @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.toDispose.pushAll([ this.notificationCenter.onLibraryInstalled(() => this.refresh(undefined)), @@ -48,7 +48,7 @@ export class LibraryListWidget extends ListWidget { ]); } - protected async install({ + protected override async install({ item, progressId, version, @@ -158,7 +158,7 @@ export class LibraryListWidget extends ListWidget { } } - protected async uninstall({ + protected override async uninstall({ item, progressId, }: { @@ -199,7 +199,7 @@ class MessageBoxDialog extends AbstractDialog { }); } - protected onCloseRequest(message: Message): void { + protected override onCloseRequest(message: Message): void { super.onCloseRequest(message); this.accept(); } @@ -217,7 +217,7 @@ class MessageBoxDialog extends AbstractDialog { return message; } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { this.response = 0; super.handleEnter(event); } diff --git a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts index 1a06e8472..37a3b0679 100644 --- a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts @@ -28,7 +28,7 @@ export class LibraryListWidgetFrontendContribution this.openView(); } - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { if (this.toggleCommand) { menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { commandId: this.toggleCommand.id, diff --git a/arduino-ide-extension/src/browser/notification-center.ts b/arduino-ide-extension/src/browser/notification-center.ts index ab52f27ab..b6ad3b4b6 100644 --- a/arduino-ide-extension/src/browser/notification-center.ts +++ b/arduino-ide-extension/src/browser/notification-center.ts @@ -1,4 +1,8 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { Emitter } from '@theia/core/lib/common/event'; import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; @@ -14,6 +18,10 @@ import { Config, Sketch, } from '../common/protocol'; +import { + FrontendApplicationStateService, + FrontendApplicationState, +} from '@theia/core/lib/browser/frontend-application-state'; @injectable() export class NotificationCenter @@ -22,8 +30,11 @@ export class NotificationCenter @inject(NotificationServiceServer) protected readonly server: JsonRpcProxy; + @inject(FrontendApplicationStateService) + private readonly appStateService: FrontendApplicationStateService; + protected readonly indexUpdatedEmitter = new Emitter(); - protected readonly daemonStartedEmitter = new Emitter(); + protected readonly daemonStartedEmitter = new Emitter(); protected readonly daemonStoppedEmitter = new Emitter(); protected readonly configChangedEmitter = new Emitter<{ config: Config | undefined; @@ -45,6 +56,8 @@ export class NotificationCenter protected readonly recentSketchesChangedEmitter = new Emitter<{ sketches: Sketch[]; }>(); + private readonly onAppStateDidChangeEmitter = + new Emitter(); protected readonly toDispose = new DisposableCollection( this.indexUpdatedEmitter, @@ -68,10 +81,16 @@ export class NotificationCenter readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event; readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event; readonly onRecentSketchesChanged = this.recentSketchesChangedEmitter.event; + readonly onAppStateDidChange = this.onAppStateDidChangeEmitter.event; @postConstruct() protected init(): void { this.server.setClient(this); + this.toDispose.push( + this.appStateService.onStateChanged((state) => + this.onAppStateDidChangeEmitter.fire(state) + ) + ); } onStop(): void { @@ -82,8 +101,8 @@ export class NotificationCenter this.indexUpdatedEmitter.fire(); } - notifyDaemonStarted(): void { - this.daemonStartedEmitter.fire(); + notifyDaemonStarted(port: string): void { + this.daemonStartedEmitter.fire(port); } notifyDaemonStopped(): void { diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index 43b5a0a12..c29f7ca3e 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -62,7 +62,7 @@ export class MonitorViewContribution }); } - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { if (this.toggleCommand) { menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { commandId: this.toggleCommand.id, @@ -95,7 +95,7 @@ export class MonitorViewContribution }); } - registerCommands(commands: CommandRegistry): void { + override registerCommands(commands: CommandRegistry): void { commands.registerCommand(SerialMonitor.Commands.CLEAR_OUTPUT, { isEnabled: (widget) => widget instanceof MonitorWidget, isVisible: (widget) => widget instanceof MonitorWidget, diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index cbe94e58b..24d6449e7 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -75,21 +75,21 @@ export class MonitorWidget extends ReactWidget { this.update(); } - dispose(): void { + override dispose(): void { super.dispose(); } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { super.onAfterAttach(msg); this.serialConnection.openWSToBE(); } - onCloseRequest(msg: Message): void { + protected override onCloseRequest(msg: Message): void { this.closing = true; super.onCloseRequest(msg); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { // TODO: `this.isAttached` // See: https://github.com/eclipse-theia/theia/issues/6704#issuecomment-562574713 if (!this.closing && this.isAttached) { @@ -97,13 +97,13 @@ export class MonitorWidget extends ReactWidget { } } - protected onResize(msg: Widget.ResizeMessage): void { + protected override onResize(msg: Widget.ResizeMessage): void { super.onResize(msg); this.widgetHeight = msg.height; this.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); (this.focusNode || this.node).focus(); } diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx index 15215307d..8000d9fe4 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx @@ -32,7 +32,7 @@ export class SerialMonitorSendInput extends React.Component< this.onKeyDown = this.onKeyDown.bind(this); } - componentDidMount(): void { + override componentDidMount(): void { this.props.serialConnection.isBESerialConnected().then((connected) => { this.setState({ connected }); }); @@ -50,12 +50,12 @@ export class SerialMonitorSendInput extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { // TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout? this.toDisposeBeforeUnmount.dispose(); } - render(): React.ReactNode { + override render(): React.ReactNode { return ( { @@ -87,7 +87,7 @@ export class SerialMonitorOutput extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { // TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout? this.toDisposeBeforeUnmount.dispose(); } diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index da2d7384a..c2f23ae8f 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -45,7 +45,7 @@ export class PlotterFrontendContribution extends Contribution { @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; - onStart(app: FrontendApplication): MaybePromise { + override onStart(app: FrontendApplication): MaybePromise { this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString(); ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => { @@ -56,13 +56,13 @@ export class PlotterFrontendContribution extends Contribution { return super.onStart(app); } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(SerialPlotterContribution.Commands.OPEN, { execute: this.connect.bind(this), }); } - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { commandId: SerialPlotterContribution.Commands.OPEN.id, label: SerialPlotterContribution.Commands.OPEN.label, diff --git a/arduino-ide-extension/src/browser/theia/core/about-dialog.ts b/arduino-ide-extension/src/browser/theia/core/about-dialog.ts new file mode 100644 index 000000000..3e89c5105 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/core/about-dialog.ts @@ -0,0 +1,10 @@ +import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog'; +import { duration } from '../../../common/decorators'; + +export class AboutDialog extends TheiaAboutDialog { + @duration({ name: 'theia-about#init' }) + protected override async init(): Promise { + // NOOP + // IDE2 has a custom about dialog, so it does not make sense to collect Theia extensions at startup time. + } +} diff --git a/arduino-ide-extension/src/browser/theia/core/application-shell.ts b/arduino-ide-extension/src/browser/theia/core/application-shell.ts index 8af61bdf2..eff6a3a04 100644 --- a/arduino-ide-extension/src/browser/theia/core/application-shell.ts +++ b/arduino-ide-extension/src/browser/theia/core/application-shell.ts @@ -15,7 +15,7 @@ import { } from '@theia/core/lib/browser'; import { Sketch } from '../../../common/protocol'; import { SaveAsSketch } from '../../contributions/save-as-sketch'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; import { nls } from '@theia/core/lib/common'; import URI from '@theia/core/lib/common/uri'; @@ -33,7 +33,7 @@ export class ApplicationShell extends TheiaApplicationShell { @inject(ConnectionStatusService) protected readonly connectionStatusService: ConnectionStatusService; - protected track(widget: Widget): void { + protected override track(widget: Widget): void { super.track(widget); if (widget instanceof OutputWidget) { widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700 @@ -41,7 +41,7 @@ export class ApplicationShell extends TheiaApplicationShell { if (widget instanceof EditorWidget) { // Make the editor un-closeable asynchronously. this.sketchesServiceClient.currentSketch().then((sketch) => { - if (sketch) { + if (CurrentSketch.isValid(sketch)) { if (!this.isSketchFile(widget.editor.uri, sketch.uri)) { return; } @@ -61,7 +61,7 @@ export class ApplicationShell extends TheiaApplicationShell { return false; } - async addWidget( + override async addWidget( widget: Widget, options: Readonly = {} ): Promise { @@ -87,19 +87,19 @@ export class ApplicationShell extends TheiaApplicationShell { return super.addWidget(widget, { ...options, ref }); } - handleEvent(): boolean { + override handleEvent(): boolean { // NOOP, dragging has been disabled - return false + return false; } // Avoid hiding top panel as we use it for arduino toolbar - protected createTopPanel(): Panel { + protected override createTopPanel(): Panel { const topPanel = super.createTopPanel(); topPanel.show(); return topPanel; } - async saveAll(): Promise { + override async saveAll(): Promise { if ( this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE ) { diff --git a/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts b/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts index 2898dd7c4..737ca98ac 100644 --- a/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts +++ b/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts @@ -12,12 +12,12 @@ export class BrowserMainMenuFactory { protected menuBar: MenuBarWidget | undefined; - createMenuBar(): MenuBarWidget { + override createMenuBar(): MenuBarWidget { this.menuBar = super.createMenuBar(); return this.menuBar; } - update() { + update(): void { if (this.menuBar) { this.menuBar.clearMenus(); this.fillMenuBar(this.menuBar); diff --git a/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts b/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts index e82fd719e..ee234fbdb 100644 --- a/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts +++ b/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts @@ -4,7 +4,7 @@ import { BrowserMenuBarContribution } from '@theia/core/lib/browser/menu/browser @injectable() export class ArduinoMenuContribution extends BrowserMenuBarContribution { - onStart(app: FrontendApplication): void { + override onStart(app: FrontendApplication): void { const menu = this.factory.createMenuBar(); app.shell.addWidget(menu, { area: 'top' }); } diff --git a/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts index 4687dfde3..8a0d30b5d 100644 --- a/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts @@ -8,7 +8,7 @@ import { CommandRegistry } from '@theia/core/lib/common/command'; @injectable() export class CommonFrontendContribution extends TheiaCommonFrontendContribution { - registerCommands(commandRegistry: CommandRegistry): void { + override registerCommands(commandRegistry: CommandRegistry): void { super.registerCommands(commandRegistry); for (const command of [ @@ -26,7 +26,7 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution } } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); for (const command of [ CommonCommands.SAVE, diff --git a/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts b/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts index 5117d2730..ae997183f 100644 --- a/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts +++ b/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts @@ -1,4 +1,8 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { Disposable } from '@theia/core/lib/common/disposable'; import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar'; import { @@ -18,18 +22,22 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; - protected isRunning = false; + protected connectedPort: string | undefined; @postConstruct() - protected async init(): Promise { + protected override async init(): Promise { this.schedulePing(); try { - this.isRunning = await this.daemon.isRunning(); + this.connectedPort = await this.daemon.tryGetPort(); } catch {} - this.notificationCenter.onDaemonStarted(() => (this.isRunning = true)); - this.notificationCenter.onDaemonStopped(() => (this.isRunning = false)); + this.notificationCenter.onDaemonStarted( + (port) => (this.connectedPort = port) + ); + this.notificationCenter.onDaemonStopped( + () => (this.connectedPort = undefined) + ); this.wsConnectionProvider.onIncomingMessageActivity(() => { - this.updateStatus(this.isRunning); + this.updateStatus(!!this.connectedPort); this.schedulePing(); }); } @@ -43,32 +51,36 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; - protected isRunning = false; + protected connectedPort: string | undefined; @postConstruct() protected async init(): Promise { try { - this.isRunning = await this.daemon.isRunning(); + this.connectedPort = await this.daemon.tryGetPort(); } catch {} - this.notificationCenter.onDaemonStarted(() => (this.isRunning = true)); - this.notificationCenter.onDaemonStopped(() => (this.isRunning = false)); + this.notificationCenter.onDaemonStarted( + (port) => (this.connectedPort = port) + ); + this.notificationCenter.onDaemonStopped( + () => (this.connectedPort = undefined) + ); } - protected onStateChange(state: ConnectionStatus): void { - if (!this.isRunning && state === ConnectionStatus.ONLINE) { + protected override onStateChange(state: ConnectionStatus): void { + if (!this.connectedPort && state === ConnectionStatus.ONLINE) { return; } super.onStateChange(state); } - protected handleOffline(): void { + protected override handleOffline(): void { this.statusBar.setElement('connection-status', { alignment: StatusBarAlignment.LEFT, - text: this.isRunning + text: this.connectedPort ? nls.localize('theia/core/offline', 'Offline') : '$(bolt) ' + nls.localize('theia/core/daemonOffline', 'CLI Daemon Offline'), - tooltip: this.isRunning + tooltip: this.connectedPort ? nls.localize( 'theia/core/cannotConnectBackend', 'Cannot connect to the backend.' diff --git a/arduino-ide-extension/src/browser/theia/core/frontend-application.ts b/arduino-ide-extension/src/browser/theia/core/frontend-application.ts index 9c5234c46..ba6b2f8bc 100644 --- a/arduino-ide-extension/src/browser/theia/core/frontend-application.ts +++ b/arduino-ide-extension/src/browser/theia/core/frontend-application.ts @@ -20,22 +20,22 @@ export class FrontendApplication extends TheiaFrontendApplication { @inject(SketchesService) protected readonly sketchesService: SketchesService; - protected async initializeLayout(): Promise { + protected override async initializeLayout(): Promise { await super.initializeLayout(); - const roots = await this.workspaceService.roots; - for (const root of roots) { - const exists = await this.fileService.exists(root.resource); - if (exists) { - this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu + this.workspaceService.roots.then(async (roots) => { + for (const root of roots) { await this.commandService.executeCommand( ArduinoCommands.OPEN_SKETCH_FILES.id, root.resource ); + this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu } - } + }); } - protected getStartupIndicator(host: HTMLElement): HTMLElement | undefined { + protected override getStartupIndicator( + host: HTMLElement + ): HTMLElement | undefined { let startupElement = this.doGetStartupIndicator(host, 'old-theia-preload'); // https://github.com/eclipse-theia/theia/pull/10761#issuecomment-1131476318 if (!startupElement) { startupElement = this.doGetStartupIndicator(host, 'theia-preload'); // We show the new Theia spinner in dev mode. diff --git a/arduino-ide-extension/src/browser/theia/core/json-schema-store.ts b/arduino-ide-extension/src/browser/theia/core/json-schema-store.ts new file mode 100644 index 000000000..8d9f96313 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/core/json-schema-store.ts @@ -0,0 +1,11 @@ +import { injectable } from '@theia/core/shared/inversify'; +import { DefaultJsonSchemaContribution as TheiaDefaultJsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store'; + +@injectable() +export class DefaultJsonSchemaContribution extends TheiaDefaultJsonSchemaContribution { + override async registerSchemas(): Promise { + // NOOP + // Do not fetch the https://www.schemastore.org/api/json/catalog.json on every single browser window load. + // If the schemas are required in the future, we should fetch the `catalog.json` on build time and load it. + } +} diff --git a/arduino-ide-extension/src/browser/theia/core/keybindings.ts b/arduino-ide-extension/src/browser/theia/core/keybindings.ts deleted file mode 100644 index b6d55f564..000000000 --- a/arduino-ide-extension/src/browser/theia/core/keybindings.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import { Command } from '@theia/core/lib/common/command'; -import { Keybinding } from '@theia/core/lib/common/keybinding'; -import { - KeybindingRegistry as TheiaKeybindingRegistry, - KeybindingScope, -} from '@theia/core/lib/browser/keybinding'; - -@injectable() -export class KeybindingRegistry extends TheiaKeybindingRegistry { - // https://github.com/eclipse-theia/theia/issues/8209 - unregisterKeybinding(key: string): void; - unregisterKeybinding(keybinding: Keybinding): void; - unregisterKeybinding(command: Command): void; - unregisterKeybinding(arg: string | Keybinding | Command): void { - const keymap = this.keymaps[KeybindingScope.DEFAULT]; - const filter = Command.is(arg) - ? ({ command }: Keybinding) => command === arg.id - : ({ keybinding }: Keybinding) => - Keybinding.is(arg) - ? keybinding === arg.keybinding - : keybinding === arg; - for (const binding of keymap.filter(filter)) { - const idx = keymap.indexOf(binding); - if (idx !== -1) { - keymap.splice(idx, 1); - } - } - } -} diff --git a/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts b/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts deleted file mode 100644 index 510a1944f..000000000 --- a/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; -import { ShellLayoutRestorer as TheiaShellLayoutRestorer } from '@theia/core/lib/browser/shell/shell-layout-restorer'; - -@injectable() -export class ShellLayoutRestorer extends TheiaShellLayoutRestorer { - // Workaround for https://github.com/eclipse-theia/theia/issues/6579. - async storeLayoutAsync(app: FrontendApplication): Promise { - if (this.shouldStoreLayout) { - try { - this.logger.info('>>> Storing the layout...'); - const layoutData = app.shell.getLayoutData(); - const serializedLayoutData = this.deflate(layoutData); - await this.storageService.setData( - this.storageKey, - serializedLayoutData - ); - this.logger.info('<<< The layout has been successfully stored.'); - } catch (error) { - await this.storageService.setData(this.storageKey, undefined); - this.logger.error('Error during serialization of layout data', error); - } - } - } - - async restoreLayout(app: FrontendApplication): Promise { - this.logger.info('>>> Restoring the layout state...'); - const serializedLayoutData = await this.storageService.getData( - this.storageKey - ); - if (serializedLayoutData === undefined) { - this.logger.info('<<< Nothing to restore.'); - return false; - } - - const layoutData = await this.inflate(serializedLayoutData); - // workaround to remove duplicated tabs - const filesUri: string[] = []; - if ((layoutData as any)?.mainPanel?.main?.widgets) { - (layoutData as any).mainPanel.main.widgets = ( - layoutData as any - ).mainPanel.main.widgets.filter((widget: any) => { - const uri = widget.getResourceUri().toString(); - if (filesUri.includes(uri)) { - return false; - } - filesUri.push(uri); - return true; - }); - } - - await app.shell.setLayoutData(layoutData); - this.logger.info('<<< The layout has been successfully restored.'); - return true; - } -} diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts b/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts index ecf9199d0..ea1f29eed 100644 --- a/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts +++ b/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts @@ -27,7 +27,7 @@ export class TabBarDecoratorService extends TheiaTabBarDecoratorService { ); } - getDecorations(title: Title): WidgetDecoration.Data[] { + override getDecorations(title: Title): WidgetDecoration.Data[] { if (title.owner instanceof EditorWidget) { const editor = title.owner.editor; if (this.dataDirUri && this.dataDirUri.isEqualOrParent(editor.uri)) { diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx b/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx index a57b658ba..42e086d2b 100644 --- a/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx +++ b/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx @@ -12,7 +12,7 @@ export class TabBarToolbar extends TheiaTabBarToolbar { * Copied over from Theia. Added an ID to the parent of the toolbar item (`--container`). * CSS3 does not support parent selectors but we want to style the parent of the toolbar item. */ - protected renderItem(item: TabBarToolbarItem): React.ReactNode { + protected override renderItem(item: TabBarToolbarItem): React.ReactNode { let innerText = ''; const classNames = []; if (item.text) { diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bars.ts b/arduino-ide-extension/src/browser/theia/core/tab-bars.ts index 03a518c70..c6adbc236 100644 --- a/arduino-ide-extension/src/browser/theia/core/tab-bars.ts +++ b/arduino-ide-extension/src/browser/theia/core/tab-bars.ts @@ -3,7 +3,7 @@ import { Saveable } from '@theia/core/lib/browser/saveable'; import { TabBarRenderer as TheiaTabBarRenderer } from '@theia/core/lib/browser/shell/tab-bars'; export class TabBarRenderer extends TheiaTabBarRenderer { - createTabClass(data: TabBar.IRenderData): string { + override createTabClass(data: TabBar.IRenderData): string { let className = super.createTabClass(data); if (!data.title.closable && Saveable.isDirty(data.title.owner)) { className += ' p-mod-closable'; diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts index dc3be2836..0059f433c 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts @@ -7,7 +7,10 @@ import { DebugConfiguration } from '@theia/debug/lib/common/debug-common'; import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model'; import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { SketchesService } from '../../../common/protocol'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { DebugConfigurationModel } from './debug-configuration-model'; import { FileOperationError, @@ -36,7 +39,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager { } @postConstruct() - protected async init(): Promise { + protected override async init(): Promise { super.init(); this.appStateService.reachedState('ready').then(async () => { const tempContent = await this.getTempLaunchJsonContent(); @@ -73,7 +76,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager { }); } - protected updateModels = debounce(async () => { + protected override updateModels = debounce(async () => { await this.appStateService.reachedState('ready'); const roots = await this.workspaceService.roots; const toDelete = new Set(this.models.keys()); @@ -113,7 +116,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager { (TheiaDebugConfigurationModel.JsonContent & { uri: URI }) | URI | undefined > { const sketch = await this.sketchesServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return undefined; } const uri = await this.sketchesService.getIdeTempFolderUri(sketch); diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts index f1ccf01d1..225a003c1 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts @@ -6,8 +6,8 @@ import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/ export class DebugConfigurationModel extends TheiaDebugConfigurationModel { constructor( - readonly workspaceFolderUri: string, - protected readonly preferences: PreferenceService, + override readonly workspaceFolderUri: string, + protected override readonly preferences: PreferenceService, protected readonly config: DebugConfiguration[], protected configUri: URI | undefined, protected readonly onConfigDidChange: Event @@ -25,7 +25,7 @@ export class DebugConfigurationModel extends TheiaDebugConfigurationModel { this.reconcile(); } - protected parseConfigurations(): TheiaDebugConfigurationModel.JsonContent { + protected override parseConfigurations(): TheiaDebugConfigurationModel.JsonContent { return { uri: this.configUri, configurations: this.config, diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts b/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts index 1060b112c..00e351ece 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts @@ -13,7 +13,7 @@ export class DebugFrontendApplicationContribution extends TheiaDebugFrontendAppl this.options.defaultWidgetOptions.rank = 4; } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); unregisterSubmenu(DebugMenus.DEBUG, registry); } diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts b/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts deleted file mode 100644 index 14c5a3d9b..000000000 --- a/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import { - ExpressionItem, - DebugVariable, -} from '@theia/debug/lib/browser/console/debug-console-items'; -import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source'; - -// TODO: remove after https://github.com/eclipse-theia/theia/pull/9256/. -@injectable() -export class DebugHoverSource extends TheiaDebugHoverSource { - async evaluate2( - expression: string - ): Promise { - const evaluated = await this.doEvaluate(expression); - const elements = evaluated && (await evaluated.getElements()); - this._expression = evaluated; - this.elements = elements ? [...elements] : []; - this.fireDidChange(); - return evaluated; - } -} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts b/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts deleted file mode 100644 index 437555bdb..000000000 --- a/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { injectable, interfaces, Container } from '@theia/core/shared/inversify'; -import { Widget } from '@theia/core/shared/@phosphor/widgets'; -import { SourceTreeWidget } from '@theia/core/lib/browser/source-tree'; -import { DisposableCollection } from '@theia/core/lib/common/disposable'; -import { DebugEditor } from '@theia/debug/lib/browser/editor/debug-editor'; -import { DebugVariable } from '@theia/debug/lib/browser/console/debug-console-items'; -import { DebugExpressionProvider } from '@theia/debug/lib/browser/editor/debug-expression-provider'; -import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source'; -import { - DebugHoverWidget as TheiaDebugHoverWidget, - ShowDebugHoverOptions, -} from '@theia/debug/lib/browser/editor/debug-hover-widget'; -import { DebugHoverSource } from './debug-hover-source'; - -export function createDebugHoverWidgetContainer( - parent: interfaces.Container, - editor: DebugEditor -): Container { - const child = SourceTreeWidget.createContainer(parent, { - virtualized: false, - }); - child.bind(DebugEditor).toConstantValue(editor); - child.bind(TheiaDebugHoverSource).toSelf(); - child.bind(DebugHoverSource).toSelf(); - child.rebind(TheiaDebugHoverSource).to(DebugHoverSource); - child.unbind(SourceTreeWidget); - child.bind(DebugExpressionProvider).toSelf(); - child.bind(TheiaDebugHoverWidget).toSelf(); - child.bind(DebugHoverWidget).toSelf(); - child.rebind(TheiaDebugHoverWidget).to(DebugHoverWidget); - return child; -} - -// TODO: remove patch after https://github.com/eclipse-theia/theia/pull/9256/ -@injectable() -export class DebugHoverWidget extends TheiaDebugHoverWidget { - protected async doShow( - options: ShowDebugHoverOptions | undefined = this.options - ): Promise { - if (!this.isEditorFrame()) { - this.hide(); - return; - } - if (!options) { - this.hide(); - return; - } - if (this.options && this.options.selection.equalsRange(options.selection)) { - return; - } - if (!this.isAttached) { - Widget.attach(this, this.contentNode); - } - - this.options = options; - const matchingExpression = this.expressionProvider.get( - this.editor.getControl().getModel()!, - options.selection - ); - if (!matchingExpression) { - this.hide(); - return; - } - const toFocus = new DisposableCollection(); - if (this.options.focus === true) { - toFocus.push( - this.model.onNodeRefreshed(() => { - toFocus.dispose(); - this.activate(); - }) - ); - } - const expression = await (this.hoverSource as DebugHoverSource).evaluate2( - matchingExpression - ); - if (!expression || !expression.value) { - toFocus.dispose(); - this.hide(); - return; - } - - this.contentNode.hidden = false; - ['number', 'boolean', 'string'].forEach((token) => - this.titleNode.classList.remove(token) - ); - this.domNode.classList.remove('complex-value'); - if (expression.hasElements) { - this.domNode.classList.add('complex-value'); - } else { - this.contentNode.hidden = true; - if ( - expression.type === 'number' || - expression.type === 'boolean' || - expression.type === 'string' - ) { - this.titleNode.classList.add(expression.type); - } else if (!isNaN(+expression.value)) { - this.titleNode.classList.add('number'); - } else if (DebugVariable.booleanRegex.test(expression.value)) { - this.titleNode.classList.add('boolean'); - } else if (DebugVariable.stringRegex.test(expression.value)) { - this.titleNode.classList.add('string'); - } - } - - // super.show(); // Here we cannot call `super.show()` but have to call `show` on the `Widget` prototype. - Widget.prototype.show.call(this); - await new Promise((resolve) => { - setTimeout( - () => - window.requestAnimationFrame(() => { - this.editor.getControl().layoutContentWidget(this); - resolve(); - }), - 0 - ); - }); - } -} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts b/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts index 16345af8c..6eb2ebdeb 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts @@ -7,7 +7,7 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class DebugSessionManager extends TheiaDebugSessionManager { - async start(options: DebugSessionOptions): Promise { + override async start(options: DebugSessionOptions): Promise { return this.progressService.withProgress( nls.localize('theia/debug/start', 'Start...'), 'debug', @@ -76,7 +76,7 @@ export class DebugSessionManager extends TheiaDebugSessionManager { } ); } - async terminateSession(session?: DebugSession): Promise { + override async terminateSession(session?: DebugSession): Promise { if (!session) { this.updateCurrentSession(this._currentSession); session = this._currentSession; diff --git a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts index ab479cf13..b93131c7f 100644 --- a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts +++ b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts @@ -8,7 +8,7 @@ import { @injectable() export abstract class AbstractDialog extends TheiaAbstractDialog { - constructor(@inject(DialogProps) protected readonly props: DialogProps) { + constructor(@inject(DialogProps) protected override readonly props: DialogProps) { super(props); this.closeCrossNode.classList.remove(...codiconArray('close')); diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-command.ts b/arduino-ide-extension/src/browser/theia/editor/editor-command.ts index 5b98de336..9f4a3ffc5 100644 --- a/arduino-ide-extension/src/browser/theia/editor/editor-command.ts +++ b/arduino-ide-extension/src/browser/theia/editor/editor-command.ts @@ -4,7 +4,7 @@ import { EditorCommandContribution as TheiaEditorCommandContribution } from '@th @injectable() export class EditorCommandContribution extends TheiaEditorCommandContribution { @postConstruct() - protected init(): void { + protected override init(): void { // Workaround for https://github.com/eclipse-theia/theia/issues/8722. this.editorPreferences.onPreferenceChanged( ({ preferenceName, newValue, oldValue }) => { diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts b/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts index 6c4f7aa8c..245fdca3b 100644 --- a/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts +++ b/arduino-ide-extension/src/browser/theia/editor/editor-manager.ts @@ -1,9 +1,7 @@ import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser/editor-manager'; export class EditorManager extends TheiaEditorManager { - - protected getOrCreateCounterForUri(): number { + protected override getOrCreateCounterForUri(): number { return 0; } - } diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts b/arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts new file mode 100644 index 000000000..ebd90f1bf --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts @@ -0,0 +1,11 @@ +import { injectable } from '@theia/core/shared/inversify'; +import { EditorNavigationContribution as TheiaEditorNavigationContribution } from '@theia/editor/lib/browser/editor-navigation-contribution'; + +@injectable() +export class EditorNavigationContribution extends TheiaEditorNavigationContribution { + override async onStart(): Promise { + // No await. + // Restore the navigation history asynchronously. + super.onStart(); + } +} diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts b/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts index be497e688..dd00e1879 100644 --- a/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts +++ b/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts @@ -3,7 +3,10 @@ import URI from '@theia/core/lib/common/uri'; import { EditorWidget } from '@theia/editor/lib/browser'; import { LabelProvider } from '@theia/core/lib/browser'; import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { SketchesService, Sketch } from '../../../common/protocol'; import { nls } from '@theia/core/lib/common'; @@ -16,9 +19,9 @@ export class EditorWidgetFactory extends TheiaEditorWidgetFactory { protected readonly sketchesServiceClient: SketchesServiceClientImpl; @inject(LabelProvider) - protected readonly labelProvider: LabelProvider; + protected override readonly labelProvider: LabelProvider; - protected async createEditor(uri: URI): Promise { + protected override async createEditor(uri: URI): Promise { const widget = await super.createEditor(uri); return this.maybeUpdateCaption(widget); } @@ -28,7 +31,7 @@ export class EditorWidgetFactory extends TheiaEditorWidgetFactory { ): Promise { const sketch = await this.sketchesServiceClient.currentSketch(); const { uri } = widget.editor; - if (sketch && Sketch.isInSketch(uri, sketch)) { + if (CurrentSketch.isValid(sketch) && Sketch.isInSketch(uri, sketch)) { const isTemp = await this.sketchesService.isTemp(sketch); if (isTemp) { widget.title.caption = nls.localize( diff --git a/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts index d993c70e6..13914d025 100644 --- a/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts @@ -9,7 +9,7 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class KeymapsFrontendContribution extends TheiaKeymapsFrontendContribution { - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, { commandId: KeymapsCommands.OPEN_KEYMAPS.id, label: nls.localize( diff --git a/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts b/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts index c837f5b73..d10f6adba 100644 --- a/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts @@ -6,15 +6,15 @@ import { ProblemContribution as TheiaProblemContribution } from '@theia/markers/ @injectable() export class ProblemContribution extends TheiaProblemContribution { - async initializeLayout(app: FrontendApplication): Promise { + override async initializeLayout(app: FrontendApplication): Promise { // NOOP } - protected setStatusBarElement(problemStat: ProblemStat): void { + protected override setStatusBarElement(problemStat: ProblemStat): void { // NOOP } - registerKeybindings(keybindings: KeybindingRegistry): void { + override registerKeybindings(keybindings: KeybindingRegistry): void { if (this.toggleCommand) { keybindings.registerKeybinding({ command: this.toggleCommand.id, diff --git a/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts b/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts index 3b3912464..0e830fd54 100644 --- a/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts +++ b/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts @@ -17,7 +17,7 @@ export class ProblemManager extends TheiaProblemManager { protected dataDirUri: URI | undefined; @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.configService .getConfiguration() @@ -27,7 +27,7 @@ export class ProblemManager extends TheiaProblemManager { ); } - setMarkers( + override setMarkers( uri: URI, owner: string, data: Diagnostic[] diff --git a/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx b/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx index 0e3bc4d52..b4b83bc77 100644 --- a/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx +++ b/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx @@ -7,7 +7,7 @@ import { codicon } from '@theia/core/lib/browser'; const PerfectScrollbar = require('react-perfect-scrollbar'); export class NotificationCenterComponent extends TheiaNotificationCenterComponent { - render(): React.ReactNode { + override render(): React.ReactNode { const empty = this.state.notifications.length === 0; const title = empty ? nls.localize( diff --git a/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx b/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx index 9eaca094c..18b967836 100644 --- a/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx +++ b/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx @@ -4,7 +4,7 @@ import { nls } from '@theia/core/lib/common'; import { codicon } from '@theia/core/lib/browser'; export class NotificationComponent extends TheiaNotificationComponent { - render(): React.ReactNode { + override render(): React.ReactNode { const { messageId, message, type, collapsed, expandable, source, actions } = this.props.notification; return ( diff --git a/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx b/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx index 6f46c8e49..393dd9b5a 100644 --- a/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx +++ b/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx @@ -3,7 +3,7 @@ import { NotificationComponent } from './notification-component'; import { NotificationToastsComponent as TheiaNotificationToastsComponent } from '@theia/messages/lib/browser/notification-toasts-component'; export class NotificationToastsComponent extends TheiaNotificationToastsComponent { - render(): React.ReactNode { + override render(): React.ReactNode { return (
{ diff --git a/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts b/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts index fa7ee0673..fca883087 100644 --- a/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts @@ -3,7 +3,7 @@ import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from @injectable() export class MonacoStatusBarContribution extends TheiaMonacoStatusBarContribution { - protected setConfigTabSizeWidget() {} + protected override setConfigTabSizeWidget() {} - protected setLineEndingWidget() {} + protected override setLineEndingWidget() {} } diff --git a/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts b/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts index a7a36adc0..7a8ece561 100644 --- a/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts +++ b/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts @@ -13,7 +13,7 @@ export class MonacoTextModelService extends TheiaMonacoTextModelService { @inject(SketchesServiceClientImpl) protected readonly sketchesServiceClient: SketchesServiceClientImpl; - protected async createModel(resource: Resource): Promise { + protected override async createModel(resource: Resource): Promise { const factory = this.factories .getContributions() .find(({ scheme }) => resource.uri.scheme === scheme); @@ -33,7 +33,7 @@ export class MonacoTextModelService extends TheiaMonacoTextModelService { // https://github.com/eclipse-theia/theia/pull/8491 class SilentMonacoEditorModel extends MonacoEditorModel { - protected trace(loggable: Loggable): void { + protected override trace(loggable: Loggable): void { if (this.logger) { this.logger.trace((log: Log) => loggable((message, ...params) => @@ -46,24 +46,24 @@ class SilentMonacoEditorModel extends MonacoEditorModel { class MaybeReadonlyMonacoEditorModel extends SilentMonacoEditorModel { constructor( - protected readonly resource: Resource, - protected readonly m2p: MonacoToProtocolConverter, - protected readonly p2m: ProtocolToMonacoConverter, - protected readonly logger?: ILogger, - protected readonly editorPreferences?: EditorPreferences, + protected override readonly resource: Resource, + protected override readonly m2p: MonacoToProtocolConverter, + protected override readonly p2m: ProtocolToMonacoConverter, + protected override readonly logger?: ILogger, + protected override readonly editorPreferences?: EditorPreferences, protected readonly _readOnly?: boolean ) { super(resource, m2p, p2m, logger, editorPreferences); } - get readOnly(): boolean { + override get readOnly(): boolean { if (typeof this._readOnly === 'boolean') { return this._readOnly; } return this.resource.saveContents === undefined; } - protected setDirty(dirty: boolean): void { + protected override setDirty(dirty: boolean): void { if (this._readOnly === true) { // NOOP return; diff --git a/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts b/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts index 86207b82a..23d46aaa1 100644 --- a/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts @@ -13,14 +13,14 @@ import { WorkspacePreferences } from '@theia/workspace/lib/browser/workspace-pre export class FileNavigatorContribution extends TheiaFileNavigatorContribution { constructor( @inject(FileNavigatorPreferences) - protected readonly fileNavigatorPreferences: FileNavigatorPreferences, - @inject(OpenerService) protected readonly openerService: OpenerService, + protected override readonly fileNavigatorPreferences: FileNavigatorPreferences, + @inject(OpenerService) protected override readonly openerService: OpenerService, @inject(FileNavigatorFilter) - protected readonly fileNavigatorFilter: FileNavigatorFilter, + protected override readonly fileNavigatorFilter: FileNavigatorFilter, @inject(WorkspaceService) - protected readonly workspaceService: WorkspaceService, + protected override readonly workspaceService: WorkspaceService, @inject(WorkspacePreferences) - protected readonly workspacePreferences: WorkspacePreferences + protected override readonly workspacePreferences: WorkspacePreferences ) { super( fileNavigatorPreferences, @@ -32,11 +32,11 @@ export class FileNavigatorContribution extends TheiaFileNavigatorContribution { this.options.defaultWidgetOptions.rank = 1; } - async initializeLayout(app: FrontendApplication): Promise { + override async initializeLayout(app: FrontendApplication): Promise { // NOOP } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { super.registerKeybindings(registry); [WorkspaceCommands.FILE_RENAME, WorkspaceCommands.FILE_DELETE].forEach( registry.unregisterKeybinding.bind(registry) diff --git a/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts b/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts index d046f1cfc..b5b791bc4 100644 --- a/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts +++ b/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts @@ -8,11 +8,11 @@ import { NavigatorTabBarDecorator as TheiaNavigatorTabBarDecorator } from '@thei */ @injectable() export class NavigatorTabBarDecorator extends TheiaNavigatorTabBarDecorator { - onStart(): void { + override onStart(): void { // NOOP } - decorate(): WidgetDecoration.Data[] { + override decorate(): WidgetDecoration.Data[] { // Does not decorate anything. return []; } diff --git a/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts b/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts index 6dc60aba5..c0232d395 100644 --- a/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts @@ -12,7 +12,7 @@ export class OutlineViewContribution extends TheiaOutlineViewContribution { }; } - async initializeLayout(app: FrontendApplication): Promise { + override async initializeLayout(app: FrontendApplication): Promise { // NOOP } } diff --git a/arduino-ide-extension/src/browser/theia/output/output-channel.ts b/arduino-ide-extension/src/browser/theia/output/output-channel.ts index 15b9daff3..b928dce06 100644 --- a/arduino-ide-extension/src/browser/theia/output/output-channel.ts +++ b/arduino-ide-extension/src/browser/theia/output/output-channel.ts @@ -11,7 +11,7 @@ import { @injectable() export class OutputChannelManager extends TheiaOutputChannelManager { - getChannel(name: string): TheiaOutputChannel { + override getChannel(name: string): TheiaOutputChannel { const existing = this.channels.get(name); if (existing) { return existing; @@ -43,7 +43,7 @@ export class OutputChannelManager extends TheiaOutputChannelManager { } export class OutputChannel extends TheiaOutputChannel { - dispose(): void { + override dispose(): void { super.dispose(); if ((this as any).disposed) { const textModifyQueue: PQueue = (this as any).textModifyQueue; diff --git a/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts b/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts index 91971e3b5..cf0e524a6 100644 --- a/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts @@ -8,7 +8,7 @@ import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@th @injectable() export class OutputToolbarContribution extends TheiaOutputToolbarContribution { - async registerToolbarItems(registry: TabBarToolbarRegistry): Promise { + override async registerToolbarItems(registry: TabBarToolbarRegistry): Promise { await super.registerToolbarItems(registry); // Why is it async? // It's a hack. Currently, it's not possible to unregister a toolbar contribution via API. ( diff --git a/arduino-ide-extension/src/browser/theia/output/output-widget.ts b/arduino-ide-extension/src/browser/theia/output/output-widget.ts index a975e6a5b..6d6b684d0 100644 --- a/arduino-ide-extension/src/browser/theia/output/output-widget.ts +++ b/arduino-ide-extension/src/browser/theia/output/output-widget.ts @@ -6,7 +6,7 @@ import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/out // Remove this module after ATL-222 and the Theia update. @injectable() export class OutputWidget extends TheiaOutputWidget { - protected onAfterShow(msg: Message): void { + protected override onAfterShow(msg: Message): void { super.onAfterShow(msg); this.onResize(Widget.ResizeMessage.UnknownSize); } diff --git a/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts b/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts index 09a79cf3f..1a5bdf12c 100644 --- a/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts +++ b/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts @@ -7,9 +7,9 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl } f @injectable() export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMainImpl { @inject(CommandService) - protected readonly commandService: CommandService; + protected override readonly commandService: CommandService; - $append( + override $append( name: string, text: string, pluginInfo: PluginInfo @@ -21,17 +21,17 @@ export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMai return Promise.resolve(); } - $clear(name: string): PromiseLike { + override $clear(name: string): PromiseLike { this.commandService.executeCommand(OutputCommands.CLEAR.id, { name }); return Promise.resolve(); } - $dispose(name: string): PromiseLike { + override $dispose(name: string): PromiseLike { this.commandService.executeCommand(OutputCommands.DISPOSE.id, { name }); return Promise.resolve(); } - async $reveal(name: string, preserveFocus: boolean): Promise { + override async $reveal(name: string, preserveFocus: boolean): Promise { const options = { preserveFocus }; this.commandService.executeCommand(OutputCommands.SHOW.id, { name, @@ -39,7 +39,7 @@ export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMai }); } - $close(name: string): PromiseLike { + override $close(name: string): PromiseLike { this.commandService.executeCommand(OutputCommands.HIDE.id, { name }); return Promise.resolve(); } diff --git a/arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts b/arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts new file mode 100644 index 000000000..0dcb7af66 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts @@ -0,0 +1,17 @@ +import { CompositeTreeNode } from '@theia/core/lib/browser/tree/tree'; +import { injectable } from '@theia/core/shared/inversify'; +import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator'; + +@injectable() +export class PreferenceTreeGenerator extends TheiaPreferenceTreeGenerator { + protected override async init(): Promise { + // The IDE2 does not use the default Theia preferences UI. + // There is no need to create and keep the the tree model synchronized when there is no UI for it. + } + + // Just returns with the empty root. + override generateTree(): CompositeTreeNode { + this._root = this.createRootNode(); + return this._root; + } +} diff --git a/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts b/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts index 4fdd770b3..4aaf87e97 100644 --- a/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts @@ -6,7 +6,7 @@ import { PreferencesContribution as TheiaPreferencesContribution } from '@theia/ @injectable() export class PreferencesContribution extends TheiaPreferencesContribution { - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); // The settings group: preferences, CLI config is not part of the `File` menu on macOS. // On Windows and Linux, we rebind it to `Preferences...`. It is safe to remove here. @@ -16,7 +16,7 @@ export class PreferencesContribution extends TheiaPreferencesContribution { ); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.unregisterKeybinding(CommonCommands.OPEN_PREFERENCES.id); } } diff --git a/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts b/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts index 47cad0ea9..cc759db5b 100644 --- a/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts @@ -4,11 +4,11 @@ import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar'; @injectable() export class ScmContribution extends TheiaScmContribution { - async initializeLayout(): Promise { + override async initializeLayout(): Promise { // NOOP } - protected setStatusBarEntry(id: string, entry: StatusBarEntry): void { + protected override setStatusBarEntry(id: string, entry: StatusBarEntry): void { // NOOP } } diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts index 81d35d0be..34afd4d22 100644 --- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts +++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts @@ -8,7 +8,7 @@ import { @injectable() export class SearchInWorkspaceFactory extends TheiaSearchInWorkspaceFactory { - async createWidget(): Promise { + override async createWidget(): Promise { const viewContainer = await super.createWidget(); viewContainer.setTitleOptions({ ...SEARCH_VIEW_CONTAINER_TITLE_OPTIONS, diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts index cda7ebc23..2204848fa 100644 --- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts @@ -13,12 +13,12 @@ export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspac this.options.defaultWidgetOptions.rank = 5; } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); registry.unregisterMenuAction(SearchInWorkspaceCommands.OPEN_SIW_WIDGET); } - registerKeybindings(keybindings: KeybindingRegistry): void { + override registerKeybindings(keybindings: KeybindingRegistry): void { super.registerKeybindings(keybindings); keybindings.unregisterKeybinding(SearchInWorkspaceCommands.OPEN_SIW_WIDGET); } diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts index 5362be736..e831cd402 100644 --- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts +++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts @@ -11,7 +11,7 @@ import { MEMORY_TEXT } from '@theia/core/lib/common/resource'; */ @injectable() export class SearchInWorkspaceResultTreeWidget extends TheiaSearchInWorkspaceResultTreeWidget { - protected async createReplacePreview( + protected override async createReplacePreview( node: SearchInWorkspaceFileNode ): Promise { const fileUri = new URI(node.fileUri).withScheme('file'); diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx index 58c35f5f6..cae633024 100644 --- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx +++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx @@ -9,12 +9,12 @@ import { SearchInWorkspaceWidget as TheiaSearchInWorkspaceWidget } from '@theia/ @injectable() export class SearchInWorkspaceWidget extends TheiaSearchInWorkspaceWidget { @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.title.iconClass = 'fa fa-arduino-search'; } - protected renderGlobField(kind: 'include' | 'exclude'): React.ReactNode { + protected override renderGlobField(kind: 'include' | 'exclude'): React.ReactNode { const currentValue = this.searchInWorkspaceOptions[kind]; const value = (currentValue && currentValue.join(', ')) || ''; return ( diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts index 34b64ab43..5b864732b 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts @@ -12,7 +12,10 @@ import { } from '@theia/workspace/lib/browser/workspace-commands'; import { Sketch, SketchesService } from '../../../common/protocol'; import { WorkspaceInputDialog } from './workspace-input-dialog'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { SaveAsSketch } from '../../contributions/save-as-sketch'; import { SingleTextInputDialog } from '@theia/core/lib/browser'; import { nls } from '@theia/core/lib/common'; @@ -28,7 +31,7 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut @inject(SketchesService) protected readonly sketchService: SketchesService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { super.registerCommands(registry); registry.unregisterCommand(WorkspaceCommands.NEW_FILE); registry.registerCommand( @@ -75,7 +78,7 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut } } - protected async validateFileName( + protected override async validateFileName( name: string, parent: FileStat, recursive = false @@ -129,15 +132,15 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut return; } const sketch = await this.sketchesServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } // file belongs to another sketch, do not allow rename - const parentsketch = await this.sketchService.getSketchFolder( + const parentSketch = await this.sketchService.getSketchFolder( uri.toString() ); - if (parentsketch && parentsketch.uri !== sketch.uri) { + if (parentSketch && parentSketch.uri !== sketch.uri) { return; } diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts index 47f93232e..e3461c379 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts @@ -2,7 +2,10 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import * as remote from '@theia/core/electron-shared/@electron/remote'; import URI from '@theia/core/lib/common/uri'; import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { nls } from '@theia/core/lib/common'; @injectable() @@ -10,9 +13,9 @@ export class WorkspaceDeleteHandler extends TheiaWorkspaceDeleteHandler { @inject(SketchesServiceClientImpl) protected readonly sketchesServiceClient: SketchesServiceClientImpl; - async execute(uris: URI[]): Promise { + override async execute(uris: URI[]): Promise { const sketch = await this.sketchesServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } // Deleting the main sketch file. diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts index 63f9eb034..4574fedfc 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts @@ -10,7 +10,7 @@ import { WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution } f @injectable() export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { super.registerCommands(registry); // TODO: instead of blacklisting commands to remove, it would be more robust to whitelist the ones we want to keep const commands = new Set(registry.commands); @@ -28,9 +28,9 @@ export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContrib .forEach(registry.unregisterCommand.bind(registry)); } - registerMenus(_: MenuModelRegistry): void {} + override registerMenus(_: MenuModelRegistry): void {} - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { super.registerKeybindings(registry); [ WorkspaceCommands.NEW_FILE, @@ -44,7 +44,7 @@ export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContrib @injectable() export class ArduinoFileMenuContribution extends FileMenuContribution { - registerMenus(_: MenuModelRegistry): void { + override registerMenus(_: MenuModelRegistry): void { // NOOP } } diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts index af5a99dbe..d70d7e27d 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts @@ -13,8 +13,8 @@ export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog { constructor( @inject(WorkspaceInputDialogProps) - protected readonly props: WorkspaceInputDialogProps, - @inject(LabelProvider) protected readonly labelProvider: LabelProvider + protected override readonly props: WorkspaceInputDialogProps, + @inject(LabelProvider) protected override readonly labelProvider: LabelProvider ) { super(props, labelProvider); this.appendCloseButton( @@ -22,18 +22,18 @@ export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog { ); } - protected appendParentPath(): void { + protected override appendParentPath(): void { // NOOP } - isValid(value: string, mode: DialogMode): MaybePromise { + override isValid(value: string, mode: DialogMode): MaybePromise { if (value !== '') { this.wasTouched = true; } return super.isValid(value, mode); } - protected setErrorMessage(error: DialogError): void { + protected override setErrorMessage(error: DialogError): void { if (this.acceptButton) { this.acceptButton.disabled = !DialogError.getResult(error); } diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts index 958ae0a6f..e49da551b 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts @@ -7,19 +7,16 @@ import { MessageService } from '@theia/core/lib/common/message-service'; import { ApplicationServer } from '@theia/core/lib/common/application-protocol'; import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; import { FocusTracker, Widget } from '@theia/core/lib/browser'; +import { DEFAULT_WINDOW_HASH } from '@theia/core/lib/common/window'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { ConfigService } from '../../../common/protocol/config-service'; import { SketchesService, Sketch, - SketchContainer, } from '../../../common/protocol/sketches-service'; -import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { BoardsConfig } from '../../boards/boards-config'; -import { nls } from '@theia/core/lib/common'; -import { URI as VSCodeUri } from '@theia/core/shared/vscode-uri'; @injectable() export class WorkspaceService extends TheiaWorkspaceService { @@ -30,10 +27,10 @@ export class WorkspaceService extends TheiaWorkspaceService { protected readonly configService: ConfigService; @inject(LabelProvider) - protected readonly labelProvider: LabelProvider; + protected override readonly labelProvider: LabelProvider; @inject(MessageService) - protected readonly messageService: MessageService; + protected override readonly messageService: MessageService; @inject(ApplicationServer) protected readonly applicationServer: ApplicationServer; @@ -44,12 +41,9 @@ export class WorkspaceService extends TheiaWorkspaceService { @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; - private application: FrontendApplication; - private workspaceUri?: Promise; private version?: string; async onStart(application: FrontendApplication): Promise { - this.application = application; const info = await this.applicationServer.getApplicationInfo(); this.version = info?.version; application.shell.onDidChangeCurrentWidget( @@ -61,53 +55,34 @@ export class WorkspaceService extends TheiaWorkspaceService { this.onCurrentWidgetChange({ newValue, oldValue: null }); } - protected getDefaultWorkspaceUri(): Promise { - if (this.workspaceUri) { - // Avoid creating a new sketch twice - return this.workspaceUri; + // Was copied from the Theia implementation. + // Unlike the default behavior, IDE2 does not check the existence of the workspace before open. + protected override async doGetDefaultWorkspaceUri(): Promise< + string | undefined + > { + // If an empty window is explicitly requested do not restore a previous workspace. + // Note: `window.location.hash` includes leading "#" if non-empty. + if (window.location.hash === `#${DEFAULT_WINDOW_HASH}`) { + window.location.hash = ''; + return undefined; + } + + // Prefer the workspace path specified as the URL fragment, if present. + if (window.location.hash.length > 1) { + // Remove the leading # and decode the URI. + const wpPath = decodeURI(window.location.hash.substring(1)); + const workspaceUri = new URI().withPath(wpPath).withScheme('file'); + // ### Customization! Here, we do no check if the workspace exists. + return workspaceUri.toString(); + } else { + // Else, ask the server for its suggested workspace (usually the one + // specified on the CLI, or the most recent). + // ### Customization! the default workspace server will create a new sketch and will return with its URI if no recent workspaces are available. + return this.server.getMostRecentlyUsedWorkspace(); } - this.workspaceUri = (async () => { - try { - const hash = window.location.hash; - const [recentWorkspacesPaths, recentSketches] = await Promise.all([ - this.server.getRecentWorkspaces(), - this.sketchService - .getSketches({}) - .then((container) => - SketchContainer.toArray(container).map((s) => s.uri) - ), - ]); - // On Dindows, `getRecentWorkspaces` returns only file paths, not URIs as expected by the `isValid` method. - const recentWorkspaces = recentWorkspacesPaths.map((e) => - VSCodeUri.file(e).toString() - ); - const toOpen = await new ArduinoWorkspaceRootResolver({ - isValid: this.isValid.bind(this), - }).resolve({ hash, recentWorkspaces, recentSketches }); - if (toOpen) { - const { uri } = toOpen; - await this.server.setMostRecentlyUsedWorkspace(uri); - return toOpen.uri; - } - return (await this.sketchService.createNewSketch()).uri; - } catch (err) { - this.appStateService - .reachedState('ready') - .then(() => this.application.shell.update()); - this.logger.fatal(`Failed to determine the sketch directory: ${err}`); - this.messageService.error( - nls.localize( - 'theia/workspace/sketchDirectoryError', - 'There was an error creating the sketch directory. See the log for more details. The application will probably not work as expected.' - ) - ); - return super.getDefaultWorkspaceUri(); - } - })(); - return this.workspaceUri; } - protected openNewWindow(workspacePath: string): void { + protected override openNewWindow(workspacePath: string): void { const { boardsConfig } = this.boardsServiceProvider; const url = BoardsConfig.Config.setConfig( boardsConfig, @@ -117,14 +92,6 @@ export class WorkspaceService extends TheiaWorkspaceService { this.windowService.openNewWindow(url.toString()); } - private async isValid(uri: string): Promise { - const exists = await this.fileService.exists(new URI(uri)); - if (!exists) { - return false; - } - return this.sketchService.isSketchFolder(uri); - } - protected onCurrentWidgetChange({ newValue, }: FocusTracker.IChangedArgs): void { @@ -146,7 +113,7 @@ export class WorkspaceService extends TheiaWorkspaceService { } } - protected formatTitle(title?: string): string { + protected override formatTitle(title?: string): string { const version = this.version ? ` ${this.version}` : ''; const name = `${this.applicationName} ${version}`; return title ? `${title} | ${name}` : name; diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts index a91949ba5..1ca0b7b85 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts @@ -1,8 +1,15 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; import { WorkspaceVariableContribution as TheiaWorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution'; import { Sketch } from '../../../common/protocol'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; @injectable() export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContribution { @@ -12,14 +19,15 @@ export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContrib protected currentSketch?: Sketch; @postConstruct() - protected init(): void { - this.sketchesServiceClient - .currentSketch() - .then() - .then((sketch) => (this.currentSketch = sketch)); + protected override init(): void { + this.sketchesServiceClient.currentSketch().then((sketch) => { + if (CurrentSketch.isValid(sketch)) { + this.currentSketch = sketch; + } + }); } - getResourceUri(): URI | undefined { + override getResourceUri(): URI | undefined { const resourceUri = super.getResourceUri(); // https://github.com/arduino/arduino-ide/issues/46 // `currentWidget` can be an editor representing a file outside of the workspace. The current sketch should be a fallback. diff --git a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts index 9180be90e..09c125e9a 100644 --- a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts +++ b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts @@ -19,7 +19,7 @@ export class ArduinoToolbarContainer extends Widget { this.toolbars = toolbars; } - onAfterAttach(msg: Message) { + override onAfterAttach(msg: Message) { for (const toolbar of this.toolbars) { Widget.attach(toolbar, this.node); } diff --git a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx index d4abffbb5..c1a656206 100644 --- a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx +++ b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx @@ -71,7 +71,7 @@ export class ArduinoToolbarComponent extends React.Component< ); }; - render(): React.ReactNode { + override render(): React.ReactNode { const tooltip = (
{this.state.tooltip} diff --git a/arduino-ide-extension/src/browser/widgets/arduino-select.tsx b/arduino-ide-extension/src/browser/widgets/arduino-select.tsx index bccc4cef6..4ee84e82a 100644 --- a/arduino-ide-extension/src/browser/widgets/arduino-select.tsx +++ b/arduino-ide-extension/src/browser/widgets/arduino-select.tsx @@ -9,7 +9,7 @@ export class ArduinoSelect extends Select { super(props); } - render(): React.ReactNode { + override render(): React.ReactNode { const controlHeight = 27; // from `monitor.css` -> `.serial-monitor-container .head` (`height: 27px;`) const styles: Styles = { control: (styles) => ({ diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx index cc10e34b1..9661c02fe 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx @@ -43,7 +43,7 @@ export class CloudSketchbookCompositeWidget extends BaseWidget { return this.cloudSketchbookTreeWidget; } - protected onAfterAttach(message: Message): void { + protected override onAfterAttach(message: Message): void { super.onAfterAttach(message); Widget.attach(this.cloudSketchbookTreeWidget, this.compositeNode); ReactDOM.render( @@ -58,7 +58,7 @@ export class CloudSketchbookCompositeWidget extends BaseWidget { ); } - protected onResize(message: Widget.ResizeMessage): void { + protected override onResize(message: Widget.ResizeMessage): void { super.onResize(message); MessageLoop.sendMessage( this.cloudSketchbookTreeWidget, diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts index 163a45131..85e703554 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts @@ -23,7 +23,7 @@ import { } from '@theia/core/lib/browser/preferences/preference-service'; import { ArduinoMenus, PlaceholderMenuNode } from '../../menu/arduino-menus'; import { SketchbookCommands } from '../sketchbook/sketchbook-commands'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; import { Contribution } from '../../contributions/contribution'; import { ArduinoPreferences } from '../../arduino-preferences'; import { MainMenuManager } from '../../../common/main-menu-manager'; @@ -149,7 +149,7 @@ export class CloudSketchbookContribution extends Contribution { protected readonly toDisposeBeforeNewContextMenu = new DisposableCollection(); - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, { commandId: CloudSketchbookCommands.TOGGLE_CLOUD_SKETCHBOOK.id, label: CloudSketchbookCommands.TOGGLE_CLOUD_SKETCHBOOK.label, @@ -157,7 +157,7 @@ export class CloudSketchbookContribution extends Contribution { }); } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(CloudSketchbookCommands.TOGGLE_CLOUD_SKETCHBOOK, { execute: () => { this.preferenceService.set( @@ -279,7 +279,8 @@ export class CloudSketchbookContribution extends Contribution { // disable the "open sketch" command for the current sketch and for those not in sync if ( !CloudSketchbookTree.CloudSketchTreeNode.isSynced(arg.node) || - (currentSketch && currentSketch.uri === arg.node.uri.toString()) + (CurrentSketch.isValid(currentSketch) && + currentSketch.uri === arg.node.uri.toString()) ) { const placeholder = new PlaceholderMenuNode( SKETCHBOOKSYNC__CONTEXT__MAIN_GROUP, diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts index 708e2ec1b..d76c7497f 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts @@ -53,7 +53,7 @@ export function sketchesToFileStats(sketches: Create.Sketch[]): FileStat[] { @injectable() export class CloudSketchbookTreeModel extends SketchbookTreeModel { @inject(FileService) - protected readonly fileService: FileService; + protected override readonly fileService: FileService; @inject(AuthenticationClientService) protected readonly authenticationService: AuthenticationClientService; @@ -65,7 +65,7 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel { protected readonly cloudSketchbookTree: CloudSketchbookTree; @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; + protected override readonly arduinoPreferences: ArduinoPreferences; @inject(LocalCacheFsProvider) protected readonly localCacheFsProvider: LocalCacheFsProvider; @@ -74,14 +74,14 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel { protected readonly sketchCache: SketchCache; @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.toDispose.push( this.authenticationService.onSessionDidChange(() => this.updateRoot()) ); } - async createRoot(): Promise { + override async createRoot(): Promise { const { session } = this.authenticationService; if (!session) { this.tree.root = undefined; @@ -108,7 +108,7 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel { return this.tree as CloudSketchbookTree; } - protected recursivelyFindSketchRoot(node: TreeNode): any { + protected override recursivelyFindSketchRoot(node: TreeNode): any { if (node && CloudSketchbookTree.CloudSketchDirNode.is(node)) { return node; } @@ -121,7 +121,7 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel { return false; } - async revealFile(uri: URI): Promise { + override async revealFile(uri: URI): Promise { // we use remote uris as keys for the tree // convert local URIs const remoteuri = this.localCacheFsProvider.from(uri); diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx index 523700aed..043dfea86 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx @@ -28,18 +28,18 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { protected readonly cloudSketchbookTree: CloudSketchbookTree; @postConstruct() - protected async init(): Promise { + protected override async init(): Promise { await super.init(); this.addClass('tree-container'); // Adds `height: 100%` to the tree. Otherwise you cannot see it. } - protected renderTree(model: TreeModel): React.ReactNode { + protected override renderTree(model: TreeModel): React.ReactNode { if (this.shouldShowWelcomeView()) return this.renderViewWelcome(); if (this.shouldShowEmptyView()) return this.renderEmptyView(); return super.renderTree(model); } - protected renderEmptyView() { + protected renderEmptyView(): React.ReactNode { return (
@@ -71,7 +71,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { ); } - protected shouldShowWelcomeView(): boolean { + protected override shouldShowWelcomeView(): boolean { if (!this.model || this.model instanceof CloudSketchbookTreeModel) { return !this.authenticationService.session; } @@ -83,7 +83,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { return CompositeTreeNode.is(node) && node.children.length === 0; } - protected createNodeClassNames(node: any, props: NodeProps): string[] { + protected override createNodeClassNames(node: any, props: NodeProps): string[] { const classNames = super.createNodeClassNames(node, props); if ( @@ -97,7 +97,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { return classNames; } - protected renderInlineCommands(node: any): React.ReactNode { + protected override renderInlineCommands(node: any): React.ReactNode { if (CloudSketchbookTree.CloudSketchDirNode.is(node) && node.commands) { return Array.from(new Set(node.commands)).map((command) => this.renderInlineCommand(command.id, node, { @@ -108,7 +108,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { return undefined; } - protected renderViewWelcome(): React.ReactNode { + protected override renderViewWelcome(): React.ReactNode { return (
@@ -151,7 +151,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { ); } - protected handleDblClickEvent( + protected override handleDblClickEvent( node: TreeNode, event: React.MouseEvent ): void { diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts index 4f4986bc2..7204df632 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts @@ -47,7 +47,7 @@ type FilesToSync = { @injectable() export class CloudSketchbookTree extends SketchbookTree { @inject(FileService) - protected readonly fileService: FileService; + protected override readonly fileService: FileService; @inject(LocalCacheFsProvider) protected readonly localCacheFsProvider: LocalCacheFsProvider; @@ -56,7 +56,7 @@ export class CloudSketchbookTree extends SketchbookTree { protected readonly sketchCache: SketchCache; @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; + protected override readonly arduinoPreferences: ArduinoPreferences; @inject(PreferenceService) protected readonly preferenceService: PreferenceService; @@ -315,7 +315,7 @@ export class CloudSketchbookTree extends SketchbookTree { return { filesToWrite, filesToDelete }; } - async refresh( + override async refresh( node?: CompositeTreeNode ): Promise { if (node) { @@ -375,7 +375,7 @@ export class CloudSketchbookTree extends SketchbookTree { ); } - async resolveChildren(parent: CompositeTreeNode): Promise { + override async resolveChildren(parent: CompositeTreeNode): Promise { return (await super.resolveChildren(parent)).sort((a, b) => { if ( WorkspaceNode.is(parent) && @@ -403,12 +403,12 @@ export class CloudSketchbookTree extends SketchbookTree { } /** - * Retrieve fileStats for the given node, merging the local and remote childrens + * Retrieve fileStats for the given node, merging the local and remote children * Local children take precedence over remote ones * @param node * @returns */ - protected async resolveFileStat( + protected override async resolveFileStat( node: FileStatNode ): Promise { if ( @@ -470,7 +470,7 @@ export class CloudSketchbookTree extends SketchbookTree { } } - protected toNode( + protected override toNode( fileStat: any, parent: CompositeTreeNode ): FileNode | DirNode { @@ -529,7 +529,7 @@ export class CloudSketchbookTree extends SketchbookTree { * @param node * @returns */ - protected async augmentSketchNode(node: DirNode): Promise { + protected override async augmentSketchNode(node: DirNode): Promise { const sketch = this.sketchCache.getSketch( node.fileStat.resource.path.toString() ); @@ -582,7 +582,7 @@ export class CloudSketchbookTree extends SketchbookTree { return node; } - protected async decorateNode( + protected override async decorateNode( node: TreeNode, showAllFiles: boolean ): Promise { @@ -592,7 +592,7 @@ export class CloudSketchbookTree extends SketchbookTree { return node; } - protected async isSketchNode(node: DirNode): Promise { + protected override async isSketchNode(node: DirNode): Promise { if (DirNode.is(node)) { const sketch = this.sketchCache.getSketch( node.fileStat.resource.path.toString() diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts index 7cf71bfe9..22239a227 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts @@ -12,11 +12,11 @@ export class CloudSketchbookWidget extends SketchbookWidget { protected readonly arduinoPreferences: ArduinoPreferences; @postConstruct() - protected init(): void { + protected override init(): void { super.init(); } - getTreeWidget(): any { + override getTreeWidget(): any { const widget: any = this.sketchbookTreesContainer.selectedWidgets().next(); if (widget && typeof widget.getTreeWidget !== 'undefined') { @@ -36,7 +36,7 @@ export class CloudSketchbookWidget extends SketchbookWidget { this.setDocumentMode(); } - setDocumentMode() { + setDocumentMode(): void { if (this.arduinoPreferences['arduino.cloud.enabled']) { this.sketchbookTreesContainer.mode = 'multiple-document'; } else { @@ -44,7 +44,7 @@ export class CloudSketchbookWidget extends SketchbookWidget { } } - protected onAfterAttach(msg: any) { + protected override onAfterAttach(msg: any): void { this.sketchbookTreesContainer.addWidget(this.widget); this.setDocumentMode(); this.arduinoPreferences.onPreferenceChanged((event) => { diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx index 1157492ef..0db8dce92 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx @@ -24,7 +24,7 @@ export class UserStatus extends React.Component< }; } - componentDidMount(): void { + override componentDidMount(): void { const statusListener = () => this.setState({ status: this.status }); window.addEventListener('online', statusListener); window.addEventListener('offline', statusListener); @@ -41,11 +41,11 @@ export class UserStatus extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { this.toDispose.dispose(); } - render(): React.ReactNode { + override render(): React.ReactNode { if (!this.props.authenticationService.session) { return null; } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx index a0ebaf44d..39c0a2ce0 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx @@ -43,7 +43,7 @@ export class ComponentListItem< this.setState({ selectedVersion: version }); } - render(): React.ReactNode { + override render(): React.ReactNode { const { item, itemRenderer } = this.props; return itemRenderer.renderItem( Object.assign(this.state, { item }), diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx index 9b212cc2b..42dce70b8 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx @@ -9,7 +9,7 @@ export class ComponentList extends React.Component< > { protected container?: HTMLElement; - render(): React.ReactNode { + override render(): React.ReactNode { return (
{this.props.items.map((item) => this.createItem(item))} @@ -17,7 +17,7 @@ export class ComponentList extends React.Component< ); } - componentDidMount(): void { + override componentDidMount(): void { if (this.container && this.props.resolveContainer) { this.props.resolveContainer(this.container); } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx index ee4882ba7..0fad3ac61 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx @@ -28,19 +28,19 @@ export class FilterableListContainer< }; } - componentDidMount(): void { + override componentDidMount(): void { this.search = debounce(this.search, 500); this.handleFilterTextChange(''); this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this)); } - componentDidUpdate(): void { + override componentDidUpdate(): void { // See: arduino/arduino-pro-ide#101 // Resets the top of the perfect scroll-bar's thumb. this.props.container.updateScrollBar(); } - render(): React.ReactNode { + override render(): React.ReactNode { return (
{this.renderSearchFilter()} diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts index 52e95a116..ed9827919 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts @@ -11,7 +11,7 @@ export abstract class ListWidgetFrontendContribution { async initializeLayout(): Promise {} - registerMenus(): void { + override registerMenus(): void { // NOOP } } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx index c19440d27..f28db5d5b 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx @@ -21,7 +21,7 @@ import { NotificationCenter } from '../../notification-center'; @injectable() export abstract class ListWidget< T extends ArduinoComponent -> extends ReactWidget { + > extends ReactWidget { @inject(MessageService) protected readonly messageService: MessageService; @@ -42,6 +42,11 @@ export abstract class ListWidget< protected readonly filterTextChangeEmitter = new Emitter< string | undefined >(); + /** + * Instead of running an `update` from the `postConstruct` `init` method, + * we use this variable to track first activate, then run. + */ + protected firstActivate = true; constructor(protected options: ListWidget.Options) { super(); @@ -61,7 +66,6 @@ export abstract class ListWidget< @postConstruct() protected init(): void { - this.update(); this.toDispose.pushAll([ this.notificationCenter.onIndexUpdated(() => this.refresh(undefined)), this.notificationCenter.onDaemonStarted(() => this.refresh(undefined)), @@ -69,21 +73,34 @@ export abstract class ListWidget< ]); } - protected getScrollContainer(): MaybePromise { + protected override getScrollContainer(): MaybePromise { return this.deferredContainer.promise; } - protected onActivateRequest(message: Message): void { + protected override onAfterShow(message: Message): void { + this.maybeUpdateOnFirstRender(); + super.onAfterShow(message); + } + + private maybeUpdateOnFirstRender() { + if (this.firstActivate) { + this.firstActivate = false; + this.update(); + } + } + + protected override onActivateRequest(message: Message): void { + this.maybeUpdateOnFirstRender(); super.onActivateRequest(message); (this.focusNode || this.node).focus(); } - protected onUpdateRequest(message: Message): void { + protected override onUpdateRequest(message: Message): void { super.onUpdateRequest(message); this.render(); } - protected onResize(message: Widget.ResizeMessage): void { + protected override onResize(message: Widget.ResizeMessage): void { super.onResize(message); this.updateScrollBar(); } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx b/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx index 81aeed196..cc9630989 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx @@ -7,7 +7,7 @@ export class SearchBar extends React.Component { this.handleFilterTextChange = this.handleFilterTextChange.bind(this); } - render(): React.ReactNode { + override render(): React.ReactNode { return ( { + override *getNodesByUri(uri: URI): IterableIterator { const workspace = this.root; if (WorkspaceNode.is(workspace)) { for (const root of workspace.children) { @@ -183,7 +183,7 @@ export class SketchbookTreeModel extends FileTreeModel { /** * Move the given source file or directory to the given target directory. */ - async move(source: TreeNode, target: TreeNode): Promise { + override async move(source: TreeNode, target: TreeNode): Promise { if (source.parent && WorkspaceRootNode.is(source)) { // do not support moving a root folder return undefined; @@ -250,7 +250,7 @@ export class SketchbookTreeModel extends FileTreeModel { // selectNode gets called when the user single-clicks on an item // when this happens, we want to open the file if it belongs to the currently open sketch - async selectNode(node: Readonly): Promise { + override async selectNode(node: Readonly): Promise { super.selectNode(node); if (FileNode.is(node) && (await this.isFileInsideCurrentSketch(node))) { this.open(node.uri); @@ -264,7 +264,7 @@ export class SketchbookTreeModel extends FileTreeModel { }); } - protected async doOpenNode(node: TreeNode): Promise { + protected override async doOpenNode(node: TreeNode): Promise { // if it's a sketch dir, or a file from another sketch, open in new window if (!(await this.isFileInsideCurrentSketch(node))) { const sketchRoot = this.recursivelyFindSketchRoot(node); @@ -294,7 +294,10 @@ export class SketchbookTreeModel extends FileTreeModel { // check if the node is a file that belongs to another sketch const sketch = await this.sketchServiceClient.currentSketch(); - if (sketch && node.uri.toString().indexOf(sketch.uri) !== 0) { + if ( + CurrentSketch.isValid(sketch) && + node.uri.toString().indexOf(sketch.uri) !== 0 + ) { return false; } return true; diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx index 4b1535216..240156472 100644 --- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx @@ -14,7 +14,10 @@ import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-render import { SketchbookTree } from './sketchbook-tree'; import { SketchbookTreeModel } from './sketchbook-tree-model'; import { ArduinoPreferences } from '../../arduino-preferences'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection'; import { Sketch } from '../../contributions/contribution'; import { nls } from '@theia/core/lib/common'; @@ -33,10 +36,10 @@ export class SketchbookTreeWidget extends FileTreeWidget { protected currentSketchUri = ''; constructor( - @inject(TreeProps) readonly props: TreeProps, - @inject(SketchbookTreeModel) readonly model: SketchbookTreeModel, + @inject(TreeProps) override readonly props: TreeProps, + @inject(SketchbookTreeModel) override readonly model: SketchbookTreeModel, @inject(ContextMenuRenderer) - readonly contextMenuRenderer: ContextMenuRenderer, + override readonly contextMenuRenderer: ContextMenuRenderer, @inject(EditorManager) readonly editorManager: EditorManager ) { super(props, model, contextMenuRenderer); @@ -50,14 +53,14 @@ export class SketchbookTreeWidget extends FileTreeWidget { } @postConstruct() - protected async init(): Promise { + protected override async init(): Promise { super.init(); // cache the current open sketch uri const currentSketch = await this.sketchServiceClient.currentSketch(); - this.currentSketchUri = (currentSketch && currentSketch.uri) || ''; + this.currentSketchUri = (CurrentSketch.isValid(currentSketch) && currentSketch.uri) || ''; } - protected createNodeClassNames(node: TreeNode, props: NodeProps): string[] { + protected override createNodeClassNames(node: TreeNode, props: NodeProps): string[] { const classNames = super.createNodeClassNames(node, props); if ( @@ -70,7 +73,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { return classNames; } - protected renderIcon(node: TreeNode, props: NodeProps): React.ReactNode { + protected override renderIcon(node: TreeNode, props: NodeProps): React.ReactNode { if (SketchbookTree.SketchDirNode.is(node) || Sketch.isSketchFile(node.id)) { return
; } @@ -81,7 +84,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { return undefined; } - protected renderTailDecorations( + protected override renderTailDecorations( node: TreeNode, props: NodeProps ): React.ReactNode { @@ -99,7 +102,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { this.update(); } - protected createNodeAttributes( + protected override createNodeAttributes( node: TreeNode, props: NodeProps ): React.Attributes & React.HTMLAttributes { @@ -160,7 +163,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { return undefined; } - protected handleClickEvent( + protected override handleClickEvent( node: TreeNode | undefined, event: React.MouseEvent ): void { @@ -186,7 +189,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { } } - protected doToggle(event: React.MouseEvent): void { + protected override doToggle(event: React.MouseEvent): void { const nodeId = event.currentTarget.getAttribute('data-node-id'); if (nodeId) { const node = this.model.getNode(nodeId); diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts index aac987f57..6726f12b6 100644 --- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts +++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts @@ -18,7 +18,7 @@ export class SketchbookTree extends FileNavigatorTree { @inject(ArduinoPreferences) protected readonly arduinoPreferences: ArduinoPreferences; - async resolveChildren(parent: CompositeTreeNode): Promise { + override async resolveChildren(parent: CompositeTreeNode): Promise { const showAllFiles = this.arduinoPreferences['arduino.sketchbook.showAllFiles']; diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts index 6c68ef55c..16b66a26a 100644 --- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts +++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts @@ -23,7 +23,10 @@ import { Disposable, DisposableCollection, } from '@theia/core/lib/common/disposable'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { URI } from '../../contributions/contribution'; @@ -95,7 +98,7 @@ export class SketchbookWidgetContribution return this.openView() as Promise; } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { super.registerCommands(registry); registry.registerCommand(SketchbookCommands.OPEN_NEW_WINDOW, { @@ -142,7 +145,10 @@ export class SketchbookWidgetContribution // disable the "open sketch" command for the current sketch. // otherwise make the command clickable const currentSketch = await this.sketchServiceClient.currentSketch(); - if (currentSketch && currentSketch.uri === arg.node.uri.toString()) { + if ( + CurrentSketch.isValid(currentSketch) && + currentSketch.uri === arg.node.uri.toString() + ) { const placeholder = new PlaceholderMenuNode( SKETCHBOOK__CONTEXT__MAIN_GROUP, SketchbookCommands.OPEN_NEW_WINDOW.label! @@ -186,7 +192,7 @@ export class SketchbookWidgetContribution }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); // unregister main menu action diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx index ce20b43b3..f0a427de7 100644 --- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx @@ -33,7 +33,7 @@ export class SketchbookWidget extends BaseWidget { this.sketchbookTreesContainer.addWidget(this.localSketchbookTreeWidget); } - protected onAfterAttach(message: Message): void { + protected override onAfterAttach(message: Message): void { super.onAfterAttach(message); Widget.attach(this.sketchbookTreesContainer, this.node); this.toDisposeOnDetach.push( @@ -45,7 +45,7 @@ export class SketchbookWidget extends BaseWidget { return this.localSketchbookTreeWidget; } - protected onActivateRequest(message: Message): void { + protected override onActivateRequest(message: Message): void { super.onActivateRequest(message); // TODO: focus the active sketchbook @@ -56,7 +56,7 @@ export class SketchbookWidget extends BaseWidget { this.node.focus(); } - protected onResize(message: Widget.ResizeMessage): void { + protected override onResize(message: Widget.ResizeMessage): void { super.onResize(message); MessageLoop.sendMessage( this.sketchbookTreesContainer, @@ -67,7 +67,7 @@ export class SketchbookWidget extends BaseWidget { } } - protected onAfterShow(msg: Message): void { + protected override onAfterShow(msg: Message): void { super.onAfterShow(msg); this.onResize(Widget.ResizeMessage.UnknownSize); } diff --git a/arduino-ide-extension/src/common/decorators.ts b/arduino-ide-extension/src/common/decorators.ts new file mode 100644 index 000000000..f02fb83d0 --- /dev/null +++ b/arduino-ide-extension/src/common/decorators.ts @@ -0,0 +1,64 @@ +import type { CancellationToken } from '@theia/core/lib/common/cancellation'; +import { default as stringifySafe } from 'fast-safe-stringify'; + +export interface DurationOptions { + /** + * If not specified, falls back to the `String()` value of the `PropertyKey`. + */ + name?: string; + + /** + * If the duration exceeds this timeout (in millis), then the duration will be logged as an error. + */ + timeout?: number; +} + +export function duration(options?: DurationOptions) { + return ( + _target: unknown, + key: PropertyKey, + descriptor: PropertyDescriptor + ): PropertyDescriptor => { + const original = descriptor.value; + descriptor.value = async function (...args: unknown[]) { + const input = args + .filter((arg) => !Boolean(isCancellationToken(arg))) + .map(stringify) + .join(','); + const start = performance.now(); + const result = await original.apply(this, args); + const end = performance.now(); + const duration = end - start; + const slow = duration > (options?.timeout ?? 100); + const message = `---- ${slow ? '!!!SLOW!!! ' : ''}DURATION: ${ + options?.name ?? String(key) + } took ${duration.toFixed(3)} ms. Args: [${input}] ----`; + if (slow) { + console.error(message); + } else { + console.info(message); + } + return result; + }; + return descriptor; + }; +} + +function stringify(arg: unknown): string { + try { + return JSON.stringify(arg); + } catch { + return stringifySafe(arg); + } +} + +// The cancellation token is implicitly the last arg of the JSON-RPC invocation. We want to filter it out from the logs. +// See: https://github.com/eclipse-theia/theia/issues/10129 +function isCancellationToken(arg: unknown): arg is CancellationToken { + return ( + typeof arg === 'object' && + arg !== null && + 'onCancellationRequested' in arg && + 'isCancellationRequested' in arg + ); +} diff --git a/arduino-ide-extension/src/common/protocol/arduino-daemon.ts b/arduino-ide-extension/src/common/protocol/arduino-daemon.ts index 783590048..696629923 100644 --- a/arduino-ide-extension/src/common/protocol/arduino-daemon.ts +++ b/arduino-ide-extension/src/common/protocol/arduino-daemon.ts @@ -1,6 +1,15 @@ export const ArduinoDaemonPath = '/services/arduino-daemon'; export const ArduinoDaemon = Symbol('ArduinoDaemon'); export interface ArduinoDaemon { - isRunning(): Promise; + /** + * Returns with a promise that resolves with the port + * of the CLI daemon when it's up and running. + */ getPort(): Promise; + /** + * Unlike `getPort` this method returns with a promise + * that resolves to `undefined` when the daemon is not running. + * Otherwise resolves to the CLI daemon port. + */ + tryGetPort(): Promise; } diff --git a/arduino-ide-extension/src/common/protocol/config-service.ts b/arduino-ide-extension/src/common/protocol/config-service.ts index b1c6285a1..adc5d9aa0 100644 --- a/arduino-ide-extension/src/common/protocol/config-service.ts +++ b/arduino-ide-extension/src/common/protocol/config-service.ts @@ -9,8 +9,6 @@ export interface ConfigService { getCliConfigFileUri(): Promise; getConfiguration(): Promise; setConfiguration(config: Config): Promise; - isInDataDir(uri: string): Promise; - isInSketchDir(uri: string): Promise; } export interface Daemon { @@ -115,10 +113,8 @@ export interface Config { readonly locale: string; readonly sketchDirUri: string; readonly dataDirUri: string; - readonly downloadsDirUri: string; readonly additionalUrls: AdditionalUrls; readonly network: Network; - readonly daemon: Daemon; } export namespace Config { export function sameAs(left: Config, right: Config): boolean { @@ -135,7 +131,6 @@ export namespace Config { return ( left.locale === right.locale && left.dataDirUri === right.dataDirUri && - left.downloadsDirUri === right.downloadsDirUri && left.sketchDirUri === right.sketchDirUri && Network.sameAs(left.network, right.network) ); diff --git a/arduino-ide-extension/src/common/protocol/notification-service.ts b/arduino-ide-extension/src/common/protocol/notification-service.ts index 59cef1886..3e33f727e 100644 --- a/arduino-ide-extension/src/common/protocol/notification-service.ts +++ b/arduino-ide-extension/src/common/protocol/notification-service.ts @@ -9,7 +9,7 @@ import { export interface NotificationServiceClient { notifyIndexUpdated(): void; - notifyDaemonStarted(): void; + notifyDaemonStarted(port: string): void; notifyDaemonStopped(): void; notifyConfigChanged(event: { config: Config | undefined }): void; notifyPlatformInstalled(event: { item: BoardsPackage }): void; diff --git a/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts b/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts index 970032a1c..1e50729d6 100644 --- a/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts +++ b/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts @@ -10,16 +10,24 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { Sketch, SketchesService } from '../../common/protocol'; import { ConfigService } from './config-service'; -import { SketchContainer } from './sketches-service'; +import { SketchContainer, SketchRef } from './sketches-service'; import { ARDUINO_CLOUD_FOLDER, REMOTE_SKETCHBOOK_FOLDER, } from '../../browser/utils/constants'; import * as monaco from '@theia/monaco-editor-core'; +import { Deferred } from '@theia/core/lib/common/promise-util'; const READ_ONLY_FILES = ['sketch.json']; const READ_ONLY_FILES_REMOTE = ['thingProperties.h', 'thingsProperties.h']; +export type CurrentSketch = Sketch | 'invalid'; +export namespace CurrentSketch { + export function isValid(arg: CurrentSketch | undefined): arg is Sketch { + return !!arg && arg !== 'invalid'; + } +} + @injectable() export class SketchesServiceClientImpl implements FrontendApplicationContribution @@ -40,13 +48,16 @@ export class SketchesServiceClientImpl protected readonly configService: ConfigService; protected toDispose = new DisposableCollection(); - protected sketches = new Map(); + protected sketches = new Map(); + // TODO: rename this + event to the `onBlabla` pattern protected sketchbookDidChangeEmitter = new Emitter<{ - created: Sketch[]; - removed: Sketch[]; + created: SketchRef[]; + removed: SketchRef[]; }>(); readonly onSketchbookDidChange = this.sketchbookDidChangeEmitter.event; + private _currentSketch = new Deferred(); + onStart(): void { this.configService.getConfiguration().then(({ sketchDirUri }) => { this.sketchService @@ -99,13 +110,16 @@ export class SketchesServiceClientImpl ); }); }); + this.loadCurrentSketch().then((currentSketch) => + this._currentSketch.resolve(currentSketch) + ); } onStop(): void { this.toDispose.dispose(); } - async currentSketch(): Promise { + private async loadCurrentSketch(): Promise { const sketches = ( await Promise.all( this.workspaceService @@ -116,7 +130,7 @@ export class SketchesServiceClientImpl ) ).filter(notEmpty); if (!sketches.length) { - return undefined; + return 'invalid'; } if (sketches.length > 1) { console.log( @@ -128,16 +142,14 @@ export class SketchesServiceClientImpl return sketches[0]; } + async currentSketch(): Promise { + return this._currentSketch.promise; + } + async currentSketchFile(): Promise { - const sketch = await this.currentSketch(); - if (sketch) { - const uri = sketch.mainFileUri; - const exists = await this.fileService.exists(new URI(uri)); - if (!exists) { - this.messageService.warn(`Could not find sketch file: ${uri}`); - return undefined; - } - return uri; + const currentSketch = await this.currentSketch(); + if (CurrentSketch.isValid(currentSketch)) { + return currentSketch.mainFileUri; } return undefined; } @@ -145,10 +157,10 @@ export class SketchesServiceClientImpl private fireSoonHandle?: number; private bufferedSketchbookEvents: { type: 'created' | 'removed'; - sketch: Sketch; + sketch: SketchRef; }[] = []; - private fireSoon(sketch: Sketch, type: 'created' | 'removed'): void { + private fireSoon(sketch: SketchRef, type: 'created' | 'removed'): void { this.bufferedSketchbookEvents.push({ type, sketch }); if (typeof this.fireSoonHandle === 'number') { @@ -156,7 +168,7 @@ export class SketchesServiceClientImpl } this.fireSoonHandle = window.setTimeout(() => { - const event: { created: Sketch[]; removed: Sketch[] } = { + const event: { created: SketchRef[]; removed: SketchRef[] } = { created: [], removed: [], }; diff --git a/arduino-ide-extension/src/common/protocol/sketches-service.ts b/arduino-ide-extension/src/common/protocol/sketches-service.ts index 06f979910..722ada586 100644 --- a/arduino-ide-extension/src/common/protocol/sketches-service.ts +++ b/arduino-ide-extension/src/common/protocol/sketches-service.ts @@ -44,7 +44,7 @@ export interface SketchesService { * Sketches are created to the temp location by default and will be moved under `directories.user` on save. * This method resolves to `true` if the `sketch` is still in the temp location. Otherwise, `false`. */ - isTemp(sketch: Sketch): Promise; + isTemp(sketch: SketchRef): Promise; /** * If `isTemp` is `true` for the `sketch`, you can call this method to move the sketch from the temp @@ -81,9 +81,20 @@ export interface SketchesService { getIdeTempFolderUri(sketch: Sketch): Promise; } -export interface Sketch { +export interface SketchRef { readonly name: string; readonly uri: string; // `LocationPath` +} +export namespace SketchRef { + export function fromUri(uriLike: string | URI): SketchRef { + const uri = typeof uriLike === 'string' ? new URI(uriLike) : uriLike; + return { + name: uri.path.base, + uri: typeof uriLike === 'string' ? uriLike : uriLike.toString(), + }; + } +} +export interface Sketch extends SketchRef { readonly mainFileUri: string; // `MainFile` readonly otherSketchFileUris: string[]; // `OtherSketchFiles` readonly additionalFileUris: string[]; // `AdditionalFiles` @@ -134,9 +145,16 @@ export namespace Sketch { export interface SketchContainer { readonly label: string; readonly children: SketchContainer[]; - readonly sketches: Sketch[]; + readonly sketches: SketchRef[]; } export namespace SketchContainer { + export function create(label: string): SketchContainer { + return { + label, + children: [], + sketches: [], + }; + } export function is(arg: any): arg is SketchContainer { return ( !!arg && @@ -174,8 +192,8 @@ export namespace SketchContainer { return container; } - export function toArray(container: SketchContainer): Sketch[] { - const visit = (parent: SketchContainer, toPushSketch: Sketch[]) => { + export function toArray(container: SketchContainer): SketchRef[] { + const visit = (parent: SketchContainer, toPushSketch: SketchRef[]) => { toPushSketch.push(...parent.sketches); parent.children.map((child) => visit(child, toPushSketch)); }; diff --git a/arduino-ide-extension/src/electron-browser/electron-window-service.ts b/arduino-ide-extension/src/electron-browser/electron-window-service.ts index 72540e680..1407ac425 100644 --- a/arduino-ide-extension/src/electron-browser/electron-window-service.ts +++ b/arduino-ide-extension/src/electron-browser/electron-window-service.ts @@ -21,7 +21,7 @@ export class ElectronWindowService extends TheiaElectronWindowService { protected readonly appStateService: FrontendApplicationStateService; @postConstruct() - protected init(): void { + protected override init(): void { this.appStateService .reachedAnyState('initialized_layout') .then(() => this.splashService.requestClose()); diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts index 84f2fe715..f18b07df8 100644 --- a/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts @@ -1,4 +1,4 @@ -import { injectable } from '@theia/core/shared/inversify'; +import { inject, injectable } from '@theia/core/shared/inversify'; import * as remote from '@theia/core/electron-shared/@electron/remote'; import { isOSX } from '@theia/core/lib/common/os'; import { @@ -14,10 +14,27 @@ import { ArduinoMenus, PlaceholderMenuNode, } from '../../../browser/menu/arduino-menus'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @injectable() export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { - createElectronMenuBar(): Electron.Menu { + @inject(FrontendApplicationStateService) + private readonly appStateService: FrontendApplicationStateService; + + private appReady = false; + private updateWhenReady = false; + + override postConstruct(): void { + super.postConstruct(); + this.appStateService.reachedState('ready').then(() => { + this.appReady = true; + if (this.updateWhenReady) { + this.setMenuBar(); + } + }); + } + + override createElectronMenuBar(): Electron.Menu { this._toggledCommands.clear(); // https://github.com/eclipse-theia/theia/issues/8977 const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR); const template = this.fillMenuTemplate([], menuModel); @@ -29,7 +46,14 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { return menu; } - async setMenuBar(): Promise { + override async setMenuBar(): Promise { + // Avoid updating menu items when the app is not ready. + // Getting the current electron window is not free and synchronous. + // Here, we defer all menu update requests, and fire one when the app is ready. + if (!this.appReady) { + this.updateWhenReady = true; + return; + } await this.preferencesService.ready; const createdMenuBar = this.createElectronMenuBar(); if (isOSX) { @@ -39,7 +63,10 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { } } - createElectronContextMenu(menuPath: MenuPath, args?: any[]): Electron.Menu { + override createElectronContextMenu( + menuPath: MenuPath, + args?: any[] + ): Electron.Menu { const menuModel = this.menuProvider.getMenu(menuPath); const template = this.fillMenuTemplate([], menuModel, args, { showDisabled: false, @@ -64,7 +91,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { return template; } - protected createOSXMenu(): Electron.MenuItemConstructorOptions { + protected override createOSXMenu(): Electron.MenuItemConstructorOptions { const { submenu } = super.createOSXMenu(); const label = 'Arduino IDE'; if (!!submenu && Array.isArray(submenu)) { @@ -96,7 +123,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { return { label, submenu }; } - protected handleElectronDefault( + protected override handleElectronDefault( menuNode: CompositeMenuNode, args: any[] = [], options?: ElectronMenuOptions diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts index 7e29b53b4..9327637d7 100644 --- a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts @@ -1,4 +1,4 @@ -import { injectable } from '@theia/core/shared/inversify'; +import { inject, injectable } from '@theia/core/shared/inversify'; import { CommandRegistry } from '@theia/core/lib/common/command'; import { MenuModelRegistry } from '@theia/core/lib/common/menu'; import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding'; @@ -7,35 +7,140 @@ import { ElectronCommands, } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'; import { MainMenuManager } from '../../../common/main-menu-manager'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; +import { ZoomLevel } from '@theia/core/lib/electron-browser/window/electron-window-preferences'; +import { PreferenceScope } from '@theia/core/lib/browser/preferences/preference-scope'; +import { + getCurrentWindow, + getCurrentWebContents, +} from '@theia/core/electron-shared/@electron/remote'; @injectable() export class ElectronMenuContribution extends TheiaElectronMenuContribution implements MainMenuManager { - protected hideTopPanel(): void { + @inject(FrontendApplicationStateService) + private readonly appStateService: FrontendApplicationStateService; + + // private appReady = false; + // private updateWhenReady = false; + + override onStart(app: FrontendApplication): void { + super.onStart(app); + this.appStateService.reachedState('ready').then(() => { + // this.appReady = true; + // if (this.updateWhenReady) { + // this.update(); + // } + }); + } + + protected override hideTopPanel(): void { // NOOP // We reuse the `div` for the Arduino toolbar. } update(): void { + // if (this.appReady) { (this as any).setMenu(); + // } else { + // this.updateWhenReady = true; + // } } - registerCommands(registry: CommandRegistry): void { - super.registerCommands(registry); + override registerCommands(registry: CommandRegistry): void { + this.theiaRegisterCommands(registry); registry.unregisterCommand(ElectronCommands.CLOSE_WINDOW); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); registry.unregisterMenuAction(ElectronCommands.CLOSE_WINDOW); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { super.registerKeybindings(registry); registry.unregisterKeybinding(ElectronCommands.CLOSE_WINDOW.id); registry.unregisterKeybinding(ElectronCommands.ZOOM_IN.id); registry.unregisterKeybinding(ElectronCommands.ZOOM_OUT.id); } + + // Copied from Theia: https://github.com/eclipse-theia/theia/blob/9ec8835cf35d5a46101a62ae93285aeb37a2f382/packages/core/src/electron-browser/menu/electron-menu-contribution.ts#L260-L314 + // Unlike the Theia implementation, this does not require synchronously the browser window, but use a function only when the command handler executes. + private theiaRegisterCommands(registry: CommandRegistry): void { + const currentWindow = () => getCurrentWindow(); + + registry.registerCommand(ElectronCommands.TOGGLE_DEVELOPER_TOOLS, { + execute: () => { + const webContent = getCurrentWebContents(); + if (!webContent.isDevToolsOpened()) { + webContent.openDevTools(); + } else { + webContent.closeDevTools(); + } + }, + }); + + registry.registerCommand(ElectronCommands.RELOAD, { + execute: () => this.windowService.reload(), + }); + registry.registerCommand(ElectronCommands.CLOSE_WINDOW, { + execute: () => currentWindow().close(), + }); + + registry.registerCommand(ElectronCommands.ZOOM_IN, { + execute: () => { + const webContents = currentWindow().webContents; + // When starting at a level that is not a multiple of 0.5, increment by at most 0.5 to reach the next highest multiple of 0.5. + let zoomLevel = + Math.floor(webContents.zoomLevel / ZoomLevel.VARIATION) * + ZoomLevel.VARIATION + + ZoomLevel.VARIATION; + if (zoomLevel > ZoomLevel.MAX) { + zoomLevel = ZoomLevel.MAX; + return; + } + this.preferenceService.set( + 'window.zoomLevel', + zoomLevel, + PreferenceScope.User + ); + }, + }); + registry.registerCommand(ElectronCommands.ZOOM_OUT, { + execute: () => { + const webContents = currentWindow().webContents; + // When starting at a level that is not a multiple of 0.5, decrement by at most 0.5 to reach the next lowest multiple of 0.5. + let zoomLevel = + Math.ceil(webContents.zoomLevel / ZoomLevel.VARIATION) * + ZoomLevel.VARIATION - + ZoomLevel.VARIATION; + if (zoomLevel < ZoomLevel.MIN) { + zoomLevel = ZoomLevel.MIN; + return; + } + this.preferenceService.set( + 'window.zoomLevel', + zoomLevel, + PreferenceScope.User + ); + }, + }); + registry.registerCommand(ElectronCommands.RESET_ZOOM, { + execute: () => + this.preferenceService.set( + 'window.zoomLevel', + ZoomLevel.DEFAULT, + PreferenceScope.User + ), + }); + registry.registerCommand(ElectronCommands.TOGGLE_FULL_SCREEN, { + isEnabled: () => currentWindow().isFullScreenable(), + isVisible: () => currentWindow().isFullScreenable(), + execute: () => + currentWindow().setFullScreen(!currentWindow().isFullScreen()), + }); + } } diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts index fd6a27aa5..fb21f234f 100644 --- a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts @@ -39,7 +39,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ElectronMenuContribution).toSelf().inSingletonScope(); bind(MainMenuManager).toService(ElectronMenuContribution); rebind(TheiaElectronMenuContribution).to(ElectronMenuContribution); - bind(ElectronMainMenuFactory).toSelf().inRequestScope(); + bind(ElectronMainMenuFactory).toSelf().inSingletonScope(); rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory); bind(ElectronWindowService).toSelf().inSingletonScope(); rebind(WindowService).toService(ElectronWindowService); diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts index 360f70eed..e91453312 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts @@ -1,5 +1,13 @@ import { inject, injectable } from '@theia/core/shared/inversify'; -import { app, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, screen, Event as ElectronEvent } from '@theia/core/electron-shared/electron'; +import { + app, + BrowserWindow, + BrowserWindowConstructorOptions, + contentTracing, + ipcMain, + screen, + Event as ElectronEvent, +} from '@theia/core/electron-shared/electron'; import { fork } from 'child_process'; import { AddressInfo } from 'net'; import { join, dirname } from 'path'; @@ -34,6 +42,28 @@ interface WorkspaceOptions { const WORKSPACES = 'workspaces'; +/** + * Purely a dev thing. If you start the app with the `--nosplash` argument, + * then you won't have the splash screen (which is always on top :confused:) and can debug the app at startup. + * Note: if you start the app from VS Code with the `App (Electron)` config, the splash screen will be disabled. + */ +const APP_STARTED_WITH_NOSPLASH = + typeof process !== 'undefined' && process.argv.indexOf('--nosplash') !== -1; + +/** + * If the app is started with `--open-devtools` argument, the `Dev Tools` will be opened. + */ +const APP_STARTED_WITH_DEV_TOOLS = + typeof process !== 'undefined' && + process.argv.indexOf('--open-devtools') !== -1; + +/** + * If the app is started with `--content-trace` argument, the `Dev Tools` will be opened and content tracing will start. + */ +const APP_STARTED_WITH_CONTENT_TRACE = + typeof process !== 'undefined' && + process.argv.indexOf('--content-trace') !== -1; + @injectable() export class ElectronMainApplication extends TheiaElectronMainApplication { protected startup = false; @@ -42,13 +72,74 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { @inject(SplashServiceImpl) protected readonly splashService: SplashServiceImpl; - async start(config: FrontendApplicationConfig): Promise { + override async start(config: FrontendApplicationConfig): Promise { // Explicitly set the app name to have better menu items on macOS. ("About", "Hide", and "Quit") // See: https://github.com/electron-userland/electron-builder/issues/2468 // Regression in Theia: https://github.com/eclipse-theia/theia/issues/8701 app.on('ready', () => app.setName(config.applicationName)); this.attachFileAssociations(); - return super.start(config); + this.useNativeWindowFrame = this.getTitleBarStyle(config) === 'native'; + this._config = config; + this.hookApplicationEvents(); + const [port] = await Promise.all([this.startBackend(), app.whenReady()]); + this.startContentTracing(); + this._backendPort.resolve(port); + await Promise.all([ + this.attachElectronSecurityToken(port), + this.startContributions(), + ]); + return this.launch({ + secondInstance: false, + argv: this.processArgv.getProcessArgvWithoutBin(process.argv), + cwd: process.cwd(), + }); + } + + private startContentTracing(): void { + if (!APP_STARTED_WITH_CONTENT_TRACE) { + return; + } + if (!app.isReady()) { + throw new Error( + 'Cannot start content tracing when the electron app is not ready.' + ); + } + const defaultTraceCategories: Readonly> = [ + '-*', + 'devtools.timeline', + 'disabled-by-default-devtools.timeline', + 'disabled-by-default-devtools.timeline.frame', + 'toplevel', + 'blink.console', + 'disabled-by-default-devtools.timeline.stack', + 'disabled-by-default-v8.cpu_profile', + 'disabled-by-default-v8.cpu_profiler', + 'disabled-by-default-v8.cpu_profiler.hires', + ]; + const traceOptions = { + categoryFilter: defaultTraceCategories.join(','), + traceOptions: 'record-until-full', + options: 'sampling-frequency=10000', + }; + (async () => { + const appPath = app.getAppPath(); + let traceFile: string | undefined; + if (appPath) { + const tracesPath = join(appPath, 'traces'); + await fs.promises.mkdir(tracesPath, { recursive: true }); + traceFile = join(tracesPath, `trace-${new Date().toISOString()}.trace`); + } + console.log('>>> Content tracing has started...'); + await contentTracing.startRecording(traceOptions); + await new Promise((resolve) => setTimeout(resolve, 10_000)); + contentTracing + .stopRecording(traceFile) + .then((out) => + console.log( + `<<< Content tracing has finished. The trace data was written to: ${out}.` + ) + ); + })(); } attachFileAssociations() { @@ -71,7 +162,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { return typeof uri === 'string' && await fs.pathExists(uri); } - protected async launch(params: ElectronMainExecutionParams): Promise { + protected override async launch(params: ElectronMainExecutionParams): Promise { try { // When running on MacOS, we either have to wait until // 1. The `open-file` command has been received by the app, rejecting the promise @@ -143,18 +234,18 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { return electronWindow; } - protected avoidOverlap(options: TheiaBrowserWindowOptions): TheiaBrowserWindowOptions { + protected override avoidOverlap(options: TheiaBrowserWindowOptions): TheiaBrowserWindowOptions { if (this.startup) { return options; } return super.avoidOverlap(options); } - protected getTitleBarStyle(): 'native' | 'custom' { + protected override getTitleBarStyle(config: FrontendApplicationConfig): 'native' | 'custom' { return 'native'; } - protected hookApplicationEvents(): void { + protected override hookApplicationEvents(): void { app.on('will-quit', this.onWillQuit.bind(this)); app.on('second-instance', this.onSecondInstance.bind(this)); app.on('window-all-closed', this.onWindowAllClosed.bind(this)); @@ -164,7 +255,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { }); } - protected async onSecondInstance(event: ElectronEvent, argv: string[], cwd: string): Promise { + protected override async onSecondInstance(event: ElectronEvent, argv: string[], cwd: string): Promise { if (!os.isOSX && await this.launchFromArgs({ cwd, argv, secondInstance: true })) { // Application has received a file in its arguments return; @@ -177,13 +268,13 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { * * @param options */ - async createWindow( + override async createWindow( asyncOptions: MaybePromise = this.getDefaultTheiaWindowOptions() ): Promise { let options = await asyncOptions; options = this.avoidOverlap(options); let electronWindow: BrowserWindow | undefined; - if (this.windows.size) { + if (this.windows.size || APP_STARTED_WITH_NOSPLASH) { electronWindow = await this.doCreateWindow(options); } else { const { bounds } = screen.getDisplayNearestPoint( @@ -235,10 +326,22 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { options: TheiaBrowserWindowOptions ): Promise { const electronWindow = await super.createWindow(options); + if (APP_STARTED_WITH_DEV_TOOLS) { + electronWindow.webContents.openDevTools(); + } this.attachListenersToWindow(electronWindow); return electronWindow; } + protected override getDefaultOptions(): TheiaBrowserWindowOptions { + const options = super.getDefaultOptions(); + if (!options.webPreferences) { + options.webPreferences = {}; + } + options.webPreferences.v8CacheOptions = 'bypassHeatCheck'; // TODO: verify this. VS Code use this V8 option. + return options; + } + private attachListenersToWindow(electronWindow: BrowserWindow) { electronWindow.webContents.on( 'new-window', @@ -270,7 +373,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { this.attachClosedWorkspace(electronWindow); } - protected async startBackend(): Promise { + protected override async startBackend(): Promise { // Check if we should run everything as one process. const noBackendFork = process.argv.indexOf('--no-cluster') !== -1; // We cannot use the `process.cwd()` as the application project path (the location of the `package.json` in other words) @@ -359,7 +462,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { }); } - protected onWillQuit(event: Electron.Event): void { + protected override onWillQuit(event: Electron.Event): void { // Only add workspaces which were closed within the last second (1000 milliseconds) const threshold = Date.now() - 1000; const visited = new Set(); diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts index bc5a2e91b..2248ac543 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts @@ -6,9 +6,9 @@ import { NewWindowOptions } from '@theia/core/lib/common/window'; @injectable() export class ElectronMainWindowServiceImpl extends TheiaElectronMainWindowService { @inject(ElectronMainApplication) - protected readonly app: ElectronMainApplication; + protected override readonly app: ElectronMainApplication; - openNewWindow(url: string, { external }: NewWindowOptions): undefined { + override openNewWindow(url: string, { external }: NewWindowOptions): undefined { if (!external) { const sanitizedUrl = this.sanitize(url); const existing = this.app.browserWindows.find( diff --git a/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts b/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts index 073ba5f48..8c5fd03bd 100644 --- a/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts +++ b/arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts @@ -1,12 +1,16 @@ import { injectable } from '@theia/core/shared/inversify'; +import { ipcMain, IpcMainEvent } from '@theia/electron/shared/electron'; import { StopReason } from '@theia/core/lib/electron-common/messaging/electron-messages'; import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window'; import { FileUri } from '@theia/core/lib/node'; import URI from '@theia/core/lib/common/uri'; +import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state'; +import { createDisposableListener } from '@theia/core/lib/electron-main/event-utils'; +import { APPLICATION_STATE_CHANGE_SIGNAL } from '@theia/core/lib/electron-common/messaging/electron-messages'; @injectable() export class TheiaElectronWindow extends DefaultTheiaElectronWindow { - protected async handleStopRequest( + protected override async handleStopRequest( onSafeCallback: () => unknown, reason: StopReason ): Promise { @@ -33,4 +37,31 @@ export class TheiaElectronWindow extends DefaultTheiaElectronWindow { } return false; } + + // Note: does the same as the Theia impl, but logs state changes. + protected override trackApplicationState(): void { + createDisposableListener( + ipcMain, + APPLICATION_STATE_CHANGE_SIGNAL, + (e: IpcMainEvent, state: FrontendApplicationState) => { + console.log( + 'app-state-change', + `>>> new app state <${state} was received from sender <${e.sender.id}>. current window ID: ${this._window.id}` + ); + if (this.isSender(e)) { + this.applicationState = state; + console.log( + 'app-state-change', + `<<< new app state is <${this.applicationState}> for window <${this._window.id}>` + ); + } else { + console.log( + 'app-state-change', + `<<< new app state <${state}> is ignored from <${e.sender.id}>. current window ID is <${this._window.id}>` + ); + } + }, + this.toDispose + ); + } } diff --git a/arduino-ide-extension/src/node/arduino-daemon-impl.ts b/arduino-ide-extension/src/node/arduino-daemon-impl.ts index 7744cd6ea..e53d6afe3 100644 --- a/arduino-ide-extension/src/node/arduino-daemon-impl.ts +++ b/arduino-ide-extension/src/node/arduino-daemon-impl.ts @@ -13,7 +13,6 @@ import { environment } from '@theia/application-package/lib/environment'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; import { ArduinoDaemon, NotificationServiceServer } from '../common/protocol'; -import { DaemonLog } from './daemon-log'; import { CLI_CONFIG } from './cli-config'; import { getExecPath, spawnCommand } from './exec-util'; @@ -32,28 +31,30 @@ export class ArduinoDaemonImpl protected readonly notificationService: NotificationServiceServer; protected readonly toDispose = new DisposableCollection(); - protected readonly onDaemonStartedEmitter = new Emitter(); + protected readonly onDaemonStartedEmitter = new Emitter(); protected readonly onDaemonStoppedEmitter = new Emitter(); protected _running = false; - protected _ready = new Deferred(); + protected _port = new Deferred(); protected _execPath: string | undefined; - protected _port: string; // Backend application lifecycle. onStart(): void { - this.startDaemon(); + this.startDaemon(); // no await } // Daemon API - async isRunning(): Promise { - return Promise.resolve(this._running); + async getPort(): Promise { + return this._port.promise; } - async getPort(): Promise { - return Promise.resolve(this._port); + async tryGetPort(): Promise { + if (this._running) { + return this._port.promise; + } + return undefined; } async startDaemon(): Promise { @@ -62,7 +63,6 @@ export class ArduinoDaemonImpl const cliPath = await this.getExecPath(); this.onData(`Starting daemon from ${cliPath}...`); const { daemon, port } = await this.spawnDaemonProcess(); - this._port = port; // Watchdog process for terminating the daemon process when the backend app terminates. spawn( process.execPath, @@ -83,7 +83,7 @@ export class ArduinoDaemonImpl Disposable.create(() => daemon.kill()), Disposable.create(() => this.fireDaemonStopped()), ]); - this.fireDaemonStarted(); + this.fireDaemonStarted(port); this.onData('Daemon is running.'); } catch (err) { this.onData('Failed to start the daemon.'); @@ -103,7 +103,7 @@ export class ArduinoDaemonImpl this.toDispose.dispose(); } - get onDaemonStarted(): Event { + get onDaemonStarted(): Event { return this.onDaemonStartedEmitter.event; } @@ -111,10 +111,6 @@ export class ArduinoDaemonImpl return this.onDaemonStoppedEmitter.event; } - get ready(): Promise { - return this._ready.promise; - } - async getExecPath(): Promise { if (this._execPath) { return this._execPath; @@ -240,11 +236,11 @@ export class ArduinoDaemonImpl return ready.promise; } - protected fireDaemonStarted(): void { + protected fireDaemonStarted(port: string): void { this._running = true; - this._ready.resolve(); - this.onDaemonStartedEmitter.fire(); - this.notificationService.notifyDaemonStarted(); + this._port.resolve(port); + this.onDaemonStartedEmitter.fire(port); + this.notificationService.notifyDaemonStarted(port); } protected fireDaemonStopped(): void { @@ -252,14 +248,14 @@ export class ArduinoDaemonImpl return; } this._running = false; - this._ready.reject(); // Reject all pending. - this._ready = new Deferred(); + this._port.reject(); // Reject all pending. + this._port = new Deferred(); this.onDaemonStoppedEmitter.fire(); this.notificationService.notifyDaemonStopped(); } protected onData(message: string): void { - DaemonLog.log(this.logger, message); + this.logger.info(message); } protected onError(error: any): void { diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 7c6c64957..9124436ce 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -58,7 +58,10 @@ import { FileSystemExt, FileSystemExtPath, } from '../common/protocol/filesystem-ext'; -import { ExamplesServiceImpl } from './examples-service-impl'; +import { + BuiltInExamplesServiceImpl, + ExamplesServiceImpl, +} from './examples-service-impl'; import { ExamplesService, ExamplesServicePath, @@ -80,8 +83,6 @@ import { } from '../common/protocol'; import { BackendApplication } from './theia/core/backend-application'; import { BoardDiscovery } from './board-discovery'; -import { DefaultGitInit } from './theia/git/git-init'; -import { GitInit } from '@theia/git/lib/node/init/git-init'; import { AuthenticationServiceImpl } from './auth/authentication-service-impl'; import { AuthenticationService, @@ -138,6 +139,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { ) ) .inSingletonScope(); + // Built-in examples are not board specific, so it is possible to have one shared instance. + bind(BuiltInExamplesServiceImpl).toSelf().inSingletonScope(); // Examples service. One per backend, each connected FE gets a proxy. bind(ConnectionContainerModule).toConstantValue( @@ -329,9 +332,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope() .whenTargetNamed(SerialServiceName); - bind(DefaultGitInit).toSelf(); - rebind(GitInit).toService(DefaultGitInit); - // Remote sketchbook bindings bind(AuthenticationServiceImpl).toSelf().inSingletonScope(); bind(AuthenticationService).toService(AuthenticationServiceImpl); diff --git a/arduino-ide-extension/src/node/board-discovery.ts b/arduino-ide-extension/src/node/board-discovery.ts index 3354d53a4..dcaa43d92 100644 --- a/arduino-ide-extension/src/node/board-discovery.ts +++ b/arduino-ide-extension/src/node/board-discovery.ts @@ -55,9 +55,7 @@ export class BoardDiscovery extends CoreClientAware { @postConstruct() protected async init(): Promise { - await this.coreClientProvider.initialized; - const coreClient = await this.coreClient(); - this.startBoardListWatch(coreClient); + this.coreClient().then((client) => this.startBoardListWatch(client)); } stopBoardListWatch(coreClient: CoreClientProvider.Client): Promise { diff --git a/arduino-ide-extension/src/node/cli-config.ts b/arduino-ide-extension/src/node/cli-config.ts index 1b19557af..16bac01a3 100644 --- a/arduino-ide-extension/src/node/cli-config.ts +++ b/arduino-ide-extension/src/node/cli-config.ts @@ -1,42 +1,31 @@ import { RecursivePartial } from '@theia/core/lib/common/types'; -import { Daemon } from '../common/protocol/config-service'; +import { AdditionalUrls } from '../common/protocol'; export const CLI_CONFIG = 'arduino-cli.yaml'; export interface BoardManager { - readonly additional_urls: Array; + readonly additional_urls: AdditionalUrls; } export namespace BoardManager { export function sameAs( left: RecursivePartial | undefined, right: RecursivePartial | undefined ): boolean { - const leftOrDefault = left || {}; - const rightOrDefault = right || {}; - const leftUrls = Array.from(new Set(leftOrDefault.additional_urls || [])); - const rightUrls = Array.from(new Set(rightOrDefault.additional_urls || [])); - if (leftUrls.length !== rightUrls.length) { - return false; - } - return leftUrls.every((url) => rightUrls.indexOf(url) !== -1); + const leftUrls = left?.additional_urls ?? []; + const rightUrls = right?.additional_urls ?? []; + return AdditionalUrls.sameAs(leftUrls, rightUrls); } } export interface Directories { readonly data: string; - readonly downloads: string; readonly user: string; } export namespace Directories { export function is( directories: RecursivePartial | undefined ): directories is Directories { - return ( - !!directories && - !!directories.data && - !!directories.downloads && - !!directories.user - ); + return !!directories && !!directories.data && !!directories.user; } export function sameAs( left: RecursivePartial | undefined, @@ -48,11 +37,7 @@ export namespace Directories { if (right === undefined) { return left === undefined; } - return ( - left.data === right.data && - left.downloads === right.downloads && - left.user === right.user - ); + return left.data === right.data && left.user === right.user; } } @@ -111,15 +96,12 @@ export interface CliConfig { // Bare minimum required CLI config. export interface DefaultCliConfig extends CliConfig { directories: Directories; - daemon: Daemon; } export namespace DefaultCliConfig { export function is( config: RecursivePartial | undefined ): config is DefaultCliConfig { - return ( - !!config && Directories.is(config.directories) && Daemon.is(config.daemon) - ); + return !!config && Directories.is(config.directories); } export function sameAs( left: DefaultCliConfig, @@ -127,7 +109,6 @@ export namespace DefaultCliConfig { ): boolean { return ( Directories.sameAs(left.directories, right.directories) && - Daemon.sameAs(left.daemon, right.daemon) && BoardManager.sameAs(left.board_manager, right.board_manager) && Logging.sameAs(left.logging, right.logging) ); diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts index 94ceab14f..a15ce4719 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts @@ -62,6 +62,12 @@ export class InitRequest extends jspb.Message { getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): InitRequest; + getProfile(): string; + setProfile(value: string): InitRequest; + + getSketchPath(): string; + setSketchPath(value: string): InitRequest; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): InitRequest.AsObject; @@ -76,6 +82,8 @@ export class InitRequest extends jspb.Message { export namespace InitRequest { export type AsObject = { instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, + profile: string, + sketchPath: string, } } @@ -93,6 +101,12 @@ export class InitResponse extends jspb.Message { setError(value?: google_rpc_status_pb.Status): InitResponse; + hasProfile(): boolean; + clearProfile(): void; + getProfile(): cc_arduino_cli_commands_v1_common_pb.Profile | undefined; + setProfile(value?: cc_arduino_cli_commands_v1_common_pb.Profile): InitResponse; + + getMessageCase(): InitResponse.MessageCase; serializeBinary(): Uint8Array; @@ -109,6 +123,7 @@ export namespace InitResponse { export type AsObject = { initProgress?: InitResponse.Progress.AsObject, error?: google_rpc_status_pb.Status.AsObject, + profile?: cc_arduino_cli_commands_v1_common_pb.Profile.AsObject, } @@ -151,6 +166,8 @@ export namespace InitResponse { ERROR = 2, + PROFILE = 3, + } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js index 1937aa229..820634067 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js @@ -866,7 +866,9 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.toObject = function(opt_i */ proto.cc.arduino.cli.commands.v1.InitRequest.toObject = function(includeInstance, msg) { var f, obj = { - instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f) + instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), + profile: jspb.Message.getFieldWithDefault(msg, 2, ""), + sketchPath: jspb.Message.getFieldWithDefault(msg, 3, "") }; if (includeInstance) { @@ -908,6 +910,14 @@ proto.cc.arduino.cli.commands.v1.InitRequest.deserializeBinaryFromReader = funct reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader); msg.setInstance(value); break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setProfile(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setSketchPath(value); + break; default: reader.skipField(); break; @@ -945,6 +955,20 @@ proto.cc.arduino.cli.commands.v1.InitRequest.serializeBinaryToWriter = function( cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter ); } + f = message.getProfile(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getSketchPath(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } }; @@ -985,6 +1009,42 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.hasInstance = function() }; +/** + * optional string profile = 2; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.getProfile = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InitRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.setProfile = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string sketch_path = 3; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.getSketchPath = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InitRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.setSketchPath = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + /** * Oneof group definitions for this message. Each group defines the field @@ -994,7 +1054,7 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.hasInstance = function() * @private {!Array>} * @const */ -proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2]]; +proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2,3]]; /** * @enum {number} @@ -1002,7 +1062,8 @@ proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2]]; proto.cc.arduino.cli.commands.v1.InitResponse.MessageCase = { MESSAGE_NOT_SET: 0, INIT_PROGRESS: 1, - ERROR: 2 + ERROR: 2, + PROFILE: 3 }; /** @@ -1044,7 +1105,8 @@ proto.cc.arduino.cli.commands.v1.InitResponse.prototype.toObject = function(opt_ proto.cc.arduino.cli.commands.v1.InitResponse.toObject = function(includeInstance, msg) { var f, obj = { initProgress: (f = msg.getInitProgress()) && proto.cc.arduino.cli.commands.v1.InitResponse.Progress.toObject(includeInstance, f), - error: (f = msg.getError()) && google_rpc_status_pb.Status.toObject(includeInstance, f) + error: (f = msg.getError()) && google_rpc_status_pb.Status.toObject(includeInstance, f), + profile: (f = msg.getProfile()) && cc_arduino_cli_commands_v1_common_pb.Profile.toObject(includeInstance, f) }; if (includeInstance) { @@ -1091,6 +1153,11 @@ proto.cc.arduino.cli.commands.v1.InitResponse.deserializeBinaryFromReader = func reader.readMessage(value,google_rpc_status_pb.Status.deserializeBinaryFromReader); msg.setError(value); break; + case 3: + var value = new cc_arduino_cli_commands_v1_common_pb.Profile; + reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Profile.deserializeBinaryFromReader); + msg.setProfile(value); + break; default: reader.skipField(); break; @@ -1136,6 +1203,14 @@ proto.cc.arduino.cli.commands.v1.InitResponse.serializeBinaryToWriter = function google_rpc_status_pb.Status.serializeBinaryToWriter ); } + f = message.getProfile(); + if (f != null) { + writer.writeMessage( + 3, + f, + cc_arduino_cli_commands_v1_common_pb.Profile.serializeBinaryToWriter + ); + } }; @@ -1415,6 +1490,43 @@ proto.cc.arduino.cli.commands.v1.InitResponse.prototype.hasError = function() { }; +/** + * optional Profile profile = 3; + * @return {?proto.cc.arduino.cli.commands.v1.Profile} + */ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.getProfile = function() { + return /** @type{?proto.cc.arduino.cli.commands.v1.Profile} */ ( + jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.Profile, 3)); +}; + + +/** + * @param {?proto.cc.arduino.cli.commands.v1.Profile|undefined} value + * @return {!proto.cc.arduino.cli.commands.v1.InitResponse} returns this +*/ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.setProfile = function(value) { + return jspb.Message.setOneofWrapperField(this, 3, proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.cc.arduino.cli.commands.v1.InitResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.clearProfile = function() { + return this.setProfile(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.hasProfile = function() { + return jspb.Message.getField(this, 3) != null; +}; + + diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts index 571b58afe..a7bd5e06c 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts @@ -185,28 +185,36 @@ export namespace Platform { } } -export class PlatformReference extends jspb.Message { +export class InstalledPlatformReference extends jspb.Message { getId(): string; - setId(value: string): PlatformReference; + setId(value: string): InstalledPlatformReference; getVersion(): string; - setVersion(value: string): PlatformReference; + setVersion(value: string): InstalledPlatformReference; + + getInstallDir(): string; + setInstallDir(value: string): InstalledPlatformReference; + + getPackageUrl(): string; + setPackageUrl(value: string): InstalledPlatformReference; serializeBinary(): Uint8Array; - toObject(includeInstance?: boolean): PlatformReference.AsObject; - static toObject(includeInstance: boolean, msg: PlatformReference): PlatformReference.AsObject; + toObject(includeInstance?: boolean): InstalledPlatformReference.AsObject; + static toObject(includeInstance: boolean, msg: InstalledPlatformReference): InstalledPlatformReference.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; - static serializeBinaryToWriter(message: PlatformReference, writer: jspb.BinaryWriter): void; - static deserializeBinary(bytes: Uint8Array): PlatformReference; - static deserializeBinaryFromReader(message: PlatformReference, reader: jspb.BinaryReader): PlatformReference; + static serializeBinaryToWriter(message: InstalledPlatformReference, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): InstalledPlatformReference; + static deserializeBinaryFromReader(message: InstalledPlatformReference, reader: jspb.BinaryReader): InstalledPlatformReference; } -export namespace PlatformReference { +export namespace InstalledPlatformReference { export type AsObject = { id: string, version: string, + installDir: string, + packageUrl: string, } } @@ -234,3 +242,28 @@ export namespace Board { fqbn: string, } } + +export class Profile extends jspb.Message { + getName(): string; + setName(value: string): Profile; + + getFqbn(): string; + setFqbn(value: string): Profile; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): Profile.AsObject; + static toObject(includeInstance: boolean, msg: Profile): Profile.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: Profile, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): Profile; + static deserializeBinaryFromReader(message: Profile, reader: jspb.BinaryReader): Profile; +} + +export namespace Profile { + export type AsObject = { + name: string, + fqbn: string, + } +} diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js index efd86871e..8b3916047 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js @@ -17,9 +17,10 @@ var global = Function('return this')(); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Board', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgress', null, global); +goog.exportSymbol('proto.cc.arduino.cli.commands.v1.InstalledPlatformReference', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Instance', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Platform', null, global); -goog.exportSymbol('proto.cc.arduino.cli.commands.v1.PlatformReference', null, global); +goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Profile', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Programmer', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.TaskProgress', null, global); /** @@ -137,16 +138,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.cc.arduino.cli.commands.v1.PlatformReference = function(opt_data) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference = function(opt_data) { jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; -goog.inherits(proto.cc.arduino.cli.commands.v1.PlatformReference, jspb.Message); +goog.inherits(proto.cc.arduino.cli.commands.v1.InstalledPlatformReference, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.cc.arduino.cli.commands.v1.PlatformReference.displayName = 'proto.cc.arduino.cli.commands.v1.PlatformReference'; + proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.displayName = 'proto.cc.arduino.cli.commands.v1.InstalledPlatformReference'; } /** * Generated by JsPbCodeGenerator. @@ -169,6 +170,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.cc.arduino.cli.commands.v1.Board.displayName = 'proto.cc.arduino.cli.commands.v1.Board'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.commands.v1.Profile = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.commands.v1.Profile, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.cc.arduino.cli.commands.v1.Profile.displayName = 'proto.cc.arduino.cli.commands.v1.Profile'; +} @@ -1405,8 +1427,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.toObject = function(opt_includeInstance) { - return proto.cc.arduino.cli.commands.v1.PlatformReference.toObject(opt_includeInstance, this); +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.toObject(opt_includeInstance, this); }; @@ -1415,14 +1437,16 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.toObject = function * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} msg The msg instance to transform. + * @param {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.cc.arduino.cli.commands.v1.PlatformReference.toObject = function(includeInstance, msg) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.toObject = function(includeInstance, msg) { var f, obj = { id: jspb.Message.getFieldWithDefault(msg, 1, ""), - version: jspb.Message.getFieldWithDefault(msg, 2, "") + version: jspb.Message.getFieldWithDefault(msg, 2, ""), + installDir: jspb.Message.getFieldWithDefault(msg, 3, ""), + packageUrl: jspb.Message.getFieldWithDefault(msg, 4, "") }; if (includeInstance) { @@ -1436,23 +1460,23 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.toObject = function(includeIn /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinary = function(bytes) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.cc.arduino.cli.commands.v1.PlatformReference; - return proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader(msg, reader); + var msg = new proto.cc.arduino.cli.commands.v1.InstalledPlatformReference; + return proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} msg The message object to deserialize into. + * @param {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader = function(msg, reader) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -1467,6 +1491,14 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader = var value = /** @type {string} */ (reader.readString()); msg.setVersion(value); break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setInstallDir(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setPackageUrl(value); + break; default: reader.skipField(); break; @@ -1480,9 +1512,9 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.deserializeBinaryFromReader = * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.serializeBinary = function() { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter(this, writer); + proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -1490,11 +1522,11 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.serializeBinary = f /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.cc.arduino.cli.commands.v1.PlatformReference} message + * @param {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = function(message, writer) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.serializeBinaryToWriter = function(message, writer) { var f = undefined; f = message.getId(); if (f.length > 0) { @@ -1510,6 +1542,20 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = fun f ); } + f = message.getInstallDir(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getPackageUrl(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } }; @@ -1517,16 +1563,16 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.serializeBinaryToWriter = fun * optional string id = 1; * @return {string} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.getId = function() { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getId = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); }; /** * @param {string} value - * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} returns this + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setId = function(value) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setId = function(value) { return jspb.Message.setProto3StringField(this, 1, value); }; @@ -1535,20 +1581,56 @@ proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setId = function(va * optional string version = 2; * @return {string} */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.getVersion = function() { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getVersion = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); }; /** * @param {string} value - * @return {!proto.cc.arduino.cli.commands.v1.PlatformReference} returns this + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this */ -proto.cc.arduino.cli.commands.v1.PlatformReference.prototype.setVersion = function(value) { +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setVersion = function(value) { return jspb.Message.setProto3StringField(this, 2, value); }; +/** + * optional string install_dir = 3; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getInstallDir = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this + */ +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setInstallDir = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string package_url = 4; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.getPackageUrl = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} returns this + */ +proto.cc.arduino.cli.commands.v1.InstalledPlatformReference.prototype.setPackageUrl = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + @@ -1709,4 +1791,164 @@ proto.cc.arduino.cli.commands.v1.Board.prototype.setFqbn = function(value) { }; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.commands.v1.Profile.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.commands.v1.Profile} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.Profile.toObject = function(includeInstance, msg) { + var f, obj = { + name: jspb.Message.getFieldWithDefault(msg, 1, ""), + fqbn: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.commands.v1.Profile} + */ +proto.cc.arduino.cli.commands.v1.Profile.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.commands.v1.Profile; + return proto.cc.arduino.cli.commands.v1.Profile.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.commands.v1.Profile} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.commands.v1.Profile} + */ +proto.cc.arduino.cli.commands.v1.Profile.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setName(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setFqbn(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.commands.v1.Profile.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.commands.v1.Profile} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.Profile.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getName(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getFqbn(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional string name = 1; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.getName = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.Profile} returns this + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.setName = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string fqbn = 2; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.getFqbn = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.Profile} returns this + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.setFqbn = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + goog.object.extend(exports, proto.cc.arduino.cli.commands.v1); diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts index 7613a69fd..db947e130 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts @@ -86,6 +86,15 @@ export class CompileRequest extends jspb.Message { setLibraryList(value: Array): CompileRequest; addLibrary(value: string, index?: number): string; + getKeysKeychain(): string; + setKeysKeychain(value: string): CompileRequest; + + getSignKey(): string; + setSignKey(value: string): CompileRequest; + + getEncryptKey(): string; + setEncryptKey(value: string): CompileRequest; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): CompileRequest.AsObject; @@ -121,6 +130,9 @@ export namespace CompileRequest { sourceOverrideMap: Array<[string, string]>, exportBinaries?: google_protobuf_wrappers_pb.BoolValue.AsObject, libraryList: Array, + keysKeychain: string, + signKey: string, + encryptKey: string, } } @@ -151,14 +163,14 @@ export class CompileResponse extends jspb.Message { hasBoardPlatform(): boolean; clearBoardPlatform(): void; - getBoardPlatform(): cc_arduino_cli_commands_v1_common_pb.PlatformReference | undefined; - setBoardPlatform(value?: cc_arduino_cli_commands_v1_common_pb.PlatformReference): CompileResponse; + getBoardPlatform(): cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference | undefined; + setBoardPlatform(value?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference): CompileResponse; hasBuildPlatform(): boolean; clearBuildPlatform(): void; - getBuildPlatform(): cc_arduino_cli_commands_v1_common_pb.PlatformReference | undefined; - setBuildPlatform(value?: cc_arduino_cli_commands_v1_common_pb.PlatformReference): CompileResponse; + getBuildPlatform(): cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference | undefined; + setBuildPlatform(value?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference): CompileResponse; hasProgress(): boolean; @@ -184,8 +196,8 @@ export namespace CompileResponse { buildPath: string, usedLibrariesList: Array, executableSectionsSizeList: Array, - boardPlatform?: cc_arduino_cli_commands_v1_common_pb.PlatformReference.AsObject, - buildPlatform?: cc_arduino_cli_commands_v1_common_pb.PlatformReference.AsObject, + boardPlatform?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.AsObject, + buildPlatform?: cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.AsObject, progress?: cc_arduino_cli_commands_v1_common_pb.TaskProgress.AsObject, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js index 1d2bd8977..69aafe386 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js @@ -146,7 +146,10 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.toObject = function(includeInsta createCompilationDatabaseOnly: jspb.Message.getBooleanFieldWithDefault(msg, 21, false), sourceOverrideMap: (f = msg.getSourceOverrideMap()) ? f.toObject(includeInstance, undefined) : [], exportBinaries: (f = msg.getExportBinaries()) && google_protobuf_wrappers_pb.BoolValue.toObject(includeInstance, f), - libraryList: (f = jspb.Message.getRepeatedField(msg, 24)) == null ? undefined : f + libraryList: (f = jspb.Message.getRepeatedField(msg, 24)) == null ? undefined : f, + keysKeychain: jspb.Message.getFieldWithDefault(msg, 25, ""), + signKey: jspb.Message.getFieldWithDefault(msg, 26, ""), + encryptKey: jspb.Message.getFieldWithDefault(msg, 27, "") }; if (includeInstance) { @@ -271,6 +274,18 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.deserializeBinaryFromReader = fu var value = /** @type {string} */ (reader.readString()); msg.addLibrary(value); break; + case 25: + var value = /** @type {string} */ (reader.readString()); + msg.setKeysKeychain(value); + break; + case 26: + var value = /** @type {string} */ (reader.readString()); + msg.setSignKey(value); + break; + case 27: + var value = /** @type {string} */ (reader.readString()); + msg.setEncryptKey(value); + break; default: reader.skipField(); break; @@ -446,6 +461,27 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.serializeBinaryToWriter = functi f ); } + f = message.getKeysKeychain(); + if (f.length > 0) { + writer.writeString( + 25, + f + ); + } + f = message.getSignKey(); + if (f.length > 0) { + writer.writeString( + 26, + f + ); + } + f = message.getEncryptKey(); + if (f.length > 0) { + writer.writeString( + 27, + f + ); + } }; @@ -926,6 +962,60 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.clearLibraryList = fun }; +/** + * optional string keys_keychain = 25; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getKeysKeychain = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 25, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setKeysKeychain = function(value) { + return jspb.Message.setProto3StringField(this, 25, value); +}; + + +/** + * optional string sign_key = 26; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getSignKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 26, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setSignKey = function(value) { + return jspb.Message.setProto3StringField(this, 26, value); +}; + + +/** + * optional string encrypt_key = 27; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getEncryptKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 27, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setEncryptKey = function(value) { + return jspb.Message.setProto3StringField(this, 27, value); +}; + + /** * List of repeated fields within this message type. @@ -972,8 +1062,8 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.toObject = function(includeInst cc_arduino_cli_commands_v1_lib_pb.Library.toObject, includeInstance), executableSectionsSizeList: jspb.Message.toObjectList(msg.getExecutableSectionsSizeList(), proto.cc.arduino.cli.commands.v1.ExecutableSectionSize.toObject, includeInstance), - boardPlatform: (f = msg.getBoardPlatform()) && cc_arduino_cli_commands_v1_common_pb.PlatformReference.toObject(includeInstance, f), - buildPlatform: (f = msg.getBuildPlatform()) && cc_arduino_cli_commands_v1_common_pb.PlatformReference.toObject(includeInstance, f), + boardPlatform: (f = msg.getBoardPlatform()) && cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.toObject(includeInstance, f), + buildPlatform: (f = msg.getBuildPlatform()) && cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.toObject(includeInstance, f), progress: (f = msg.getProgress()) && cc_arduino_cli_commands_v1_common_pb.TaskProgress.toObject(includeInstance, f) }; @@ -1034,13 +1124,13 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.deserializeBinaryFromReader = f msg.addExecutableSectionsSize(value); break; case 6: - var value = new cc_arduino_cli_commands_v1_common_pb.PlatformReference; - reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.PlatformReference.deserializeBinaryFromReader); + var value = new cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference; + reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.deserializeBinaryFromReader); msg.setBoardPlatform(value); break; case 7: - var value = new cc_arduino_cli_commands_v1_common_pb.PlatformReference; - reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.PlatformReference.deserializeBinaryFromReader); + var value = new cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference; + reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.deserializeBinaryFromReader); msg.setBuildPlatform(value); break; case 8: @@ -1119,7 +1209,7 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.serializeBinaryToWriter = funct writer.writeMessage( 6, f, - cc_arduino_cli_commands_v1_common_pb.PlatformReference.serializeBinaryToWriter + cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.serializeBinaryToWriter ); } f = message.getBuildPlatform(); @@ -1127,7 +1217,7 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.serializeBinaryToWriter = funct writer.writeMessage( 7, f, - cc_arduino_cli_commands_v1_common_pb.PlatformReference.serializeBinaryToWriter + cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference.serializeBinaryToWriter ); } f = message.getProgress(); @@ -1320,17 +1410,17 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.clearExecutableSectio /** - * optional PlatformReference board_platform = 6; - * @return {?proto.cc.arduino.cli.commands.v1.PlatformReference} + * optional InstalledPlatformReference board_platform = 6; + * @return {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.getBoardPlatform = function() { - return /** @type{?proto.cc.arduino.cli.commands.v1.PlatformReference} */ ( - jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.PlatformReference, 6)); + return /** @type{?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ ( + jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference, 6)); }; /** - * @param {?proto.cc.arduino.cli.commands.v1.PlatformReference|undefined} value + * @param {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference|undefined} value * @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this */ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.setBoardPlatform = function(value) { @@ -1357,17 +1447,17 @@ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.hasBoardPlatform = fu /** - * optional PlatformReference build_platform = 7; - * @return {?proto.cc.arduino.cli.commands.v1.PlatformReference} + * optional InstalledPlatformReference build_platform = 7; + * @return {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.getBuildPlatform = function() { - return /** @type{?proto.cc.arduino.cli.commands.v1.PlatformReference} */ ( - jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.PlatformReference, 7)); + return /** @type{?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference} */ ( + jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.InstalledPlatformReference, 7)); }; /** - * @param {?proto.cc.arduino.cli.commands.v1.PlatformReference|undefined} value + * @param {?proto.cc.arduino.cli.commands.v1.InstalledPlatformReference|undefined} value * @return {!proto.cc.arduino.cli.commands.v1.CompileResponse} returns this */ proto.cc.arduino.cli.commands.v1.CompileResponse.prototype.setBuildPlatform = function(value) { diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts index f19526ab3..4d65f4723 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.d.ts @@ -93,6 +93,9 @@ export class MonitorResponse extends jspb.Message { setAppliedSettingsList(value: Array): MonitorResponse; addAppliedSettings(value?: MonitorPortSetting, index?: number): MonitorPortSetting; + getSuccess(): boolean; + setSuccess(value: boolean): MonitorResponse; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): MonitorResponse.AsObject; @@ -109,6 +112,7 @@ export namespace MonitorResponse { error: string, rxData: Uint8Array | string, appliedSettingsList: Array, + success: boolean, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js index 8583f3388..0a2889c04 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/monitor_pb.js @@ -712,7 +712,8 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.toObject = function(includeInst error: jspb.Message.getFieldWithDefault(msg, 1, ""), rxData: msg.getRxData_asB64(), appliedSettingsList: jspb.Message.toObjectList(msg.getAppliedSettingsList(), - proto.cc.arduino.cli.commands.v1.MonitorPortSetting.toObject, includeInstance) + proto.cc.arduino.cli.commands.v1.MonitorPortSetting.toObject, includeInstance), + success: jspb.Message.getBooleanFieldWithDefault(msg, 4, false) }; if (includeInstance) { @@ -762,6 +763,10 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.deserializeBinaryFromReader = f reader.readMessage(value,proto.cc.arduino.cli.commands.v1.MonitorPortSetting.deserializeBinaryFromReader); msg.addAppliedSettings(value); break; + case 4: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setSuccess(value); + break; default: reader.skipField(); break; @@ -813,6 +818,13 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.serializeBinaryToWriter = funct proto.cc.arduino.cli.commands.v1.MonitorPortSetting.serializeBinaryToWriter ); } + f = message.getSuccess(); + if (f) { + writer.writeBool( + 4, + f + ); + } }; @@ -914,6 +926,24 @@ proto.cc.arduino.cli.commands.v1.MonitorResponse.prototype.clearAppliedSettingsL }; +/** + * optional bool success = 4; + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.MonitorResponse.prototype.getSuccess = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.cc.arduino.cli.commands.v1.MonitorResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.MonitorResponse.prototype.setSuccess = function(value) { + return jspb.Message.setProto3BooleanField(this, 4, value); +}; + + diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts index 1c1ca38ca..4427473a6 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts @@ -371,9 +371,6 @@ export class SupportedUserFieldsRequest extends jspb.Message { getProtocol(): string; setProtocol(value: string): SupportedUserFieldsRequest; - getAddress(): string; - setAddress(value: string): SupportedUserFieldsRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SupportedUserFieldsRequest.AsObject; @@ -390,7 +387,6 @@ export namespace SupportedUserFieldsRequest { instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, fqbn: string, protocol: string, - address: string, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js index 87914c167..8841575ad 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js @@ -2718,8 +2718,7 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.toObject = function( var f, obj = { instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), fqbn: jspb.Message.getFieldWithDefault(msg, 2, ""), - protocol: jspb.Message.getFieldWithDefault(msg, 3, ""), - address: jspb.Message.getFieldWithDefault(msg, 4, "") + protocol: jspb.Message.getFieldWithDefault(msg, 3, "") }; if (includeInstance) { @@ -2769,10 +2768,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.deserializeBinaryFro var value = /** @type {string} */ (reader.readString()); msg.setProtocol(value); break; - case 4: - var value = /** @type {string} */ (reader.readString()); - msg.setAddress(value); - break; default: reader.skipField(); break; @@ -2824,13 +2819,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.serializeBinaryToWri f ); } - f = message.getAddress(); - if (f.length > 0) { - writer.writeString( - 4, - f - ); - } }; @@ -2907,24 +2895,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setProtoco }; -/** - * optional string address = 4; - * @return {string} - */ -proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.getAddress = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); -}; - - -/** - * @param {string} value - * @return {!proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest} returns this - */ -proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setAddress = function(value) { - return jspb.Message.setProto3StringField(this, 4, value); -}; - - diff --git a/arduino-ide-extension/src/node/config-service-impl.ts b/arduino-ide-extension/src/node/config-service-impl.ts index 3e626f4c1..420d4185f 100644 --- a/arduino-ide-extension/src/node/config-service-impl.ts +++ b/arduino-ide-extension/src/node/config-service-impl.ts @@ -1,8 +1,6 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as temp from 'temp'; +import { promises as fs } from 'fs'; +import { dirname } from 'path'; import * as yaml from 'js-yaml'; -import { promisify } from 'util'; import * as grpc from '@grpc/grpc-js'; import { injectable, inject, named } from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; @@ -28,9 +26,9 @@ import { DefaultCliConfig, CLI_CONFIG } from './cli-config'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { deepClone } from '@theia/core'; +import { duration } from '../common/decorators'; const deepmerge = require('deepmerge'); -const track = temp.track(); @injectable() export class ConfigServiceImpl @@ -54,18 +52,19 @@ export class ConfigServiceImpl protected ready = new Deferred(); protected readonly configChangeEmitter = new Emitter(); - async onStart(): Promise { - await this.ensureCliConfigExists(); - this.cliConfig = await this.loadCliConfig(); - if (this.cliConfig) { - const config = await this.mapCliConfigToAppConfig(this.cliConfig); - if (config) { - this.config = config; - this.ready.resolve(); - return; + onStart(): void { + this.loadCliConfig().then(async (cliConfig) => { + this.cliConfig = cliConfig; + if (this.cliConfig) { + const config = await this.mapCliConfigToAppConfig(this.cliConfig); + if (config) { + this.config = config; + this.ready.resolve(); + return; + } } - } - this.fireInvalidConfig(); + this.fireInvalidConfig(); + }); } async getCliConfigFileUri(): Promise { @@ -75,8 +74,7 @@ export class ConfigServiceImpl async getConfiguration(): Promise { await this.ready.promise; - await this.daemon.ready; - return { ...this.config, daemon: { port: await this.daemon.getPort() } }; + return { ...this.config }; } // Used by frontend to update the config. @@ -91,17 +89,10 @@ export class ConfigServiceImpl if (!copyDefaultCliConfig) { copyDefaultCliConfig = await this.getFallbackCliConfig(); } - const { - additionalUrls, - dataDirUri, - downloadsDirUri, - sketchDirUri, - network, - locale, - } = config; + const { additionalUrls, dataDirUri, sketchDirUri, network, locale } = + config; copyDefaultCliConfig.directories = { data: FileUri.fsPath(dataDirUri), - downloads: FileUri.fsPath(downloadsDirUri), user: FileUri.fsPath(sketchDirUri), }; copyDefaultCliConfig.board_manager = { @@ -135,76 +126,44 @@ export class ConfigServiceImpl return this.daemon.getVersion(); } - async isInDataDir(uri: string): Promise { - return this.getConfiguration().then(({ dataDirUri }) => - new URI(dataDirUri).isEqualOrParent(new URI(uri)) - ); - } - - async isInSketchDir(uri: string): Promise { - return this.getConfiguration().then(({ sketchDirUri }) => - new URI(sketchDirUri).isEqualOrParent(new URI(uri)) - ); - } - - protected async loadCliConfig(): Promise { + @duration() + protected async loadCliConfig( + initializeIfAbsent = true + ): Promise { const cliConfigFileUri = await this.getCliConfigFileUri(); const cliConfigPath = FileUri.fsPath(cliConfigFileUri); try { - const content = await promisify(fs.readFile)(cliConfigPath, { + const content = await fs.readFile(cliConfigPath, { encoding: 'utf8', }); - const model = yaml.safeLoad(content) || {}; - // The CLI can run with partial (missing `port`, `directories`), the app cannot, we merge the default with the user's config. + const model = (yaml.safeLoad(content) || {}) as DefaultCliConfig; + if (model.directories.data && model.directories.user) { + return model; + } + // The CLI can run with partial (missing `port`, `directories`), the IDE2 cannot. + // We merge the default CLI config with the partial user's config. const fallbackModel = await this.getFallbackCliConfig(); return deepmerge(fallbackModel, model) as DefaultCliConfig; } catch (error) { - this.logger.error( - `Error occurred when loading CLI config from ${cliConfigPath}.`, - error - ); + if ('code' in error && error.code === 'ENOENT') { + if (initializeIfAbsent) { + await this.initCliConfigTo(dirname(cliConfigPath)); + return this.loadCliConfig(false); + } + } + throw error; } - return undefined; } protected async getFallbackCliConfig(): Promise { const cliPath = await this.daemon.getExecPath(); - const throwawayDirPath = await new Promise((resolve, reject) => { - track.mkdir({}, (err, dirPath) => { - if (err) { - reject(err); - return; - } - resolve(dirPath); - }); - }); - await spawnCommand(`"${cliPath}"`, [ + const rawJson = await spawnCommand(`"${cliPath}"`, [ 'config', - 'init', - '--dest-dir', - `"${throwawayDirPath}"`, + 'dump', + 'format', + '--json', ]); - const rawYaml = await promisify(fs.readFile)( - path.join(throwawayDirPath, CLI_CONFIG), - { encoding: 'utf-8' } - ); - const model = yaml.safeLoad(rawYaml.trim()); - return model as DefaultCliConfig; - } - - protected async ensureCliConfigExists(): Promise { - const cliConfigFileUri = await this.getCliConfigFileUri(); - const cliConfigPath = FileUri.fsPath(cliConfigFileUri); - let exists = await promisify(fs.exists)(cliConfigPath); - if (!exists) { - await this.initCliConfigTo(path.dirname(cliConfigPath)); - exists = await promisify(fs.exists)(cliConfigPath); - if (!exists) { - throw new Error( - `Could not initialize the default CLI configuration file at ${cliConfigPath}.` - ); - } - } + return JSON.parse(rawJson); } protected async initCliConfigTo(fsPathToDir: string): Promise { @@ -220,8 +179,8 @@ export class ConfigServiceImpl protected async mapCliConfigToAppConfig( cliConfig: DefaultCliConfig ): Promise { - const { directories, locale = 'en', daemon } = cliConfig; - const { data, user, downloads } = directories; + const { directories, locale = 'en' } = cliConfig; + const { user, data } = directories; const additionalUrls: Array = []; if (cliConfig.board_manager && cliConfig.board_manager.additional_urls) { additionalUrls.push( @@ -232,11 +191,9 @@ export class ConfigServiceImpl return { dataDirUri: FileUri.create(data).toString(), sketchDirUri: FileUri.create(user).toString(), - downloadsDirUri: FileUri.create(downloads).toString(), additionalUrls, network, locale, - daemon, }; } diff --git a/arduino-ide-extension/src/node/core-client-provider.ts b/arduino-ide-extension/src/node/core-client-provider.ts index 18b78e626..c585afeda 100644 --- a/arduino-ide-extension/src/node/core-client-provider.ts +++ b/arduino-ide-extension/src/node/core-client-provider.ts @@ -1,5 +1,9 @@ import * as grpc from '@grpc/grpc-js'; -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { Event, Emitter } from '@theia/core/lib/common/event'; import { GrpcClientProvider } from './grpc-client-provider'; import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; @@ -16,7 +20,8 @@ import { } from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb'; import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; import { NotificationServiceServer } from '../common/protocol'; -import { Deferred } from '@theia/core/lib/common/promise-util'; +import { Deferred, retry } from '@theia/core/lib/common/promise-util'; +import { Status as RpcStatus } from './cli-protocol/google/rpc/status_pb'; @injectable() export class CoreClientProvider extends GrpcClientProvider { @@ -48,9 +53,7 @@ export class CoreClientProvider extends GrpcClientProvider(); } - protected async reconcileClient(): Promise { - const port = await this.daemon.getPort(); - + protected override async reconcileClient(port: string): Promise { if (port && port === this._port) { // No need to create a new gRPC client, but we have to update the indexes. if (this._client && !(this._client instanceof Error)) { @@ -58,29 +61,47 @@ export class CoreClientProvider extends GrpcClientProvider { - this.daemon.ready.then(async () => { + protected override init(): void { + this.daemon.getPort().then(async (port) => { // First create the client and the instance synchronously // and notify client is ready. // TODO: Creation failure should probably be handled here - await this.reconcileClient().then(() => { - this._created.resolve(); - }); + await this.reconcileClient(port); // create instance + this._created.resolve(); - // If client has been created correctly update indexes and initialize - // its instance by loading platforms and cores. + // Normal startup workflow: + // 1. create instance, + // 2. init instance, + // 3. update indexes asynchronously. + + // First startup workflow: + // 1. create instance, + // 2. update indexes and wait, + // 3. init instance. if (this._client && !(this._client instanceof Error)) { - await this.updateIndexes(this._client) - .then(this.initInstance) - .then(() => { + try { + await this.initInstance(this._client); // init instance + this._initialized.resolve(); + this.updateIndex(this._client); // Update the indexes asynchronously + } catch (error: unknown) { + if ( + this.isPackageIndexMissingError(error) || + this.isDiscoveryNotFoundError(error) + ) { + // If it's a first start, IDE2 must run index update before the init request. + await this.updateIndexes(this._client); + await this.initInstance(this._client); this._initialized.resolve(); - }); + } else { + throw error; + } + } } }); @@ -93,6 +114,41 @@ export class CoreClientProvider extends GrpcClientProvider + message.includes('loading json index file'); + // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247 + return this.isRpcStatusError(error, assert); + } + + private isDiscoveryNotFoundError(error: unknown): boolean { + const assert = (message: string) => + message.includes('discovery') && + (message.includes('not found') || message.includes('not installed')); + // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740 + // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744 + return this.isRpcStatusError(error, assert); + } + + private isCancelError(error: unknown): boolean { + return ( + error instanceof Error && + error.message.toLocaleLowerCase().includes('cancelled on client') + ); + } + + // Final error codes are not yet defined by the CLI. Hence, we do string matching in the message RPC status. + private isRpcStatusError( + error: unknown, + assert: (message: string) => boolean + ) { + if (error instanceof RpcStatus) { + const { message } = RpcStatus.toObject(false, error); + return assert(message.toLocaleLowerCase()); + } + return false; + } + protected async createClient( port: string | number ): Promise { @@ -134,8 +190,9 @@ export class CoreClientProvider extends GrpcClientProvider { const initReq = new InitRequest(); initReq.setInstance(instance); - await new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const stream = client.init(initReq); + const errorStatus: RpcStatus[] = []; stream.on('data', (res: InitResponse) => { const progress = res.getInitProgress(); if (progress) { @@ -151,55 +208,39 @@ export class CoreClientProvider extends GrpcClientProvider reject(err)); - stream.on('end', resolve); + stream.on('error', (error) => { + // On any error during the init request, the request is canceled. + // On cancel, the IDE2 ignores the cancel error and rejects with the original one. + reject( + this.isCancelError(error) && errorStatus.length + ? errorStatus[0] + : error + ); + }); + stream.on('end', () => + errorStatus.length ? reject(errorStatus) : resolve() + ); }); } - protected async updateIndexes({ - client, - instance, - }: CoreClientProvider.Client): Promise { - // in a separate promise, try and update the index - let indexUpdateSucceeded = true; - for (let i = 0; i < 10; i++) { - try { - await this.updateIndex({ client, instance }); - indexUpdateSucceeded = true; - break; - } catch (e) { - console.error(`Error while updating index in attempt ${i}.`, e); - } - } - if (!indexUpdateSucceeded) { - console.error('Could not update the index. Please restart to try again.'); - } - - let libIndexUpdateSucceeded = true; - for (let i = 0; i < 10; i++) { - try { - await this.updateLibraryIndex({ client, instance }); - libIndexUpdateSucceeded = true; - break; - } catch (e) { - console.error(`Error while updating library index in attempt ${i}.`, e); - } - } - if (!libIndexUpdateSucceeded) { - console.error( - 'Could not update the library index. Please restart to try again.' - ); - } - - if (indexUpdateSucceeded && libIndexUpdateSucceeded) { - this.notificationService.notifyIndexUpdated(); - } - return { client, instance }; + protected async updateIndexes( + client: CoreClientProvider.Client + ): Promise { + await Promise.all([ + retry(() => this.updateIndex(client), 50, 3), + retry(() => this.updateLibraryIndex(client), 50, 3), + ]); + this.notificationService.notifyIndexUpdated(); + return client; } protected async updateLibraryIndex({ @@ -231,7 +272,9 @@ export class CoreClientProvider extends GrpcClientProvider((resolve, reject) => { - resp.on('error', reject); + resp.on('error', (error) => { + reject(error); + }); resp.on('end', resolve); }); } diff --git a/arduino-ide-extension/src/node/daemon-log.ts b/arduino-ide-extension/src/node/daemon-log.ts deleted file mode 100644 index f87a61425..000000000 --- a/arduino-ide-extension/src/node/daemon-log.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { ILogger, LogLevel } from '@theia/core/lib/common/logger'; - -export interface DaemonLog { - readonly time: string; - readonly level: DaemonLog.Level; - readonly msg: string; -} - -export namespace DaemonLog { - export interface Url { - readonly Scheme: string; - readonly Host: string; - readonly Path: string; - } - - export namespace Url { - export function is(arg: any | undefined): arg is Url { - return ( - !!arg && - typeof arg.Scheme === 'string' && - typeof arg.Host === 'string' && - typeof arg.Path === 'string' - ); - } - - export function toString(url: Url): string { - const { Scheme, Host, Path } = url; - return `${Scheme}://${Host}${Path}`; - } - } - - export interface System { - readonly os: string; - // readonly Resource: Resource; - } - - export namespace System { - export function toString(system: System): string { - return `OS: ${system.os}`; - } - } - - export interface Tool { - readonly version: string; - readonly systems: System[]; - } - - export namespace Tool { - export function is(arg: any | undefined): arg is Tool { - return !!arg && typeof arg.version === 'string' && 'systems' in arg; - } - - export function toString(tool: Tool): string { - const { version, systems } = tool; - return `Version: ${version}${ - !!systems - ? ` Systems: [${tool.systems.map(System.toString).join(', ')}]` - : '' - }`; - } - } - - export type Level = 'trace' | 'debug' | 'info' | 'warning' | 'error'; - - export function is(arg: any | undefined): arg is DaemonLog { - return ( - !!arg && - typeof arg.time === 'string' && - typeof arg.level === 'string' && - typeof arg.msg === 'string' - ); - } - - export function toLogLevel(log: DaemonLog): LogLevel { - const { level } = log; - switch (level) { - case 'trace': - return LogLevel.TRACE; - case 'debug': - return LogLevel.DEBUG; - case 'info': - return LogLevel.INFO; - case 'warning': - return LogLevel.WARN; - case 'error': - return LogLevel.ERROR; - default: - return LogLevel.INFO; - } - } - - export function log(logger: ILogger, logMessages: string): void { - const parsed = parse(logMessages); - for (const log of parsed) { - const logLevel = toLogLevel(log); - const message = toMessage(log, { omitLogLevel: true }); - logger.log(logLevel, message); - } - } - - function parse(toLog: string): DaemonLog[] { - const messages = toLog.trim().split('\n'); - const result: DaemonLog[] = []; - for (let i = 0; i < messages.length; i++) { - try { - const maybeDaemonLog = JSON.parse(messages[i]); - if (DaemonLog.is(maybeDaemonLog)) { - result.push(maybeDaemonLog); - continue; - } - } catch { - /* NOOP */ - } - result.push({ - time: new Date().toString(), - level: 'info', - msg: messages[i], - }); - } - return result; - } - - export function toPrettyString(logMessages: string): string { - const parsed = parse(logMessages); - return parsed.map((log) => toMessage(log)).join('\n') + '\n'; - } - - function toMessage( - log: DaemonLog, - options: { omitLogLevel: boolean } = { omitLogLevel: false } - ): string { - const details = Object.keys(log) - .filter((key) => key !== 'msg' && key !== 'level' && key !== 'time') - .map((key) => toDetails(log, key)) - .join(', '); - const logLevel = options.omitLogLevel - ? '' - : `[${log.level.toUpperCase()}] `; - return `${logLevel}${log.msg}${!!details ? ` [${details}]` : ''}`; - } - - function toDetails(log: DaemonLog, key: string): string { - let value = (log as any)[key]; - if (DaemonLog.Url.is(value)) { - value = DaemonLog.Url.toString(value); - } else if (DaemonLog.Tool.is(value)) { - value = DaemonLog.Tool.toString(value); - } else if (typeof value === 'object') { - value = JSON.stringify(value).replace(/\"([^(\")"]+)\":/g, '$1:'); // Remove the quotes from the property keys. - } - return `${key.toLowerCase()}: ${value}`; - } -} diff --git a/arduino-ide-extension/src/node/examples-service-impl.ts b/arduino-ide-extension/src/node/examples-service-impl.ts index b3730ce53..0a056d0a7 100644 --- a/arduino-ide-extension/src/node/examples-service-impl.ts +++ b/arduino-ide-extension/src/node/examples-service-impl.ts @@ -1,10 +1,17 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { join, basename } from 'path'; import * as fs from 'fs'; import { promisify } from 'util'; import { FileUri } from '@theia/core/lib/node/file-uri'; -import { notEmpty } from '@theia/core/lib/common/objects'; -import { Sketch, SketchContainer } from '../common/protocol/sketches-service'; +import { + Sketch, + SketchRef, + SketchContainer, +} from '../common/protocol/sketches-service'; import { SketchesServiceImpl } from './sketches-service-impl'; import { ExamplesService } from '../common/protocol/examples-service'; import { @@ -13,6 +20,71 @@ import { LibraryService, } from '../common/protocol'; import { ConfigServiceImpl } from './config-service-impl'; +import { duration } from '../common/decorators'; +import { URI } from '@theia/core/lib/common/uri'; +import { Path } from '@theia/core/lib/common/path'; + +interface BuiltInSketchRef { + readonly name: string; + readonly relativePath: string; +} +namespace BuiltInSketchRef { + export function toSketchRef( + { name, relativePath }: BuiltInSketchRef, + root: URI + ): SketchRef { + return { + name, + uri: root.resolve(relativePath).toString(), + }; + } +} +interface BuiltInSketchContainer { + readonly label: string; + readonly children: BuiltInSketchContainer[]; + readonly sketches: BuiltInSketchRef[]; +} +namespace BuiltInSketchContainer { + export function toSketchContainer( + source: BuiltInSketchContainer, + root: URI + ): SketchContainer { + return { + label: source.label, + children: source.children.map((child) => toSketchContainer(child, root)), + sketches: source.sketches.map((child) => + BuiltInSketchRef.toSketchRef(child, root) + ), + }; + } +} + +@injectable() +export class BuiltInExamplesServiceImpl { + protected _builtIns: SketchContainer[] | undefined; + + @postConstruct() + protected init(): void { + this.builtIns(); + } + + async builtIns(): Promise { + if (this._builtIns) { + return this._builtIns; + } + const examplesRootPath = join(__dirname, '..', '..', 'Examples'); + const examplesRootUri = FileUri.create(examplesRootPath); + const rawJson = await fs.promises.readFile( + join(examplesRootPath, 'examples.json'), + { encoding: 'utf8' } + ); + const examples: BuiltInSketchContainer[] = JSON.parse(rawJson); + this._builtIns = examples.map((container) => + BuiltInSketchContainer.toSketchContainer(container, examplesRootUri) + ); + return this._builtIns; + } +} @injectable() export class ExamplesServiceImpl implements ExamplesService { @@ -25,28 +97,14 @@ export class ExamplesServiceImpl implements ExamplesService { @inject(ConfigServiceImpl) protected readonly configService: ConfigServiceImpl; - protected _all: SketchContainer[] | undefined; + @inject(BuiltInExamplesServiceImpl) + private readonly builtInExamplesService: BuiltInExamplesServiceImpl; - @postConstruct() - protected init(): void { - this.builtIns(); + builtIns(): Promise { + return this.builtInExamplesService.builtIns(); } - async builtIns(): Promise { - if (this._all) { - return this._all; - } - const exampleRootPath = join(__dirname, '..', '..', 'Examples'); - const exampleNames = await promisify(fs.readdir)(exampleRootPath); - this._all = await Promise.all( - exampleNames - .map((name) => join(exampleRootPath, name)) - .map((path) => this.load(path)) - ); - return this._all; - } - - // TODO: decide whether it makes sense to cache them. Keys should be: `fqbn` + version of containing core/library. + @duration() async installed({ fqbn }: { fqbn?: string }): Promise<{ user: SketchContainer[]; current: SketchContainer[]; @@ -59,7 +117,7 @@ export class ExamplesServiceImpl implements ExamplesService { fqbn, }); for (const pkg of packages) { - const container = await this.tryGroupExamples(pkg); + const container = await this.tryGroupExamplesNew(pkg); const { location } = pkg; if (location === LibraryLocation.USER) { user.push(container); @@ -72,6 +130,9 @@ export class ExamplesServiceImpl implements ExamplesService { any.push(container); } } + // user.sort((left, right) => left.label.localeCompare(right.label)); + // current.sort((left, right) => left.label.localeCompare(right.label)); + // any.sort((left, right) => left.label.localeCompare(right.label)); return { user, current, any }; } @@ -80,60 +141,101 @@ export class ExamplesServiceImpl implements ExamplesService { * folder hierarchy. This method tries to workaround it by falling back to the `installDirUri` and manually creating the * location of the examples. Otherwise it creates the example container from the direct examples FS paths. */ - protected async tryGroupExamples({ + protected async tryGroupExamplesNew({ label, exampleUris, installDirUri, }: LibraryPackage): Promise { - const paths = exampleUris.map((uri) => FileUri.fsPath(uri)); - if (installDirUri) { - for (const example of [ - 'example', - 'Example', - 'EXAMPLE', - 'examples', - 'Examples', - 'EXAMPLES', - ]) { - const examplesPath = join(FileUri.fsPath(installDirUri), example); - const exists = await promisify(fs.exists)(examplesPath); - const isDir = - exists && (await promisify(fs.lstat)(examplesPath)).isDirectory(); - if (isDir) { - const fileNames = await promisify(fs.readdir)(examplesPath); - const children: SketchContainer[] = []; - const sketches: Sketch[] = []; - for (const fileName of fileNames) { - const subPath = join(examplesPath, fileName); - const subIsDir = (await promisify(fs.lstat)(subPath)).isDirectory(); - if (subIsDir) { - const sketch = await this.tryLoadSketch(subPath); - if (!sketch) { - const container = await this.load(subPath); - if (container.children.length || container.sketches.length) { - children.push(container); - } - } else { - sketches.push(sketch); - } - } - } - return { - label, - children, - sketches, - }; - } + const container = SketchContainer.create(label); + if (!installDirUri || !exampleUris.length) { + return container; + } + // Args example: + // exampleUris + // 0:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/MQTT' + // 1:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/Modbus/ModBus-RTU/Master' + // 2:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/Modbus/ModBus-RTU/Slave' + // installDirUri + // 'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1' + // Expected menu structure: + // ATOM_DTU_CAT1 > Modbus > ModBus-RTU > Master + // | > Slave + // > MQTT + const logInfo = (ref: SketchRef) => + `Example URI: ${ref.uri}, install location URI: ${installDirUri}.`; + for (const ref of exampleUris.map(SketchRef.fromUri)) { + const path = new URI(installDirUri).relative(new URI(ref.uri)); + if (!path) { + console.warn( + `Could not resolve the sketch location from its install location. Skipping. ${logInfo( + ref + )}` + ); + continue; } + if (path.isAbsolute) { + console.warn( + `Expected a relative path between the sketch and the install locations. Skipping. Path was: ${path}. ${logInfo( + ref + )}` + ); + continue; + } + const pathSegments = path.toString().split(Path.separator); + if (pathSegments.length < 2) { + console.warn( + `Expected at least two segments long relative path. Skipping. Path segments were: ${pathSegments}. ${logInfo( + ref + )}` + ); + continue; + } + // the relative must start start with `example` or `Examples` or `EXAMPLE`, .etc. It's open source. + if (!/^examples?$/gi.test(pathSegments[0])) { + console.warn( + `First segment must start with "examples-like". More formally: \`/^examples?$/gi\`. Path segments were: ${pathSegments}. ${logInfo( + ref + )}` + ); + } + const getOrCreateChildContainer = ( + label: string, + parent: SketchContainer + ) => { + let child = parent.children.find( + ({ label: childLabel }) => childLabel === label + ); + if (!child) { + child = SketchContainer.create(label); + parent.children.push(child); + //TODO: remove or move sort + parent.children.sort((left, right) => + left.label.localeCompare(right.label) + ); + } + return child; + }; + const refContainer = pathSegments.reduce( + (container, segment, index, segments) => { + if (index === 0) { + // skip the first "example-like" segment + return container; + } + if (index === segments.length - 1) { + // if last segment, it's the example sketch itself, do not create container for it. + return container; + } + return getOrCreateChildContainer(segment, container); + }, + container + ); + refContainer.sketches.push(ref); + //TODO: remove or move sort + refContainer.sketches.sort((left, right) => + left.name.localeCompare(right.name) + ); } - const sketches = await Promise.all( - paths.map((path) => this.tryLoadSketch(path)) - ); - return { - label, - children: [], - sketches: sketches.filter(notEmpty), - }; + return container; } // Built-ins are included inside the IDE. @@ -146,17 +248,19 @@ export class ExamplesServiceImpl implements ExamplesService { throw new Error(`${path} is not a directory.`); } const names = await promisify(fs.readdir)(path); - const sketches: Sketch[] = []; + const sketches: SketchRef[] = []; const children: SketchContainer[] = []; for (const p of names.map((name) => join(path, name))) { const stat = await promisify(fs.stat)(p); if (stat.isDirectory()) { const sketch = await this.tryLoadSketch(p); if (sketch) { - sketches.push(sketch); + sketches.push({ name: sketch.name, uri: sketch.uri }); + sketches.sort((left, right) => left.name.localeCompare(right.name)); } else { const child = await this.load(p); children.push(child); + children.sort((left, right) => left.label.localeCompare(right.label)); } } } diff --git a/arduino-ide-extension/src/node/grpc-client-provider.ts b/arduino-ide-extension/src/node/grpc-client-provider.ts index af1fcede6..10d7b3d70 100644 --- a/arduino-ide-extension/src/node/grpc-client-provider.ts +++ b/arduino-ide-extension/src/node/grpc-client-provider.ts @@ -1,4 +1,8 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { ILogger } from '@theia/core/lib/common/logger'; import { MaybePromise } from '@theia/core/lib/common/types'; import { ConfigServiceImpl } from './config-service-impl'; @@ -15,16 +19,18 @@ export abstract class GrpcClientProvider { @inject(ConfigServiceImpl) protected readonly configService: ConfigServiceImpl; - protected _port: string | number | undefined; + protected _port: string | undefined; protected _client: C | Error | undefined; @postConstruct() protected init(): void { - const updateClient = () => { - this.reconcileClient(); - }; - this.configService.onConfigChange(updateClient); - this.daemon.ready.then(updateClient); + this.configService.onConfigChange(() => { + // Only reconcile the gRPC client if the port is known. Hence the CLI daemon is running. + if (this._port) { + this.reconcileClient(this._port); + } + }); + this.daemon.getPort().then((port) => this.reconcileClient(port)); this.daemon.onDaemonStopped(() => { if (this._client && !(this._client instanceof Error)) { this.close(this._client); @@ -36,32 +42,25 @@ export abstract class GrpcClientProvider { async client(): Promise { try { - await this.daemon.ready; + await this.daemon.getPort(); return this._client; } catch (error) { return error; } } - protected async reconcileClient(): Promise { - const port = await this.daemon.getPort(); - - if (this._port === port) { - return; // Nothing to do. - } + protected async reconcileClient(port: string): Promise { this._port = port; if (this._client && !(this._client instanceof Error)) { this.close(this._client); this._client = undefined; } - if (this._port) { - try { - const client = await this.createClient(this._port); - this._client = client; - } catch (error) { - this.logger.error('Could not create client for gRPC.', error); - this._client = error; - } + try { + const client = await this.createClient(this._port); + this._client = client; + } catch (error) { + this.logger.error('Could not create client for gRPC.', error); + this._client = error; } } diff --git a/arduino-ide-extension/src/node/notification-service-server.ts b/arduino-ide-extension/src/node/notification-service-server.ts index 7ce211eca..851452d38 100644 --- a/arduino-ide-extension/src/node/notification-service-server.ts +++ b/arduino-ide-extension/src/node/notification-service-server.ts @@ -19,8 +19,8 @@ export class NotificationServiceServerImpl this.clients.forEach((client) => client.notifyIndexUpdated()); } - notifyDaemonStarted(): void { - this.clients.forEach((client) => client.notifyDaemonStarted()); + notifyDaemonStarted(port: string): void { + this.clients.forEach((client) => client.notifyDaemonStarted(port)); } notifyDaemonStopped(): void { diff --git a/arduino-ide-extension/src/node/sketches-service-impl.ts b/arduino-ide-extension/src/node/sketches-service-impl.ts index b197f2125..b2ec97eaf 100644 --- a/arduino-ide-extension/src/node/sketches-service-impl.ts +++ b/arduino-ide-extension/src/node/sketches-service-impl.ts @@ -1,19 +1,20 @@ import { injectable, inject } from '@theia/core/shared/inversify'; -import * as minimatch from 'minimatch'; import * as fs from 'fs'; import * as os from 'os'; import * as temp from 'temp'; +import * as tempDir from 'temp-dir'; import * as path from 'path'; import * as crypto from 'crypto'; import { ncp } from 'ncp'; import { promisify } from 'util'; import URI from '@theia/core/lib/common/uri'; import { FileUri } from '@theia/core/lib/node'; -import { isWindows } from '@theia/core/lib/common/os'; +import { isWindows, isOSX } from '@theia/core/lib/common/os'; import { ConfigService } from '../common/protocol/config-service'; import { SketchesService, Sketch, + SketchRef, SketchContainer, } from '../common/protocol/sketches-service'; import { firstToLowerCase } from '../common/utils'; @@ -24,16 +25,28 @@ import { ArchiveSketchRequest, LoadSketchRequest, } from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb'; +import { duration } from '../common/decorators'; +import * as glob from 'glob'; +import { Deferred } from '@theia/core/lib/common/promise-util'; const WIN32_DRIVE_REGEXP = /^[a-zA-Z]:\\/; const prefix = '.arduinoIDE-unsaved'; @injectable() -export class SketchesServiceImpl extends CoreClientAware - implements SketchesService { +export class SketchesServiceImpl + extends CoreClientAware + implements SketchesService +{ private sketchSuffixIndex = 1; private lastSketchBaseName: string; + // If on macOS, the `temp-dir` lib will make sure there is resolved realpath. + // If on Windows, the `C:\Users\KITTAA~1\AppData\Local\Temp` path will be resolved and normalized to `C:\Users\kittaakos\AppData\Local\Temp`. + // Note: VS Code URI normalizes the drive letter. `C:` will be converted into `c:`. + // https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992 + private tempDirRealpath = isOSX + ? tempDir + : maybeNormalizeDrive(fs.realpathSync.native(tempDir)); @inject(ConfigService) protected readonly configService: ConfigService; @@ -43,116 +56,172 @@ export class SketchesServiceImpl extends CoreClientAware @inject(EnvVariablesServer) protected readonly envVariableServer: EnvVariablesServer; + async getSketches({ uri, exclude, }: { uri?: string; exclude?: string[]; - }): Promise { - const start = Date.now(); - let sketchbookPath: undefined | string; - if (!uri) { - const { sketchDirUri } = await this.configService.getConfiguration(); - sketchbookPath = FileUri.fsPath(sketchDirUri); - if (!(await promisify(fs.exists)(sketchbookPath))) { - await promisify(fs.mkdir)(sketchbookPath, { recursive: true }); - } - } else { - sketchbookPath = FileUri.fsPath(uri); - } - const container: SketchContainerWithDetails = { - label: uri ? path.basename(sketchbookPath) : 'Sketchbook', - sketches: [], - children: [], - }; - if (!(await promisify(fs.exists)(sketchbookPath))) { - return container; - } - const stat = await promisify(fs.stat)(sketchbookPath); - if (!stat.isDirectory()) { - return container; - } + }): Promise { + const [/*old,*/ _new] = await Promise.all([ + // this.getSketchesOld({ uri, exclude }), + this.getSketchesNew({ uri, exclude }), + ]); + return _new; + } - const recursivelyLoad = async ( - fsPath: string, - containerToLoad: SketchContainerWithDetails - ) => { - const filenames = await promisify(fs.readdir)(fsPath); - for (const name of filenames) { - const childFsPath = path.join(fsPath, name); - let skip = false; - for (const pattern of exclude || [ - '**/libraries/**', - '**/hardware/**', - ]) { - if (!skip && minimatch(childFsPath, pattern)) { - skip = true; - } - } - if (skip) { - continue; - } - try { - const stat = await promisify(fs.stat)(childFsPath); - if (stat.isDirectory()) { - const sketch = await this._isSketchFolder( - FileUri.create(childFsPath).toString() - ); - if (sketch) { - containerToLoad.sketches.push({ - ...sketch, - mtimeMs: stat.mtimeMs, - }); + @duration() + async getSketchesNew({ + uri, + exclude, + }: { + uri?: string; + exclude?: string[]; + }): Promise { + const root = await this.root(uri); + const pathToAllSketchFiles = await new Promise( + (resolve, reject) => { + glob( + '/!(libraries|hardware)/**/*.{ino,pde}', + { root }, + (error, results) => { + if (error) { + reject(error); } else { - const childContainer: SketchContainerWithDetails = { - label: name, - children: [], - sketches: [], - }; - await recursivelyLoad(childFsPath, childContainer); - if (!SketchContainer.isEmpty(childContainer)) { - containerToLoad.children.push(childContainer); - } + resolve(results); } } - } catch { - console.warn(`Could not load sketch from ${childFsPath}.`); - } + ); + } + ); + // Sort by path length to filter out nested sketches, such as the `Nested_folder` inside the `Folder` sketch. + // + // `directories#user` + // | + // +--Folder + // | + // +--Folder.ino + // | + // +--Nested_folder + // | + // +--Nested_folder.ino + pathToAllSketchFiles.sort((left, right) => left.length - right.length); + const container = SketchContainer.create( + uri ? path.basename(root) : 'Sketchbook' + ); + const getOrCreateChildContainer = ( + parent: SketchContainer, + segments: string[] + ) => { + if (segments.length === 1) { + throw new Error( + `Expected at least two segments relative path: ['ExampleSketchName', 'ExampleSketchName.{ino,pde}]. Was: ${segments}` + ); + } + if (segments.length === 2) { + return parent; } - containerToLoad.sketches.sort( - (left, right) => right.mtimeMs - left.mtimeMs + const label = segments[0]; + const existingSketch = parent.sketches.find( + (sketch) => sketch.name === label ); - return containerToLoad; + if (existingSketch) { + // If the container has a sketch with the same label, it cannot have a child container. + // See above example about how to ignore nested sketches. + return undefined; + } + let child = parent.children.find((child) => child.label === label); + if (!child) { + child = SketchContainer.create(label); + parent.children.push(child); + } + return child; }; - - await recursivelyLoad(sketchbookPath, container); - SketchContainer.prune(container); - console.debug( - `Loading the sketches from ${sketchbookPath} took ${ - Date.now() - start - } ms.` - ); + for (const pathToSketchFile of pathToAllSketchFiles) { + const relative = path.relative(root, pathToSketchFile); + if (!relative) { + console.warn( + `Could not determine relative sketch path from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping. Relative path was: ${relative}` + ); + continue; + } + const segments = relative.split(path.sep); + if (segments.length < 2) { + // folder name, and sketch name. + console.warn( + `Expected at least one segment relative path from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping. Segments were: ${segments}.` + ); + continue; + } + // the folder name and the sketch name must match. For example, `Foo/foo.ino` is invalid. + // drop the folder name from the sketch name, if `.ino` or `.pde` remains, it's valid + const sketchName = segments[segments.length - 2]; + const sketchFilename = segments[segments.length - 1]; + const sketchFileExtension = segments[segments.length - 1].replace( + new RegExp(sketchName), + '' + ); + if (sketchFileExtension !== '.ino' && sketchFileExtension !== '.pde') { + console.warn( + `Mismatching sketch file <${sketchFilename}> and sketch folder name <${sketchName}>. Skipping` + ); + continue; + } + const child = getOrCreateChildContainer(container, segments); + if (child) { + child.sketches.push({ + name: sketchName, + uri: FileUri.create(pathToSketchFile).toString(), + }); + } + } return container; } + private async root(uri?: string | undefined): Promise { + return FileUri.fsPath(uri ?? (await this.sketchbookUri())); + } + + private async sketchbookUri(): Promise { + const { sketchDirUri } = await this.configService.getConfiguration(); + return sketchDirUri; + } + async loadSketch(uri: string): Promise { - await this.coreClientProvider.initialized; const { client, instance } = await this.coreClient(); const req = new LoadSketchRequest(); - req.setSketchPath(FileUri.fsPath(uri)); + const requestSketchPath = FileUri.fsPath(uri); + req.setSketchPath(requestSketchPath); req.setInstance(instance); + const stat = new Deferred(); + fs.lstat(requestSketchPath, (err, result) => + err ? stat.resolve(err) : stat.resolve(result) + ); const sketch = await new Promise((resolve, reject) => { client.loadSketch(req, async (err, resp) => { if (err) { reject(err); return; } - const sketchFolderPath = resp.getLocationPath(); - const { mtimeMs } = await promisify(fs.lstat)(sketchFolderPath); + const responseSketchPath = maybeNormalizeDrive(resp.getLocationPath()); + if (requestSketchPath !== responseSketchPath) { + console.warn( + `Warning! The request sketch path was different than the response sketch path from the CLI. This could be a potential bug. Request: <${requestSketchPath}>, response: <${responseSketchPath}>.` + ); + } + const resolvedStat = await stat.promise; + if (resolvedStat instanceof Error) { + console.error( + `The CLI could load the sketch from ${requestSketchPath}, but stating the folder has failed.` + ); + reject(resolvedStat); + return; + } + const { mtimeMs } = resolvedStat; resolve({ - name: path.basename(sketchFolderPath), - uri: FileUri.create(sketchFolderPath).toString(), + name: path.basename(responseSketchPath), + uri: FileUri.create(responseSketchPath).toString(), mainFileUri: FileUri.create(resp.getMainFile()).toString(), otherSketchFileUris: resp .getOtherSketchFilesList() @@ -292,12 +361,18 @@ export class SketchesServiceImpl extends CoreClientAware ]; const today = new Date(); const parentPath = await new Promise((resolve, reject) => { - temp.mkdir({ prefix }, (err, dirPath) => { - if (err) { - reject(err); + temp.mkdir({ prefix }, (createError, dirPath) => { + if (createError) { + reject(createError); return; } - resolve(dirPath); + fs.realpath.native(dirPath, (resolveError, resolvedDirPath) => { + if (resolveError) { + reject(resolveError); + return; + } + resolve(resolvedDirPath); + }); }); }); const sketchBaseName = `sketch_${ @@ -395,20 +470,21 @@ void loop() { return undefined; } - async isTemp(sketch: Sketch): Promise { - let sketchPath = FileUri.fsPath(sketch.uri); - let temp = await promisify(fs.realpath)(os.tmpdir()); - // Note: VS Code URI normalizes the drive letter. `C:` will be converted into `c:`. - // https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992 - if (isWindows) { - if (WIN32_DRIVE_REGEXP.exec(sketchPath)) { - sketchPath = firstToLowerCase(sketchPath); - } - if (WIN32_DRIVE_REGEXP.exec(temp)) { - temp = firstToLowerCase(temp); - } - } - return sketchPath.indexOf(prefix) !== -1 && sketchPath.startsWith(temp); + async isTemp(sketch: SketchRef): Promise { + // Consider the following paths: + // macOS: + // - Temp folder: /var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T + // - Sketch folder: /private/var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-A0337D47F86B24A51DF3DBCF2CC17925 + // Windows: + // - Temp folder: C:\Users\KITTAA~1\AppData\Local\Temp + // - Sketch folder: c:\Users\kittaakos\AppData\Local\Temp\.arduinoIDE-unsaved2022431-21824-116kfaz.9ljl\sketch_may31a + // Both sketches are valid and temp, but this function will give a false-negative result if we use the default `os.tmpdir()` logic. + const sketchPath = maybeNormalizeDrive(FileUri.fsPath(sketch.uri)); + const tempPath = this.tempDirRealpath; // https://github.com/sindresorhus/temp-dir + const result = + sketchPath.indexOf(prefix) !== -1 && sketchPath.startsWith(tempPath); + console.log('isTemp?', result, sketch.uri); + return result; } async copy( @@ -512,10 +588,15 @@ void loop() { interface SketchWithDetails extends Sketch { readonly mtimeMs: number; } -interface SketchContainerWithDetails extends SketchContainer { - readonly label: string; - readonly children: SketchContainerWithDetails[]; - readonly sketches: SketchWithDetails[]; + +/** + * If on Windows, will change the input `C:\\path\\to\\somewhere` to `c:\\path\\to\\somewhere`. + */ +function maybeNormalizeDrive(input: string): string { + if (isWindows && WIN32_DRIVE_REGEXP.test(input)) { + return firstToLowerCase(input); + } + return input; } /* @@ -523,7 +604,7 @@ interface SketchContainerWithDetails extends SketchContainer { * from other new sketches I created today. * If 'sketch_jul8a' is already used, go with 'sketch_jul8b'. * If 'sketch_jul8b' already used, go with 'sketch_jul8c'. - * When it reacheas 'sketch_jul8z', go with 'sketch_jul8aa', + * When it reach 'sketch_jul8z', go with 'sketch_jul8aa', * and so on. */ function sketchIndexToLetters(num: number): string { diff --git a/arduino-ide-extension/src/node/theia/core/backend-application.ts b/arduino-ide-extension/src/node/theia/core/backend-application.ts index 6346d5e3c..1b7d6f927 100644 --- a/arduino-ide-extension/src/node/theia/core/backend-application.ts +++ b/arduino-ide-extension/src/node/theia/core/backend-application.ts @@ -11,9 +11,9 @@ export class BackendApplication extends TheiaBackendApplication { constructor( @inject(ContributionProvider) @named(BackendApplicationContribution) - protected readonly contributionsProvider: ContributionProvider, + protected override readonly contributionsProvider: ContributionProvider, @inject(BackendApplicationCliContribution) - protected readonly cliParams: BackendApplicationCliContribution + protected override readonly cliParams: BackendApplicationCliContribution ) { super(contributionsProvider, cliParams); // Workaround for Electron not installing a handler to ignore SIGPIPE diff --git a/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts b/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts index 819ce028b..7380cb71e 100644 --- a/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts +++ b/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts @@ -7,7 +7,7 @@ import { EnvVariablesServerImpl as TheiaEnvVariablesServerImpl } from '@theia/co @injectable() export class EnvVariablesServer extends TheiaEnvVariablesServerImpl { - protected readonly configDirUri = Promise.resolve( + protected override readonly configDirUri = Promise.resolve( FileUri.create( join(homedir(), BackendApplicationConfigProvider.get().configDirName) ).toString() diff --git a/arduino-ide-extension/src/node/theia/git/git-init.ts b/arduino-ide-extension/src/node/theia/git/git-init.ts deleted file mode 100644 index 56bcc02f1..000000000 --- a/arduino-ide-extension/src/node/theia/git/git-init.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import findGit from 'find-git-exec'; -import { dirname } from 'path'; -import { pathExists } from 'fs-extra'; -import { GitInit } from '@theia/git/lib/node/init/git-init'; -import { DisposableCollection } from '@theia/core/lib/common/disposable'; - -@injectable() -export class DefaultGitInit implements GitInit { - protected readonly toDispose = new DisposableCollection(); - - async init(): Promise { - const { env } = process; - try { - const { execPath, path, version } = await findGit(); - if (!!execPath && !!path && !!version) { - const dir = dirname(dirname(path)); - const [execPathOk, pathOk, dirOk] = await Promise.all([ - pathExists(execPath), - pathExists(path), - pathExists(dir), - ]); - if (execPathOk && pathOk && dirOk) { - if ( - typeof env.LOCAL_GIT_DIRECTORY !== 'undefined' && - env.LOCAL_GIT_DIRECTORY !== dir - ) { - console.error( - `Misconfigured env.LOCAL_GIT_DIRECTORY: ${env.LOCAL_GIT_DIRECTORY}. dir was: ${dir}` - ); - return; - } - if ( - typeof env.GIT_EXEC_PATH !== 'undefined' && - env.GIT_EXEC_PATH !== execPath - ) { - console.error( - `Misconfigured env.GIT_EXEC_PATH: ${env.GIT_EXEC_PATH}. execPath was: ${execPath}` - ); - return; - } - process.env.LOCAL_GIT_DIRECTORY = dir; - process.env.GIT_EXEC_PATH = execPath; - console.info(`Using Git [${version}] from the PATH. (${path})`); - return; - } - } - } catch (err) { - console.error(err); - } - } - - dispose(): void { - this.toDispose.dispose(); - } -} diff --git a/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts b/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts index fd322ca4c..3481f7de1 100644 --- a/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts +++ b/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts @@ -1,7 +1,10 @@ +import { promises as fs, constants } from 'fs'; import { injectable, inject } from '@theia/core/shared/inversify'; import { ILogger } from '@theia/core/lib/common/logger'; import { DefaultWorkspaceServer as TheiaDefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server'; import { ConfigService } from '../../../common/protocol/config-service'; +import { SketchesService } from '../../../common/protocol'; +import { FileUri } from '@theia/core/lib/node'; @injectable() export class DefaultWorkspaceServer extends TheiaDefaultWorkspaceServer { @@ -11,13 +14,49 @@ export class DefaultWorkspaceServer extends TheiaDefaultWorkspaceServer { @inject(ILogger) protected readonly logger: ILogger; - protected async getWorkspaceURIFromCli(): Promise { + @inject(SketchesService) + private readonly sketchesService: SketchesService; + + override async onStart(): Promise { + // NOOP + // No need to remove untitled workspaces. IDE2 does not use workspaces. + } + + override async getMostRecentlyUsedWorkspace(): Promise { + const uri = await super.getMostRecentlyUsedWorkspace(); + if (!uri) { + const { uri } = await this.sketchesService.createNewSketch(); + return uri; + } + return uri; + } + + /** + * This is the async re-implementation of the default Theia behavior. + */ + override async getRecentWorkspaces(): Promise { + const listUri: string[] = []; + const data = await this.readRecentWorkspacePathsFromUserHome(); + if (data && data.recentRoots) { + await Promise.all( + data.recentRoots + .filter((element) => Boolean(element)) + .map(async (element) => { + if (await this.exists(element)) { + listUri.push(element); + } + }) + ); + } + return listUri; + } + + private async exists(uri: string): Promise { try { - const config = await this.configService.getConfiguration(); - return config.sketchDirUri; - } catch (err) { - this.logger.error(`Failed to determine the sketch directory: ${err}`); - return super.getWorkspaceURIFromCli(); + await fs.access(FileUri.fsPath(uri), constants.R_OK | constants.W_OK); + return true; + } catch { + return false; } } } diff --git a/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts b/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts index 0fd6cf4d9..b0f44b701 100644 --- a/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts +++ b/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts @@ -16,15 +16,15 @@ class SilentArduinoDaemonImpl extends ArduinoDaemonImpl { super(); } - onData(data: string): void { + override onData(data: string): void { // NOOP } - async spawnDaemonProcess(): Promise<{ daemon: ChildProcess; port: string }> { + override async spawnDaemonProcess(): Promise<{ daemon: ChildProcess; port: string }> { return super.spawnDaemonProcess(); } - protected async getSpawnArgs(): Promise { + protected override async getSpawnArgs(): Promise { const cliConfigPath = await this.initCliConfig(); return [ 'daemon', diff --git a/arduino-ide-extension/src/test/node/cli-config.test.ts b/arduino-ide-extension/src/test/node/cli-config.test.ts index d21f56879..3d09ef2ee 100644 --- a/arduino-ide-extension/src/test/node/cli-config.test.ts +++ b/arduino-ide-extension/src/test/node/cli-config.test.ts @@ -19,7 +19,6 @@ describe('cli-config', () => { [ () => { const conf = defaultConfig(); - (conf.daemon as any).port = String(conf.daemon.port); return conf; }, defaultConfig, @@ -41,12 +40,8 @@ describe('cli-config', () => { board_manager: { additional_urls: [], }, - daemon: { - port: 5000, - }, directories: { data: 'data', - downloads: 'downloads', user: 'user', }, }; diff --git a/arduino-ide-extension/tsconfig.json b/arduino-ide-extension/tsconfig.json index e35c848fa..24604fe4c 100644 --- a/arduino-ide-extension/tsconfig.json +++ b/arduino-ide-extension/tsconfig.json @@ -6,6 +6,7 @@ "noEmitOnError": true, "noImplicitThis": true, "noUnusedLocals": true, + "noImplicitOverride": true, "strictNullChecks": true, "experimentalDecorators": true, "downlevelIteration": true, diff --git a/electron/build/template-package.json b/electron/build/template-package.json index 3100d1e89..caa8087cd 100644 --- a/electron/build/template-package.json +++ b/electron/build/template-package.json @@ -3,9 +3,7 @@ "author": "Arduino SA", "resolutions": { "**/fs-extra": "^4.0.3", - "electron-builder": "23.0.2", - "find-git-exec": "0.0.4", - "dugite-extra": "0.1.15" + "electron-builder": "23.0.2" }, "dependencies": { "node-log-rotate": "^0.1.5" diff --git a/i18n/en.json b/i18n/en.json index 3bd7251da..09659b6fb 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -345,8 +345,7 @@ "fileNewName": "Name for new file", "invalidExtension": ".{0} is not a valid extension", "invalidFilename": "Invalid filename.", - "newFileName": "New name for file", - "sketchDirectoryError": "There was an error creating the sketch directory. See the log for more details. The application will probably not work as expected." + "newFileName": "New name for file" } } } diff --git a/package.json b/package.json index f51038496..3d7fbd15e 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,6 @@ "xhr2": "^0.2.1" }, "resolutions": { - "find-git-exec": "0.0.4", - "dugite-extra": "0.1.15", "@types/react": "16.14.25" }, "scripts": { diff --git a/yarn.lock b/yarn.lock index 08bab7fb9..b53bf4248 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1105,7 +1105,7 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@grpc/grpc-js@^1.3.7": +"@grpc/grpc-js@^1.6.7": version "1.6.7" resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.7.tgz#4c4fa998ff719fe859ac19fe977fdef097bb99aa" integrity sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw== @@ -1870,7 +1870,7 @@ dependencies: cross-spawn "^7.0.1" -"@mapbox/node-pre-gyp@^1.0.4", "@mapbox/node-pre-gyp@^1.0.5": +"@mapbox/node-pre-gyp@^1.0.5": version "1.0.9" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc" integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw== @@ -2638,30 +2638,6 @@ uuid "^8.0.0" vscode-languageserver-textdocument "^1.0.1" -"@theia/git@1.25.0": - version "1.25.0" - resolved "https://registry.yarnpkg.com/@theia/git/-/git-1.25.0.tgz#f1c028d7432ebca8622e296aa1139bcb7fe9fd4d" - integrity sha512-UB/m2vt+WEJZS81PZ2aOXbIDSVNtith/V3o8H41XcxZ25dYwbpC0F6Ln4br15vGOASGBKFPzAwBeNXDyEAOZkw== - dependencies: - "@theia/core" "1.25.0" - "@theia/editor" "1.25.0" - "@theia/filesystem" "1.25.0" - "@theia/monaco-editor-core" "1.65.2" - "@theia/navigator" "1.25.0" - "@theia/scm" "1.25.0" - "@theia/scm-extra" "1.25.0" - "@theia/workspace" "1.25.0" - "@types/diff" "^3.2.2" - "@types/p-queue" "^2.3.1" - diff "^3.4.0" - dugite-extra "0.1.15" - find-git-exec "^0.0.4" - find-git-repositories "^0.1.1" - moment "2.29.2" - octicons "^7.1.0" - p-queue "^2.4.2" - ts-md5 "^1.2.2" - "@theia/keymaps@1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@theia/keymaps/-/keymaps-1.25.0.tgz#0adfb6f088a532aa3c2f05448ac5697e6a16f5ad" @@ -2860,17 +2836,6 @@ node-pty "0.11.0-beta17" string-argv "^0.1.1" -"@theia/scm-extra@1.25.0": - version "1.25.0" - resolved "https://registry.yarnpkg.com/@theia/scm-extra/-/scm-extra-1.25.0.tgz#0ab22c77ef5918e35c44c3750fb46202fad26cbd" - integrity sha512-n4slV6Reb/zjtf9gZCs0l9pFmixi5gY+ut0Du1ZN4sqGJvSF5bfEx6567CbqX1zitsMTr2/dSLReS/Rqsu8flg== - dependencies: - "@theia/core" "1.25.0" - "@theia/editor" "1.25.0" - "@theia/filesystem" "1.25.0" - "@theia/navigator" "1.25.0" - "@theia/scm" "1.25.0" - "@theia/scm@1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@theia/scm/-/scm-1.25.0.tgz#e9765fc09979508b77bcfecd28dda8d8c4e6c8b0" @@ -3016,14 +2981,6 @@ dependencies: "@types/node" "*" -"@types/bytebuffer@^5.0.40": - version "5.0.43" - resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.43.tgz#b5259fca1412106bcee0cabfbf7c104846d06738" - integrity sha512-vQnTYvy4LpSojHjKdmg4nXFI1BAiYPvZ/k3ouczZAQnbDprk1xqxJiFmFHyy8y6MuUq3slz5erNMtn6n87uVKw== - dependencies: - "@types/long" "*" - "@types/node" "*" - "@types/cacheable-request@^6.0.1": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" @@ -3161,7 +3118,7 @@ dependencies: "@types/node" "*" -"@types/glob@*", "@types/glob@^7.1.1": +"@types/glob@*", "@types/glob@^7.1.1", "@types/glob@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== @@ -3169,14 +3126,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/glob@^5.0.35": - version "5.0.37" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.37.tgz#d0982abc88f9aebbd62099d3d70440cbcea692de" - integrity sha512-ATA/xrS7CZ3A2WCPVY4eKdNpybq56zqlTirnHhhyOztZM/lPxJzusOBI3BsaXbu6FrUluqzvMlI4sZ6BDYMlMg== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - "@types/google-protobuf@^3.7.2": version "3.15.6" resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.6.tgz#674a69493ef2c849b95eafe69167ea59079eb504" @@ -3264,7 +3213,7 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== -"@types/long@*", "@types/long@^4.0.1": +"@types/long@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== @@ -3351,11 +3300,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef" integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA== -"@types/node@^10.14.22": - version "10.17.60" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" - integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== - "@types/node@^14.6.2": version "14.18.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.18.tgz#5c9503030df484ccffcbb935ea9a9e1d6fad1a20" @@ -3591,7 +3535,7 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.5.tgz#b1d2f772142a301538fae9bdf9cf15b9f2573a29" integrity sha512-hKB88y3YHL8oPOs/CNlaXtjWn93+Bs48sDQR37ZUqG2tLeCS7EA1cmnkKsuQsub9OKEB/y/Rw9zqJqqNSbqVlQ== -"@types/which@^1.3.1", "@types/which@^1.3.2": +"@types/which@^1.3.1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf" integrity sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA== @@ -4470,14 +4414,6 @@ asap@^2.0.0: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== -ascli@~1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ascli/-/ascli-1.0.1.tgz#bcfa5974a62f18e81cabaeb49732ab4a88f906bc" - integrity sha512-JGQaNxpaCJz9Bd1JvVaFIHuWn9S+l3xhN17R0V/vmUDiGE0QngNMXhjlqpwqV+91plWz9Fg+Lt28Lj7p5vjs8A== - dependencies: - colour "~0.7.1" - optjs "~3.2.2" - asn1@~0.2.3: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" @@ -4961,13 +4897,6 @@ byte-size@^5.0.1: resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191" integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw== -bytebuffer@~5: - version "5.0.1" - resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" - integrity sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ== - dependencies: - long "~3" - bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -5177,7 +5106,7 @@ camelcase-keys@^6.2.2: map-obj "^4.0.0" quick-lru "^4.0.1" -camelcase@^2.0.0, camelcase@^2.0.1: +camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw== @@ -5206,15 +5135,6 @@ capital-case@^1.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" -capital-case@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/capital-case/-/capital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669" - integrity sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - upper-case-first "^2.0.2" - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -5272,7 +5192,7 @@ chalk@^2.0.0, chalk@^2.3.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -5306,16 +5226,6 @@ change-case@^4.1.2: snake-case "^3.0.4" tslib "^2.0.3" -changes-stream@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/changes-stream/-/changes-stream-2.2.0.tgz#9cf2bdbc2173c29c634aec9948e5d23b24d37c18" - integrity sha1-nPK9vCFzwpxjSuyZSOXSOyTTfBg= - dependencies: - back "~0.1.5" - debug "~0.8.0" - http-https "~1.0.0" - readable-stream "1.0.x" - character-entities@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.1.tgz#98724833e1e27990dee0bd0f2b8a859c3476aac7" @@ -5331,13 +5241,6 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== -checksum@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/checksum/-/checksum-0.1.1.tgz#dc6527d4c90be8560dbd1ed4cecf3297d528e9e9" - integrity sha512-xWkkJpoWQ6CptWw2GvtoQbScL3xtvGjoqvHpALE7B0tSHxSw0ex0tlsKOKkbETaOYGBhMliAyscestDyAZIN9g== - dependencies: - optimist "~0.3.5" - chokidar@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" @@ -5430,15 +5333,6 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== -cliui@^3.0.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - cliui@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" @@ -5577,11 +5471,6 @@ colorette@^2.0.16: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== -colour@~0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" - integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g= - columnify@^1.5.4: version "1.6.0" resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" @@ -5612,7 +5501,7 @@ commander@^7.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^8.3.0: +commander@^8.2.0, commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== @@ -5717,11 +5606,6 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control- resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -content-disposition@0.5.4, content-disposition@^0.5.2: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - constant-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1" @@ -5731,6 +5615,13 @@ constant-case@^3.0.4: tslib "^2.0.3" upper-case "^2.0.2" +content-disposition@0.5.4, content-disposition@^0.5.2: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -6009,11 +5900,6 @@ crypto-js@^4.1.1: resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== -css-element-queries@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/css-element-queries/-/css-element-queries-1.2.3.tgz#e14940b1fcd4bf0da60ea4145d05742d7172e516" - integrity sha512-QK9uovYmKTsV2GXWQiMOByVNrLn2qz6m3P7vWpOR4IdD6I3iXoDw5qtgJEN3Xq7gIbdHVKvzHjdAtcl+4Arc4Q== - css-loader@^6.2.0: version "6.7.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" @@ -6481,14 +6367,6 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - dot-prop@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4" @@ -6538,28 +6416,6 @@ drivelist@^9.0.2: nan "^2.14.0" prebuild-install "^5.2.4" -dugite-extra@0.1.15: - version "0.1.15" - resolved "https://registry.yarnpkg.com/dugite-extra/-/dugite-extra-0.1.15.tgz#322406b628ea5515c5c6fcd65e4d040543d6268a" - integrity sha512-beLmQcIXLA8aXqWQZF/ooECoZvYKpBywIFwgqAoYnV04NdWUXDtZ6mMcjQf5eAz5PjXGXAYSuQ31zkPL8J85+A== - dependencies: - byline "^5.0.0" - dugite-no-gpl "1.69.0" - find-git-exec "^0.0.4" - upath "^2.0.1" - -dugite-no-gpl@1.69.0: - version "1.69.0" - resolved "https://registry.yarnpkg.com/dugite-no-gpl/-/dugite-no-gpl-1.69.0.tgz#bc9007cf5a595180f563ccc0e4f2cc80ebbaa52e" - integrity sha512-9NzPMyWW1uWEm+rEGivfQ0+zZ9soXrtk/zb6FIVpPa5CLoUdhMkLY4jHc0DDyayarxivJgrI/rHDdTUej4Zhrw== - dependencies: - checksum "^0.1.1" - mkdirp "^0.5.1" - progress "^2.0.0" - request "^2.86.0" - rimraf "^2.5.4" - tar "^4.0.2" - duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -7332,7 +7188,7 @@ fast-plist@^0.1.2: resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= -fast-safe-stringify@^2.0.7: +fast-safe-stringify@^2.0.7, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -7477,22 +7333,6 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-git-exec@0.0.4, find-git-exec@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/find-git-exec/-/find-git-exec-0.0.4.tgz#f1d0d35f93ad99bc81aacd357388d00ae902bc92" - integrity sha512-klzQwno+dpdeahtHhvZZ5Yn6K+zme1Aj+YJ4ZD+DywSLrQoyCywTrsubUZa1hHRehmfwBThoeKjS7fsaxhpfNA== - dependencies: - "@types/node" "^10.14.22" - "@types/which" "^1.3.2" - which "^2.0.1" - -find-git-repositories@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/find-git-repositories/-/find-git-repositories-0.1.3.tgz#4e83e085b20cb3e393c1e091adc3a3eec50b6dda" - integrity sha512-6q8ZIQ7loe0eWbz1O79J0gQ2wVpQ1ajsjV64HC2iJ7gOOqlEuDlG/T0xYr5gDYBFSHlS8dah1KGbndiWWdJ0PA== - dependencies: - nan "^2.14.0" - find-root@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -8024,7 +7864,7 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: +glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -8131,7 +7971,7 @@ google-protobuf@3.12.4: resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.12.4.tgz#fd89b7e5052cdb35a80f9b455612851d542a5c9f" integrity sha512-ItTn8YepDQMHEMHloUPH+FDaTPiHTnbsMvP50aXfbI65IK3AA5+wXlHSygJH8xz+h1g4gu7V+CK5X1/SaGITsA== -google-protobuf@^3.11.4: +google-protobuf@^3.20.1: version "3.20.1" resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.20.1.tgz#1b255c2b59bcda7c399df46c65206aa3c7a0ce8b" integrity sha512-XMf1+O32FjYIV3CYu6Tuh5PNbfNEU5Xu22X+Xkdb/DUexFlCzhvv7d5Iirm4AOwn8lv4al1YvIhzGrg2j9Zfzw== @@ -8210,18 +8050,6 @@ grpc-tools@^1.9.0: dependencies: "@mapbox/node-pre-gyp" "^1.0.5" -grpc@^1.24.11: - version "1.24.11" - resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.24.11.tgz#7039da9f6f22ce35168535a6d5dda618398a5966" - integrity sha512-8/AQdFCzCeCDWW3SoaMNp6ccbRvTQEH1O1u1uFtt29eWsg5gSZCJ3m6fbkduEIh3smY7WAPP+LgVJ5n3nZRxcA== - dependencies: - "@mapbox/node-pre-gyp" "^1.0.4" - "@types/bytebuffer" "^5.0.40" - lodash.camelcase "^4.3.0" - lodash.clone "^4.5.0" - nan "^2.13.2" - protobufjs "^5.0.3" - grpc_tools_node_protoc_ts@^4.1.0: version "4.1.5" resolved "https://registry.yarnpkg.com/grpc_tools_node_protoc_ts/-/grpc_tools_node_protoc_ts-4.1.5.tgz#ad540a51867ff407196538d2d6370b27d6d3cfc8" @@ -8856,11 +8684,6 @@ inversify@^5.1.1: resolved "https://registry.yarnpkg.com/inversify/-/inversify-5.1.1.tgz#6fbd668c591337404e005a1946bfe0d802c08730" integrity sha512-j8grHGDzv1v+8T1sAQ+3boTCntFPfvxLCkNcxB1J8qA0lUN+fAlSyYd+RXKvaPRL4AGyPxViutBEJHNXOyUdFQ== -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - invert-kv@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" @@ -9506,17 +9329,7 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - -json5@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - -json5@^2.2.0: +json5@^2.1.2, json5@^2.2.0, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -9659,13 +9472,6 @@ lazy-val@^1.0.5: resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - lcid@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" @@ -9880,11 +9686,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.clone@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" - integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= - lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -10024,11 +9825,6 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -long@~3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" - integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= - loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -10901,11 +10697,6 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment@2.29.2: - version "2.29.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" - integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== - moment@^2.18.1, moment@^2.24.0: version "2.29.3" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" @@ -11009,7 +10800,7 @@ mz@^2.5.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.13.2, nan@^2.14.0: +nan@^2.14.0: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== @@ -11103,10 +10894,6 @@ nise@^5.1.0: just-extend "^4.0.2" path-to-regexp "^1.7.0" -node-abi@*, node-abi@^3.0.0, node-abi@^3.3.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.15.0.tgz#cd9ac8c58328129b49998cc6fa16aa5506152716" - integrity sha512-Ic6z/j6I9RLm4ov7npo1I48UQr2BEyFCqh6p7S1dhEx9jPO0GPGq/e2Rb7x7DroQrmiVMz/Bw1vJm9sPAl2nxA== no-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" @@ -11129,6 +10916,13 @@ node-abi@^2.21.0, node-abi@^2.7.0: dependencies: semver "^5.4.1" +node-abi@^3.0.0, node-abi@^3.3.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.15.0.tgz#cd9ac8c58328129b49998cc6fa16aa5506152716" + integrity sha512-Ic6z/j6I9RLm4ov7npo1I48UQr2BEyFCqh6p7S1dhEx9jPO0GPGq/e2Rb7x7DroQrmiVMz/Bw1vJm9sPAl2nxA== + dependencies: + semver "^7.3.5" + node-addon-api@*: version "5.0.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" @@ -11547,13 +11341,6 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" -octicons@^7.1.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/octicons/-/octicons-7.4.0.tgz#0be0082ed75b81e680800ef978bf47078b670091" - integrity sha512-j53BDX+FpJ4DQwENARbk9hHkwG/Oaq5NPUMNzYdGxRA/R5M6BbPVQEakUVMNKLzvzPue/gEEUTtSj6utFse5QQ== - dependencies: - object-assign "^4.1.1" - octokit-pagination-methods@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" @@ -11610,13 +11397,6 @@ open@^8.0.6: is-docker "^2.1.1" is-wsl "^2.2.0" -optimist@~0.3.5: - version "0.3.7" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9" - integrity sha1-yQlBrVnkJzMokjB00s8ufLxuwNk= - dependencies: - wordwrap "~0.0.2" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -11641,11 +11421,6 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -optjs@~3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee" - integrity sha1-aabOicRCpEQDFBrS+bNwvVu29O4= - ora@^5.1.0: version "5.4.1" resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" @@ -11666,13 +11441,6 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - os-locale@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" @@ -12381,16 +12149,6 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -protobufjs@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-5.0.3.tgz#e4dfe9fb67c90b2630d15868249bcc4961467a17" - integrity sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA== - dependencies: - ascli "~1" - bytebuffer "~5" - glob "^7.0.5" - yargs "^3.10.0" - protobufjs@^6.10.0: version "6.11.2" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b" @@ -13126,7 +12884,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.82.0, request@^2.86.0, request@^2.87.0, request@^2.88.0: +request@^2.82.0, request@^2.87.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -13499,15 +13257,6 @@ sentence-case@^3.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" -sentence-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f" - integrity sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - upper-case-first "^2.0.2" - serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -13732,14 +13481,6 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" -snake-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" - integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -14474,7 +14215,7 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.0.0, tar@^4.0.2, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: +tar@^4.0.0, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: version "4.4.19" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== @@ -14504,6 +14245,11 @@ temp-dir@^1.0.0: resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + temp-write@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492" @@ -14774,7 +14520,7 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.1.0, tslib@^2.3.1: +tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== @@ -15152,11 +14898,6 @@ upath@^1.1.2, upath@^1.2.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -upath@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" - integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== - upper-case-first@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324" @@ -15594,11 +15335,6 @@ winchan@^0.2.2: resolved "https://registry.yarnpkg.com/winchan/-/winchan-0.2.2.tgz#6766917b88e5e1cb75f455ffc7cc13f51e5c834e" integrity sha512-pvN+IFAbRP74n/6mc6phNyCH8oVkzXsto4KCHPJ2AScniAnA1AmeLI03I2BzjePpaClGSI4GUMowzsD3qz5PRQ== -window-size@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" - integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= - windows-release@^3.1.0: version "3.3.3" resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" @@ -15616,11 +15352,6 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - worker-loader@^3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37" @@ -15787,7 +15518,7 @@ xterm@^4.16.0: resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0.tgz#a1f6ab2c330c3918fb094ae5f4c2562987398ea1" integrity sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ== -y18n@^3.2.0, y18n@^3.2.1: +y18n@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== @@ -15966,19 +15697,6 @@ yargs@^17.0.1: y18n "^5.0.5" yargs-parser "^21.0.0" -yargs@^3.10.0: - version "3.32.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" - integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= - dependencies: - camelcase "^2.0.1" - cliui "^3.0.3" - decamelize "^1.1.1" - os-locale "^1.4.0" - string-width "^1.0.1" - window-size "^0.1.4" - y18n "^3.2.0" - yauzl@^2.10.0, yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From 2c330ad88e42f0dc454ec16a8c2a0c0bef4449b5 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 7 Jun 2022 12:48:53 +0200 Subject: [PATCH 65/66] override coreClientProvider in monitor-service --- arduino-ide-extension/src/node/monitor-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arduino-ide-extension/src/node/monitor-service.ts b/arduino-ide-extension/src/node/monitor-service.ts index df2b3c8b5..086e98de7 100644 --- a/arduino-ide-extension/src/node/monitor-service.ts +++ b/arduino-ide-extension/src/node/monitor-service.ts @@ -79,7 +79,7 @@ export class MonitorService extends CoreClientAware implements Disposable { private readonly board: Board, private readonly port: Port, private readonly monitorID: string, - protected readonly coreClientProvider: CoreClientProvider + protected override readonly coreClientProvider: CoreClientProvider ) { super(); From cb8df1c9b86d8fa91631adb3cc09ce90893b556d Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 7 Jun 2022 15:28:25 +0200 Subject: [PATCH 66/66] cleanup merged code --- arduino-ide-extension/package.json | 2 +- .../src/browser/arduino-ide-frontend-module.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 2e9250548..93b9889cf 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -155,7 +155,7 @@ ], "arduino": { "cli": { - "version": "0.23.0-rc1" + "version": "0.23.0" }, "fwuploader": { "version": "2.2.0" diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index c22a20b07..9289218b5 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -543,8 +543,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TheiaEditorManager).to(EditorManager); - rebind(TheiaEditorManager).to(EditorManager); - // replace search icon rebind(TheiaSearchInWorkspaceFactory) .to(SearchInWorkspaceFactory)