Skip to content

Commit 3735553

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
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 <[email protected]>
1 parent f6d112e commit 3735553

File tree

1 file changed

+51
-47
lines changed

1 file changed

+51
-47
lines changed

Diff for: arduino-ide-extension/src/browser/contributions/open-sketch-files.ts

+51-47
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { nls } from '@theia/core/lib/common/nls';
2-
import { inject, injectable } from '@theia/core/shared/inversify';
2+
import { injectable } from '@theia/core/shared/inversify';
33
import type { EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
44
import { Later } from '../../common/nls';
55
import { Sketch, SketchesError } from '../../common/protocol';
@@ -15,14 +15,9 @@ import { ApplicationError } from '@theia/core/lib/common/application-error';
1515
import { Deferred, wait } from '@theia/core/lib/common/promise-util';
1616
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
1717
import { DisposableCollection } from '@theia/core/lib/common/disposable';
18-
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
19-
import { ContextKeyService as VSCodeContextKeyService } from '@theia/monaco-editor-core/esm/vs/platform/contextkey/browser/contextKeyService';
2018

2119
@injectable()
2220
export class OpenSketchFiles extends SketchContribution {
23-
@inject(VSCodeContextKeyService)
24-
private readonly contextKeyService: VSCodeContextKeyService;
25-
2621
override registerCommands(registry: CommandRegistry): void {
2722
registry.registerCommand(OpenSketchFiles.Commands.OPEN_SKETCH_FILES, {
2823
execute: (uri: URI) => this.openSketchFiles(uri),
@@ -135,39 +130,36 @@ export class OpenSketchFiles extends SketchContribution {
135130
const widget = this.editorManager.all.find(
136131
(widget) => widget.editor.uri.toString() === uri
137132
);
133+
if (widget && !forceOpen) {
134+
return widget;
135+
}
136+
138137
const disposables = new DisposableCollection();
139-
if (!widget || forceOpen) {
140-
const deferred = new Deferred<EditorWidget>();
138+
const deferred = new Deferred<EditorWidget>();
139+
// An editor can be in two primary states:
140+
// - The editor is not yet opened. The `widget` is `undefined`. With `editorManager#open`, Theia will create an editor and fire an `editorManager#onCreated` event.
141+
// - The editor is opened. Can be active, current, or open.
142+
// - If the editor has the focus (the cursor blinks in the editor): it's the active editor.
143+
// - 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.
144+
// - If the editor is not the top editor in the main area, it's opened.
145+
if (!widget) {
146+
// If the widget is `undefined`, IDE2 expects one `onCreate` event. Subscribe to the `onCreated` event
147+
// and resolve the promise with the editor only when the new editor's visibility changes.
141148
disposables.push(
142149
this.editorManager.onCreated((editor) => {
143150
if (editor.editor.uri.toString() === uri) {
144-
if (editor.isVisible) {
145-
disposables.dispose();
151+
if (editor.isAttached && editor.isVisible) {
146152
deferred.resolve(editor);
147153
} else {
148-
// In Theia, the promise resolves after opening the editor, but the editor is neither attached to the DOM, nor visible.
149-
// 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.
150-
// Here, the monaco context key event is not used, but this is the first event after the editor is visible in the UI.
151154
disposables.push(
152-
(editor.editor as MonacoEditor).onDidResize((dimension) => {
153-
if (dimension) {
154-
const isKeyOwner = (
155-
arg: unknown
156-
): arg is { key: string } => {
157-
if (typeof arg === 'object') {
158-
const object = arg as Record<string, unknown>;
159-
return typeof object['key'] === 'string';
160-
}
161-
return false;
162-
};
163-
disposables.push(
164-
this.contextKeyService.onDidChangeContext((e) => {
165-
// `commentIsEmpty` is the first context key change event received from monaco after the editor is for real visible in the UI.
166-
if (isKeyOwner(e) && e.key === 'commentIsEmpty') {
167-
deferred.resolve(editor);
168-
disposables.dispose();
169-
}
170-
})
155+
editor.onDidChangeVisibility((visible) => {
156+
if (visible) {
157+
// wait an animation frame. although the visible and attached props are true the editor is not there.
158+
// let the browser render the widget
159+
setTimeout(
160+
() =>
161+
requestAnimationFrame(() => deferred.resolve(editor)),
162+
0
171163
);
172164
}
173165
})
@@ -176,29 +168,41 @@ export class OpenSketchFiles extends SketchContribution {
176168
}
177169
})
178170
);
179-
this.editorManager.open(
171+
}
172+
173+
this.editorManager
174+
.open(
180175
new URI(uri),
181176
options ?? {
182177
mode: 'reveal',
183178
preview: false,
184179
counter: 0,
185180
}
181+
)
182+
.then((editorWidget) => {
183+
// If the widget was defined, it was already opened.
184+
// The editor is expected to be attached to the shell and visible in the UI.
185+
// The deferred promise does not have to wait for the `editorManager#onCreated` event.
186+
// It can resolve earlier.
187+
if (!widget) {
188+
deferred.resolve(editorWidget);
189+
}
190+
});
191+
192+
const timeout = 5_000; // number of ms IDE2 waits for the editor to show up in the UI
193+
const result = await Promise.race([
194+
deferred.promise,
195+
wait(timeout).then(() => {
196+
disposables.dispose();
197+
return 'timeout';
198+
}),
199+
]);
200+
if (result === 'timeout') {
201+
console.warn(
202+
`Timeout after ${timeout} millis. The editor has not shown up in time. URI: ${uri}`
186203
);
187-
const timeout = 5_000; // number of ms IDE2 waits for the editor to show up in the UI
188-
const result = await Promise.race([
189-
deferred.promise,
190-
wait(timeout).then(() => {
191-
disposables.dispose();
192-
return 'timeout';
193-
}),
194-
]);
195-
if (result === 'timeout') {
196-
console.warn(
197-
`Timeout after ${timeout} millis. The editor has not shown up in time. URI: ${uri}`
198-
);
199-
}
200-
return result;
201204
}
205+
return result;
202206
}
203207
}
204208
export namespace OpenSketchFiles {

0 commit comments

Comments
 (0)