mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 00:01:49 +00:00
fix(editor): leading-edge fire + max-wait cap on Monaco debounce
The Editor debounced `onDidChangeModelContent` purely on the trailing edge — every keystroke rescheduled a 500ms timer, and uninterrupted typing held the bindable `code` prop stale until a pause. Stacked behind our 1.5s autosave debouncer that meant our clock didn't even start ticking until 500ms after the user paused, and the `code` binding never updated mid-burst for downstream consumers (lint, live preview, change listeners). Switch to leading + trailing + max-wait: * First keystroke of a burst fires `updateCode` synchronously, then stamps a wall-clock chain start. * Each subsequent keystroke (re)arms a trailing timer at `min(now + changeTimeout, chainStart + maxChangeTimeout)` — the cap is what makes continuous typing materialize at least once per maxChangeTimeout window instead of indefinitely. * When the trailing fires it resets the chain so the next keystroke after a pause is a fresh leading fire. New prop `maxChangeTimeout` (default 1000ms) sits next to the existing `changeTimeout` (default 500ms). Dispose path clears the chain stamp alongside the timer.
This commit is contained in:
@@ -136,7 +136,17 @@
|
||||
lineNumbersMinChars?: number
|
||||
files?: Record<string, { code: string; readonly?: boolean }> | undefined
|
||||
extraLib?: string | undefined
|
||||
/** Trailing debounce window (ms) on Monaco's onDidChangeModelContent.
|
||||
* Each keystroke schedules (or reschedules) an `updateCode` call this
|
||||
* far in the future. */
|
||||
changeTimeout?: number
|
||||
/** Hard ceiling (ms) on how long `updateCode` can be deferred while
|
||||
* the user is typing continuously — measured from the FIRST
|
||||
* keystroke of the burst (the leading fire). Without this cap,
|
||||
* uninterrupted typing would hold the bindable `code` prop stale
|
||||
* indefinitely and downstream consumers (autosave, lint, live
|
||||
* preview) would never see the latest text. */
|
||||
maxChangeTimeout?: number
|
||||
loadAsync?: boolean
|
||||
key?: string | undefined
|
||||
class?: string | undefined
|
||||
@@ -177,6 +187,7 @@
|
||||
files = {},
|
||||
extraLib = undefined,
|
||||
changeTimeout = 500,
|
||||
maxChangeTimeout = 1000,
|
||||
loadAsync = false,
|
||||
key = undefined,
|
||||
class: clazz = undefined,
|
||||
@@ -1327,6 +1338,10 @@
|
||||
}
|
||||
|
||||
let timeoutModel: number | undefined = undefined
|
||||
/** Wall-clock start (ms) of the current debounce chain. Reset whenever
|
||||
* the trailing fire lands — so a typing burst → pause → typing burst
|
||||
* gets a fresh leading fire instead of inheriting the previous cap. */
|
||||
let changeChainStart: number | undefined = undefined
|
||||
async function loadMonaco() {
|
||||
setMonacoTypescriptOptions()
|
||||
console.log('path', uri)
|
||||
@@ -1433,10 +1448,29 @@
|
||||
let ataModel: number | undefined = undefined
|
||||
|
||||
editor?.onDidChangeModelContent((event) => {
|
||||
timeoutModel && clearTimeout(timeoutModel)
|
||||
timeoutModel = setTimeout(() => {
|
||||
// Leading fire on the first keystroke of a burst: every
|
||||
// downstream consumer (autosave's 1.5s debouncer, the
|
||||
// `bind:code` chain, change listeners) sees text within the
|
||||
// same tick instead of after `changeTimeout` ms of silence.
|
||||
// Subsequent keystrokes within the burst are trailing-only
|
||||
// (debounced by `changeTimeout`), with a hard ceiling at
|
||||
// `chainStart + maxChangeTimeout` so continuous typing still
|
||||
// materializes at least once per `maxChangeTimeout` window.
|
||||
const now = Date.now()
|
||||
if (changeChainStart === undefined) {
|
||||
updateCode()
|
||||
}, changeTimeout)
|
||||
changeChainStart = now
|
||||
}
|
||||
timeoutModel && clearTimeout(timeoutModel)
|
||||
const fireAt = Math.min(now + changeTimeout, changeChainStart + maxChangeTimeout)
|
||||
timeoutModel = setTimeout(
|
||||
() => {
|
||||
updateCode()
|
||||
timeoutModel = undefined
|
||||
changeChainStart = undefined
|
||||
},
|
||||
Math.max(0, fireAt - now)
|
||||
)
|
||||
|
||||
ataModel && clearTimeout(ataModel)
|
||||
ataModel = setTimeout(() => {
|
||||
@@ -1788,6 +1822,7 @@
|
||||
resultCollectionCompletor && resultCollectionCompletor.dispose()
|
||||
preprocessorCompletor && preprocessorCompletor.dispose()
|
||||
timeoutModel && clearTimeout(timeoutModel)
|
||||
changeChainStart = undefined
|
||||
loadTimeout && clearTimeout(loadTimeout)
|
||||
aiChatEditorHandler?.clear()
|
||||
absolutePathExtraLibs.forEach((d) => d.dispose())
|
||||
|
||||
Reference in New Issue
Block a user