From 830a9ef2d78c122e5fd2510aeb5190a09ec0bb3e Mon Sep 17 00:00:00 2001 From: Milly Date: Thu, 27 Jul 2023 18:26:00 +0900 Subject: [PATCH 1/2] Rename property to `uiRedrawLock` --- denops/ddu/ddu.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/denops/ddu/ddu.ts b/denops/ddu/ddu.ts index 50a412b..2776cd2 100644 --- a/denops/ddu/ddu.ts +++ b/denops/ddu/ddu.ts @@ -97,7 +97,7 @@ export class Ddu { private quitted = false; private cancelledToRefresh = false; private abortController = new AbortController(); - private redrawLock = new Lock(0); + private uiRedrawLock = new Lock(0); private startTime = 0; private expandedPaths = new Set(); private searchPath: TreePath = ""; @@ -188,7 +188,7 @@ export class Ddu { await uiRedraw( denops, this, - this.redrawLock, + this.uiRedrawLock, ui, uiOptions, uiParams, @@ -652,7 +652,7 @@ export class Ddu { await uiRedraw( denops, this, - this.redrawLock, + this.uiRedrawLock, ui, uiOptions, uiParams, @@ -1108,7 +1108,7 @@ export class Ddu { await uiRedraw( denops, this, - this.redrawLock, + this.uiRedrawLock, ui, uiOptions, uiParams, @@ -1288,7 +1288,7 @@ export class Ddu { await uiRedraw( denops, this, - this.redrawLock, + this.uiRedrawLock, ui, uiOptions, uiParams, @@ -1351,7 +1351,7 @@ export class Ddu { await uiRedraw( denops, this, - this.redrawLock, + this.uiRedrawLock, ui, uiOptions, uiParams, From 0bfe23c7eb1ecafb6216f2b092db97f2b4bd2323 Mon Sep 17 00:00:00 2001 From: Milly Date: Thu, 27 Jul 2023 18:36:19 +0900 Subject: [PATCH 2/2] Suppress duplicate execution of redraw --- denops/ddu/ddu.ts | 115 ++++++++++++++++++++++++++++++++------------- denops/ddu/deps.ts | 1 + 2 files changed, 84 insertions(+), 32 deletions(-) diff --git a/denops/ddu/ddu.ts b/denops/ddu/ddu.ts index 2776cd2..8c0ca9e 100644 --- a/denops/ddu/ddu.ts +++ b/denops/ddu/ddu.ts @@ -1,6 +1,7 @@ import { assertEquals, basename, + deferred, Denops, echo, equal, @@ -86,6 +87,14 @@ type ItemActions = { actions: Record; }; +type RedrawOptions = { + /** + * NOTE: Set restoreItemState to true if redraw without regather because + * item's states reset to gathered. + */ + restoreItemState?: boolean; +}; + export class Ddu { private loader: Loader; private gatherStates: Record = {}; @@ -98,6 +107,8 @@ export class Ddu { private cancelledToRefresh = false; private abortController = new AbortController(); private uiRedrawLock = new Lock(0); + private waitRedrawComplete?: Promise; + private scheduledRedrawOptions?: RedrawOptions; private startTime = 0; private expandedPaths = new Set(); private searchPath: TreePath = ""; @@ -179,7 +190,7 @@ export class Ddu { if (this.searchPath) { // Redraw only without regather items. - return this.redraw(denops, true); + return this.redraw(denops, { restoreItemState: true }); } // UI Redraw only @@ -297,7 +308,8 @@ export class Ddu { // Initialize UI window if (!this.options.sync) { - await this.redraw(denops); + // Do not await here + this.redraw(denops); } await Promise.all( @@ -327,44 +339,41 @@ export class Ddu { return; } + // Start gather asynchronously + const gatherItems = this.gatherItems( + denops, + index, + source, + sourceOptions, + sourceParams, + this.loader, + 0, + ); + // Call "onRefreshItems" hooks const filters = sourceOptions.matchers.concat( sourceOptions.sorters, ).concat(sourceOptions.converters); - for (const userFilter of filters) { + await Promise.all(filters.map(async (userFilter) => { const [filter, filterOptions, filterParams] = await this.getFilter( denops, userFilter, ); - if (!filter || !filter.onRefreshItems) { - continue; - } - - await filter.onRefreshItems({ + await filter?.onRefreshItems?.({ denops, filterOptions, filterParams, }); - } + })); + + // Get path option, or current directory instead if it is empty + const path = sourceOptions.path.length > 0 + ? sourceOptions.path + : await fn.getcwd(denops); let prevLength = state.items.length; - for await ( - const newItems of this.gatherItems( - denops, - index, - source, - sourceOptions, - sourceParams, - this.loader, - 0, - ) - ) { - let path = sourceOptions.path; - if (path === "") { - // Use current directory instead - path = await fn.getcwd(denops) as string; - } + for await (const newItems of gatherItems) { if (path !== this.context.path) { if (this.context.path.length > 0) { this.context.pathHistories.push(this.context.path); @@ -374,8 +383,9 @@ export class Ddu { state.items = state.items.concat(newItems); - if (prevLength !== state.items.length && !this.options.sync) { - await this.redraw(denops); + if (!this.options.sync && prevLength !== state.items.length) { + // Do not await inside loop + this.redraw(denops); prevLength = state.items.length; } } @@ -391,6 +401,9 @@ export class Ddu { if (this.options.sync) { await this.redraw(denops); + } else { + // Wait complete redraw + await this.waitRedrawComplete; } } @@ -491,11 +504,49 @@ export class Ddu { } } - async redraw( + redraw( + denops: Denops, + opts?: RedrawOptions, + ): Promise { + if (this.waitRedrawComplete) { + // Already redrawing, so adding to schedule + this.scheduledRedrawOptions = { + // Override with true + restoreItemState: opts?.restoreItemState || + this.scheduledRedrawOptions?.restoreItemState, + }; + } else { + // Start redraw + const complete = this.waitRedrawComplete = deferred(); + + const scheduleRunner = async (opts?: RedrawOptions) => { + try { + await this.redrawInternal(denops, opts); + + opts = this.scheduledRedrawOptions; + if (opts) { + // Scheduled to redraw + this.scheduledRedrawOptions = undefined; + scheduleRunner(opts); + } else { + // All schedules completed + this.waitRedrawComplete = undefined; + complete.resolve(); + } + } catch (e: unknown) { + complete.reject(e); + } + }; + + scheduleRunner(opts); + } + + return this.waitRedrawComplete; + } + + private async redrawInternal( denops: Denops, - // NOTE: Set restoreItemState to true if redraw without regather because - // item's states reset to gathered. - restoreItemState?: boolean, + opts?: RedrawOptions, ): Promise { // Update current input this.context.done = true; @@ -532,7 +583,7 @@ export class Ddu { index, this.input, ); - if (restoreItemState) { + if (opts?.restoreItemState) { items.forEach((item) => { if (item.treePath) { item.__expanded = this.isExpanded(convertTreePath(item.treePath)); diff --git a/denops/ddu/deps.ts b/denops/ddu/deps.ts index ee4f607..c66a40e 100644 --- a/denops/ddu/deps.ts +++ b/denops/ddu/deps.ts @@ -28,3 +28,4 @@ export { dirname, SEP as pathsep, } from "https://deno.land/std@0.194.0/path/mod.ts"; +export { deferred } from "https://deno.land/std@0.196.0/async/deferred.ts";