From bde45ea8284b9662e64915002841c3b2d1a02bb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:08:26 +0000 Subject: [PATCH 1/3] Initial plan From 5aaa81f6a687932d173a7a3611a9c4ee7a4fb01f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:18:49 +0000 Subject: [PATCH 2/3] Improve autoCompleteDelay: per-buffer debounce timers and early cbContext revoke Agent-Logs-Url: https://github.com/Shougo/ddc.vim/sessions/817d4016-fb2c-4d65-a0a3-a2e2df20976a Co-authored-by: Shougo <41495+Shougo@users.noreply.github.com> --- denops/ddc/app.ts | 39 ++++++++++++++++++++++++++++++--------- denops/ddc/context.ts | 1 + denops/ddc/types.ts | 1 + 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/denops/ddc/app.ts b/denops/ddc/app.ts index 5090bd9..b135548 100644 --- a/denops/ddc/app.ts +++ b/denops/ddc/app.ts @@ -34,6 +34,7 @@ export const main: Entrypoint = (denops: Denops) => { const cbContext = createCallbackContext(); const lock = new Lock(0); let queuedEvent: DdcEvent | null = null; + const autoCompleteTimers = new Map(); const setAlias = (extType: DdcExtType, alias: string, base: string) => { loader.registerAlias(extType, alias, base); @@ -260,6 +261,11 @@ export const main: Entrypoint = (denops: Denops) => { async onEvent(arg1: unknown): Promise { queuedEvent = ensure(arg1, is.String) as DdcEvent; + // Revoke immediately to cancel any pending callback waits from the + // previous event processing, allowing it to fail fast and release the + // lock sooner. + cbContext.revoke(); + // NOTE: must be locked await lock.lock(async () => { while (queuedEvent !== null) { @@ -385,15 +391,30 @@ export const main: Entrypoint = (denops: Denops) => { // Check auto complete delay. if (options.autoCompleteDelay > 0) { - // Cancel previous completion - await ddc.cancelCompletion(denops, context, options); - - await new Promise((resolve) => - setTimeout( - resolve, - options.autoCompleteDelay, - ) - ); + // Cancel previous timer for this buffer (debounce) + const prevTimer = autoCompleteTimers.get(context.bufNr); + if (prevTimer !== undefined) { + clearTimeout(prevTimer); + } + + // Set a new per-buffer debounce timer. When it fires, cancel the + // previous completion once and then run doCompletion, avoiding + // repeated cancelCompletion/hide calls during rapid input. + const timerId = setTimeout(async () => { + autoCompleteTimers.delete(context.bufNr); + + await ddc.cancelCompletion(denops, context, options); + + if (options.hideOnEvents || event === "Update") { + // Hide the current completion + await ddc.hide(denops, context, options); + } + + await ddc.doCompletion(denops, context, cbContext, options); + }, options.autoCompleteDelay); + + autoCompleteTimers.set(context.bufNr, timerId); + return; } if (options.hideOnEvents || event === "Update") { diff --git a/denops/ddc/context.ts b/denops/ddc/context.ts index c656952..f6d473d 100644 --- a/denops/ddc/context.ts +++ b/denops/ddc/context.ts @@ -420,6 +420,7 @@ export class ContextBuilderImpl implements ContextBuilder { } const context = { + bufNr: world.bufnr, cursor: world.cursor, event: event, filetype: world.filetype, diff --git a/denops/ddc/types.ts b/denops/ddc/types.ts index 5496e28..e62600a 100644 --- a/denops/ddc/types.ts +++ b/denops/ddc/types.ts @@ -16,6 +16,7 @@ export type SourceName = string; export type FilterName = string; export type Context = { + bufNr: number; cursor: (number | undefined)[]; event: DdcEvent; filetype: string; From 2b48d57af02ab163cdea7657fd9820d4ede1fb0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:21:11 +0000 Subject: [PATCH 3/3] Address code review: add lock to timer callback and simplify clearTimeout Agent-Logs-Url: https://github.com/Shougo/ddc.vim/sessions/817d4016-fb2c-4d65-a0a3-a2e2df20976a Co-authored-by: Shougo <41495+Shougo@users.noreply.github.com> --- denops/ddc/app.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/denops/ddc/app.ts b/denops/ddc/app.ts index b135548..4dbb58b 100644 --- a/denops/ddc/app.ts +++ b/denops/ddc/app.ts @@ -392,25 +392,24 @@ export const main: Entrypoint = (denops: Denops) => { // Check auto complete delay. if (options.autoCompleteDelay > 0) { // Cancel previous timer for this buffer (debounce) - const prevTimer = autoCompleteTimers.get(context.bufNr); - if (prevTimer !== undefined) { - clearTimeout(prevTimer); - } + clearTimeout(autoCompleteTimers.get(context.bufNr)); // Set a new per-buffer debounce timer. When it fires, cancel the // previous completion once and then run doCompletion, avoiding // repeated cancelCompletion/hide calls during rapid input. - const timerId = setTimeout(async () => { + const timerId = setTimeout(() => { autoCompleteTimers.delete(context.bufNr); - await ddc.cancelCompletion(denops, context, options); + lock.lock(async () => { + await ddc.cancelCompletion(denops, context, options); - if (options.hideOnEvents || event === "Update") { - // Hide the current completion - await ddc.hide(denops, context, options); - } + if (options.hideOnEvents || event === "Update") { + // Hide the current completion + await ddc.hide(denops, context, options); + } - await ddc.doCompletion(denops, context, cbContext, options); + await ddc.doCompletion(denops, context, cbContext, options); + }); }, options.autoCompleteDelay); autoCompleteTimers.set(context.bufNr, timerId);