From d0ed1bd4c30b7248ee375bfb81bf9f66dfd834fa Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Fri, 3 Jun 2022 13:28:47 +0200 Subject: [PATCH] Fixed LS stops working after OS sleep/wakeup cycle Signed-off-by: Akos Kitta --- .../browser/arduino-frontend-contribution.tsx | 14 +++- .../browser/arduino-ide-frontend-module.ts | 8 ++ .../src/browser/hosted-plugin-events.ts | 74 +++++++++++++++++++ .../browser/theia/plugin-ext/hosted-plugin.ts | 34 +++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 arduino-ide-extension/src/browser/hosted-plugin-events.ts create mode 100644 arduino-ide-extension/src/browser/theia/plugin-ext/hosted-plugin.ts diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 4d81f4fc8..d70f2a596 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -54,7 +54,6 @@ import { OutputContribution } from '@theia/output/lib/browser/output-contributio import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution'; import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution'; -import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; 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'; @@ -75,6 +74,7 @@ import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-wi 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'; +import { HostedPluginEvents } from './hosted-plugin-events'; const INIT_LIBS_AND_PACKAGES = 'initializedLibsAndPackages'; export const SKIP_IDE_VERSION = 'skipIDEVersion'; @@ -147,8 +147,8 @@ export class ArduinoFrontendContribution @inject(ConfigService) protected readonly configService: ConfigService; - @inject(HostedPluginSupport) - protected hostedPluginSupport: HostedPluginSupport; + @inject(HostedPluginEvents) + protected hostedPluginEvents: HostedPluginEvents; @inject(ExecutableService) protected executableService: ExecutableService; @@ -317,6 +317,12 @@ export class ArduinoFrontendContribution } }; this.boardsServiceClientImpl.onBoardsConfigChanged(start); + this.hostedPluginEvents.onPluginsDidStart(() => + start(this.boardsServiceClientImpl.boardsConfig) + ); + this.hostedPluginEvents.onPluginsWillUnload( + () => (this.languageServerFqbn = undefined) + ); this.arduinoPreferences.onPreferenceChanged((event) => { if (event.newValue !== event.oldValue) { switch (event.preferenceName) { @@ -371,7 +377,7 @@ export class ArduinoFrontendContribution ): Promise { const release = await this.languageServerStartMutex.acquire(); try { - await this.hostedPluginSupport.didStart; + await this.hostedPluginEvents.didStart; const details = await this.boardsService.getBoardDetails({ fqbn }); if (!details) { // Core is not installed for the selected board. 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 1f70245cb..d48bfd0c9 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -277,6 +277,9 @@ import { import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider'; import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser/editor-manager'; import { EditorManager } from './theia/editor/editor-manager'; +import { HostedPluginEvents } from './hosted-plugin-events'; +import { HostedPluginSupport } from './theia/plugin-ext/hosted-plugin'; +import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -805,4 +808,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { ); }) .inSingletonScope(); + + bind(HostedPluginSupport).toSelf().inSingletonScope(); + rebind(TheiaHostedPluginSupport).toService(HostedPluginSupport); + bind(HostedPluginEvents).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(HostedPluginEvents); }); diff --git a/arduino-ide-extension/src/browser/hosted-plugin-events.ts b/arduino-ide-extension/src/browser/hosted-plugin-events.ts new file mode 100644 index 000000000..ac7b1fe0d --- /dev/null +++ b/arduino-ide-extension/src/browser/hosted-plugin-events.ts @@ -0,0 +1,74 @@ +import { DisposableCollection, Emitter, Event } from '@theia/core'; +import { FrontendApplicationContribution } from '@theia/core/lib/browser'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { HostedPluginSupport } from './theia/plugin-ext/hosted-plugin'; + +/** + * Frontend contribution to watch VS Code extension start/stop events from Theia. + * + * In Theia, there are no events when a VS Code extension is loaded, started, unloaded, and stopped. + * Currently, it's possible to `@inject` the `HostedPluginSupport` service from Theia and `await` + * for the `didStart` promise to resolve. But if the OS goes to sleep, the VS Code extensions will + * be unloaded and loaded and started again when the OS awakes. Theia reloads the VS Code extensions + * after the OS awake event, but the `didStart` promise was already resolved, so IDE2 cannot restart the LS. + * This service is meant to work around the limitation of Theia and fire an event every time the VS Code extensions + * loaded and started. + */ +@injectable() +export class HostedPluginEvents implements FrontendApplicationContribution { + @inject(HostedPluginSupport) + private readonly hostedPluginSupport: HostedPluginSupport; + + private firstStart = true; + private readonly onPluginsDidStartEmitter = new Emitter(); + private readonly onPluginsWillUnloadEmitter = new Emitter(); + private readonly toDispose = new DisposableCollection( + this.onPluginsDidStartEmitter, + this.onPluginsWillUnloadEmitter + ); + + onStart(): void { + this.hostedPluginSupport.onDidLoad(() => { + // Fire the first event, when `didStart` resolves. + if (!this.firstStart) { + console.debug('HostedPluginEvents', "Received 'onDidLoad' event."); + this.onPluginsDidStartEmitter.fire(); + } else { + console.debug( + 'HostedPluginEvents', + "Received 'onDidLoad' event before the first start. Skipping." + ); + } + }); + this.hostedPluginSupport.didStart.then(() => { + console.debug('HostedPluginEvents', "Hosted plugins 'didStart'."); + if (!this.firstStart) { + throw new Error( + 'Unexpectedly received a `didStart` event after the first start.' + ); + } + this.firstStart = false; + this.onPluginsDidStartEmitter.fire(); + }); + this.hostedPluginSupport.onDidCloseConnection(() => { + console.debug('HostedPluginEvents', "Received 'onDidCloseConnection'."); + this.onPluginsWillUnloadEmitter.fire(); + }); + } + + onStop(): void { + this.toDispose.dispose(); + } + + get onPluginsDidStart(): Event { + return this.onPluginsDidStartEmitter.event; + } + + get onPluginsWillUnload(): Event { + return this.onPluginsWillUnloadEmitter.event; + } + + get didStart(): Promise { + return this.hostedPluginSupport.didStart; + } +} diff --git a/arduino-ide-extension/src/browser/theia/plugin-ext/hosted-plugin.ts b/arduino-ide-extension/src/browser/theia/plugin-ext/hosted-plugin.ts new file mode 100644 index 000000000..4ef4b1f55 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/plugin-ext/hosted-plugin.ts @@ -0,0 +1,34 @@ +import { Emitter, Event, JsonRpcProxy } from '@theia/core'; +import { injectable, interfaces } from '@theia/core/shared/inversify'; +import { HostedPluginServer } from '@theia/plugin-ext/lib/common/plugin-protocol'; +import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; +@injectable() +export class HostedPluginSupport extends TheiaHostedPluginSupport { + private readonly onDidLoadEmitter = new Emitter(); + private readonly onDidCloseConnectionEmitter = new Emitter(); + + override onStart(container: interfaces.Container): void { + super.onStart(container); + this.hostedPluginServer.onDidCloseConnection(() => + this.onDidCloseConnectionEmitter.fire() + ); + } + + protected override async doLoad(): Promise { + await super.doLoad(); + this.onDidLoadEmitter.fire(); // Unlike Theia, IDE2 fires an event after loading the VS Code extensions. + } + + get onDidLoad(): Event { + return this.onDidLoadEmitter.event; + } + + get onDidCloseConnection(): Event { + return this.onDidCloseConnectionEmitter.event; + } + + private get hostedPluginServer(): JsonRpcProxy { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (this as any).server; + } +}