Skip to content

Commit 3890434

Browse files
committed
fix: debounce output clearing during cell re-execution to prevent flash
When a notebook cell is re-run, the runtime immediately clears outputs before sending new ones. This caused the output container to briefly disappear and reappear, producing a distracting flash. Defer the visual clear by 150ms when execution is active so fast-completing cells never flash. Flush the deferred clear immediately when execution ends.
1 parent 2f6d162 commit 3890434

1 file changed

Lines changed: 58 additions & 6 deletions

File tree

src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/PositronNotebookCodeCell.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Event } from '../../../../../base/common/event.js';
7-
import { ISettableObservable, observableFromEvent, observableValue } from '../../../../../base/common/observable.js';
7+
import { ISettableObservable, observableValue } from '../../../../../base/common/observable.js';
88
import { ITextModelService } from '../../../../../editor/common/services/resolverService.js';
99
import { NotebookCellTextModel } from '../../../notebook/common/model/notebookCellTextModel.js';
1010
import { CellKind } from '../../../notebook/common/notebookCommon.js';
@@ -15,12 +15,12 @@ import { IPositronNotebookCodeCell, NotebookCellOutputs } from './IPositronNoteb
1515
import { IPositronWebviewPreloadService } from '../../../../services/positronWebviewPreloads/browser/positronWebviewPreloadService.js';
1616
import { pickPreferredOutputItem } from './notebookOutputUtils.js';
1717
import { getWebviewMessageType, isComplexHtml } from '../../../../services/positronIPyWidgets/common/webviewPreloadUtils.js';
18-
import { INotebookExecutionStateService } from '../../../notebook/common/notebookExecutionStateService.js';
18+
import { INotebookExecutionStateService, NotebookExecutionType } from '../../../notebook/common/notebookExecutionStateService.js';
1919
import { IPositronCellOutputViewModel } from '../IPositronNotebookEditor.js';
2020

2121
export class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IPositronNotebookCodeCell {
2222
override kind: CellKind.Code = CellKind.Code;
23-
private readonly _outputs;
23+
private readonly _outputs: ISettableObservable<NotebookCellOutputs[]>;
2424

2525
// Output collapse state
2626
private readonly _outputIsCollapsed: ISettableObservable<boolean>;
@@ -55,9 +55,61 @@ export class PositronNotebookCodeCell extends PositronNotebookCellGeneral implem
5555
undefined
5656
);
5757

58-
this._outputs = observableFromEvent(this, Event.any(this.model.onDidChangeOutputs, this.model.onDidChangeOutputItems), () => {
59-
/** @description cellOutputs */
60-
return this.parseCellOutputs();
58+
// Initialize with current outputs. Uses observableValue (manually updated)
59+
// instead of observableFromEvent so we can defer output clearing during
60+
// execution to prevent a visual flash when re-running cells.
61+
this._outputs = observableValue('cellOutputs', this.parseCellOutputs());
62+
63+
// Debounce output clearing during cell execution to prevent visual flash.
64+
// When a cell is re-run, the runtime immediately clears outputs before
65+
// sending new ones. Without debouncing, the output container briefly
66+
// disappears and reappears, causing a distracting flash.
67+
let clearOutputsTimer: ReturnType<typeof setTimeout> | undefined;
68+
this._register(Event.any(this.model.onDidChangeOutputs, this.model.onDidChangeOutputItems)(() => {
69+
const newOutputs = this.parseCellOutputs();
70+
71+
// Cancel any pending deferred clear.
72+
if (clearOutputsTimer !== undefined) {
73+
clearTimeout(clearOutputsTimer);
74+
clearOutputsTimer = undefined;
75+
}
76+
77+
if (newOutputs.length > 0) {
78+
// New outputs available -- show immediately.
79+
this._outputs.set(newOutputs, undefined);
80+
} else if (_executionStateService.getCellExecution(this.uri)) {
81+
// Outputs cleared during active execution (re-run). Defer the
82+
// visual clear so fast-completing executions never flash.
83+
clearOutputsTimer = setTimeout(() => {
84+
clearOutputsTimer = undefined;
85+
this._outputs.set(this.parseCellOutputs(), undefined);
86+
}, 150);
87+
} else {
88+
// Outputs cleared outside execution (user action) -- immediately.
89+
this._outputs.set(newOutputs, undefined);
90+
}
91+
}));
92+
93+
// When execution ends, flush any deferred clear immediately.
94+
// Only re-parse when a timer is pending to avoid redundant calls to
95+
// parseCellOutputs() which has side effects (addNotebookOutput).
96+
this._register(_executionStateService.onDidChangeExecution(e => {
97+
if (e.type === NotebookExecutionType.cell && e.affectsCell(this.model.uri) && !e.changed) {
98+
if (clearOutputsTimer !== undefined) {
99+
clearTimeout(clearOutputsTimer);
100+
clearOutputsTimer = undefined;
101+
this._outputs.set(this.parseCellOutputs(), undefined);
102+
}
103+
}
104+
}));
105+
106+
// Clean up timer on dispose.
107+
this._register({
108+
dispose: () => {
109+
if (clearOutputsTimer !== undefined) {
110+
clearTimeout(clearOutputsTimer);
111+
}
112+
}
61113
});
62114

63115
// Reset collapse state when outputs are cleared so new outputs aren't born collapsed

0 commit comments

Comments
 (0)