mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-11 06:36:33 +00:00
fix: flawed timing issue when opening editors
From now on, the editor widget open promise resolution does not rely on internal Theia events but solely on @phosphor's `isAttached`/`isVisible` properties. The editor widget promise resolves with the next task after a navigation frame so the browser can render the widget. Closes #1612 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
f6d112e1f6
commit
3735553003
@ -1,5 +1,5 @@
|
|||||||
import { nls } from '@theia/core/lib/common/nls';
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
import type { EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
|
import type { EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
|
||||||
import { Later } from '../../common/nls';
|
import { Later } from '../../common/nls';
|
||||||
import { Sketch, SketchesError } from '../../common/protocol';
|
import { Sketch, SketchesError } from '../../common/protocol';
|
||||||
@ -15,14 +15,9 @@ import { ApplicationError } from '@theia/core/lib/common/application-error';
|
|||||||
import { Deferred, wait } from '@theia/core/lib/common/promise-util';
|
import { Deferred, wait } from '@theia/core/lib/common/promise-util';
|
||||||
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
||||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
|
||||||
import { ContextKeyService as VSCodeContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/browser/contextKeyService';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class OpenSketchFiles extends SketchContribution {
|
export class OpenSketchFiles extends SketchContribution {
|
||||||
@inject(VSCodeContextKeyService)
|
|
||||||
private readonly contextKeyService: VSCodeContextKeyService;
|
|
||||||
|
|
||||||
override registerCommands(registry: CommandRegistry): void {
|
override registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(OpenSketchFiles.Commands.OPEN_SKETCH_FILES, {
|
registry.registerCommand(OpenSketchFiles.Commands.OPEN_SKETCH_FILES, {
|
||||||
execute: (uri: URI) => this.openSketchFiles(uri),
|
execute: (uri: URI) => this.openSketchFiles(uri),
|
||||||
@ -135,39 +130,36 @@ export class OpenSketchFiles extends SketchContribution {
|
|||||||
const widget = this.editorManager.all.find(
|
const widget = this.editorManager.all.find(
|
||||||
(widget) => widget.editor.uri.toString() === uri
|
(widget) => widget.editor.uri.toString() === uri
|
||||||
);
|
);
|
||||||
|
if (widget && !forceOpen) {
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
const disposables = new DisposableCollection();
|
const disposables = new DisposableCollection();
|
||||||
if (!widget || forceOpen) {
|
const deferred = new Deferred<EditorWidget>();
|
||||||
const deferred = new Deferred<EditorWidget>();
|
// An editor can be in two primary states:
|
||||||
|
// - The editor is not yet opened. The `widget` is `undefined`. With `editorManager#open`, Theia will create an editor and fire an `editorManager#onCreated` event.
|
||||||
|
// - The editor is opened. Can be active, current, or open.
|
||||||
|
// - If the editor has the focus (the cursor blinks in the editor): it's the active editor.
|
||||||
|
// - If the editor does not have the focus (the focus is on a different widget or the context menu is opened in the editor): it's the current editor.
|
||||||
|
// - If the editor is not the top editor in the main area, it's opened.
|
||||||
|
if (!widget) {
|
||||||
|
// If the widget is `undefined`, IDE2 expects one `onCreate` event. Subscribe to the `onCreated` event
|
||||||
|
// and resolve the promise with the editor only when the new editor's visibility changes.
|
||||||
disposables.push(
|
disposables.push(
|
||||||
this.editorManager.onCreated((editor) => {
|
this.editorManager.onCreated((editor) => {
|
||||||
if (editor.editor.uri.toString() === uri) {
|
if (editor.editor.uri.toString() === uri) {
|
||||||
if (editor.isVisible) {
|
if (editor.isAttached && editor.isVisible) {
|
||||||
disposables.dispose();
|
|
||||||
deferred.resolve(editor);
|
deferred.resolve(editor);
|
||||||
} else {
|
} else {
|
||||||
// In Theia, the promise resolves after opening the editor, but the editor is neither attached to the DOM, nor visible.
|
|
||||||
// This is a hack to first get an event from monaco after the widget update request, then IDE2 waits for the next monaco context key event.
|
|
||||||
// Here, the monaco context key event is not used, but this is the first event after the editor is visible in the UI.
|
|
||||||
disposables.push(
|
disposables.push(
|
||||||
(editor.editor as MonacoEditor).onDidResize((dimension) => {
|
editor.onDidChangeVisibility((visible) => {
|
||||||
if (dimension) {
|
if (visible) {
|
||||||
const isKeyOwner = (
|
// wait an animation frame. although the visible and attached props are true the editor is not there.
|
||||||
arg: unknown
|
// let the browser render the widget
|
||||||
): arg is { key: string } => {
|
setTimeout(
|
||||||
if (typeof arg === 'object') {
|
() =>
|
||||||
const object = arg as Record<string, unknown>;
|
requestAnimationFrame(() => deferred.resolve(editor)),
|
||||||
return typeof object['key'] === 'string';
|
0
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
disposables.push(
|
|
||||||
this.contextKeyService.onDidChangeContext((e) => {
|
|
||||||
// `commentIsEmpty` is the first context key change event received from monaco after the editor is for real visible in the UI.
|
|
||||||
if (isKeyOwner(e) && e.key === 'commentIsEmpty') {
|
|
||||||
deferred.resolve(editor);
|
|
||||||
disposables.dispose();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -176,29 +168,41 @@ export class OpenSketchFiles extends SketchContribution {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
this.editorManager.open(
|
}
|
||||||
|
|
||||||
|
this.editorManager
|
||||||
|
.open(
|
||||||
new URI(uri),
|
new URI(uri),
|
||||||
options ?? {
|
options ?? {
|
||||||
mode: 'reveal',
|
mode: 'reveal',
|
||||||
preview: false,
|
preview: false,
|
||||||
counter: 0,
|
counter: 0,
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
.then((editorWidget) => {
|
||||||
|
// If the widget was defined, it was already opened.
|
||||||
|
// The editor is expected to be attached to the shell and visible in the UI.
|
||||||
|
// The deferred promise does not have to wait for the `editorManager#onCreated` event.
|
||||||
|
// It can resolve earlier.
|
||||||
|
if (!widget) {
|
||||||
|
deferred.resolve(editorWidget);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeout = 5_000; // number of ms IDE2 waits for the editor to show up in the UI
|
||||||
|
const result = await Promise.race([
|
||||||
|
deferred.promise,
|
||||||
|
wait(timeout).then(() => {
|
||||||
|
disposables.dispose();
|
||||||
|
return 'timeout';
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
if (result === 'timeout') {
|
||||||
|
console.warn(
|
||||||
|
`Timeout after ${timeout} millis. The editor has not shown up in time. URI: ${uri}`
|
||||||
);
|
);
|
||||||
const timeout = 5_000; // number of ms IDE2 waits for the editor to show up in the UI
|
|
||||||
const result = await Promise.race([
|
|
||||||
deferred.promise,
|
|
||||||
wait(timeout).then(() => {
|
|
||||||
disposables.dispose();
|
|
||||||
return 'timeout';
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
if (result === 'timeout') {
|
|
||||||
console.warn(
|
|
||||||
`Timeout after ${timeout} millis. The editor has not shown up in time. URI: ${uri}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export namespace OpenSketchFiles {
|
export namespace OpenSketchFiles {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user