Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions denops/ddc/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,12 @@ export const main: Entrypoint = (denops: Denops) => {

await ddc.checkManualCompletion(denops, context, options, event);

if (skip) return;

// Revoke any pending callbacks from the previous completion cycle before
// starting a new one.
cbContext.revoke();

await onEvent(
denops,
loader,
Expand All @@ -375,10 +381,6 @@ export const main: Entrypoint = (denops: Denops) => {
options,
);

if (skip) return;

cbContext.revoke();

if (await ddc.checkSkipCompletion(denops, context, options)) {
return;
}
Expand Down
30 changes: 22 additions & 8 deletions denops/ddc/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ function isNegligible(older: World, newer: World): boolean {
export class ContextBuilderImpl implements ContextBuilder {
#lastWorld: World = initialWorld();
#custom: Custom = new Custom();
// Set to true whenever options are mutated; cleared after first validation.
#needsValidation = true;

async createContext(
denops: Denops,
Expand Down Expand Up @@ -431,14 +433,17 @@ export class ContextBuilderImpl implements ContextBuilder {

const userOptions = await this.#getUserOptions(denops, world, options);

await this.#validate(denops, "options", userOptions, defaultDdcOptions());
for (const key in userOptions.sourceOptions) {
await this.#validate(
denops,
"sourceOptions",
userOptions.sourceOptions[key],
defaultSourceOptions(),
);
if (this.#needsValidation) {
this.#needsValidation = false;
await this.#validate(denops, "options", userOptions, defaultDdcOptions());
for (const key in userOptions.sourceOptions) {
await this.#validate(
denops,
"sourceOptions",
userOptions.sourceOptions[key],
defaultSourceOptions(),
);
}
}

if (context.mode === "c") {
Expand Down Expand Up @@ -520,31 +525,40 @@ export class ContextBuilderImpl implements ContextBuilder {

setGlobal(options: Partial<DdcOptions>) {
this.#custom.setGlobal(options);
this.#needsValidation = true;
}
setFiletype(ft: string, options: Partial<DdcOptions>) {
this.#custom.setFiletype(ft, options);
this.#needsValidation = true;
}
setBuffer(bufnr: number, options: Partial<DdcOptions>) {
this.#custom.setBuffer(bufnr, options);
this.#needsValidation = true;
}
setContextGlobal(callback: Callback) {
this.#custom.setContextGlobal(callback);
this.#needsValidation = true;
}
setContextFiletype(callback: Callback, ft: string) {
this.#custom.setContextFiletype(callback, ft);
this.#needsValidation = true;
}
setContextBuffer(callback: Callback, bufnr: number) {
this.#custom.setContextBuffer(callback, bufnr);
this.#needsValidation = true;
}

patchGlobal(options: Partial<DdcOptions>) {
this.#custom.patchGlobal(options);
this.#needsValidation = true;
}
patchFiletype(ft: string, options: Partial<DdcOptions>) {
this.#custom.patchFiletype(ft, options);
this.#needsValidation = true;
}
patchBuffer(bufnr: number, options: Partial<DdcOptions>) {
this.#custom.patchBuffer(bufnr, options);
this.#needsValidation = true;
}
}

Expand Down
13 changes: 8 additions & 5 deletions denops/ddc/ddc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import { batch } from "@denops/std/batch";

import { assertEquals } from "@std/assert/equals";

const _encoder = new TextEncoder();
const _decoder = new TextDecoder();

type DdcResult = {
items: Item[];
completePos: number;
Expand Down Expand Up @@ -241,7 +244,7 @@ export class Ddc {
completeStr.length > o.maxAutoCompleteLength);

// Check cache timeout.
const currentTime = new Date().getSeconds();
const currentTime = Math.floor(Date.now() / 1000);
if (
o.cacheTimeout > 0 && this.#prevResults[s.name] &&
currentTime > this.#prevResults[s.name].time + o.cacheTimeout
Expand Down Expand Up @@ -342,7 +345,7 @@ export class Ddc {
completeStr,
lineNr: context.lineNr,
isIncomplete,
time: currentTime,
time: Math.floor(Date.now() / 1000),
};
}

Expand Down Expand Up @@ -731,12 +734,12 @@ function formatMenu(prefix: string, menu: string | undefined): string {
}

function byteposToCharpos(input: string, pos: number): number {
const bytes = (new TextEncoder()).encode(input);
return (new TextDecoder()).decode(bytes.slice(0, pos)).length;
const bytes = _encoder.encode(input);
return _decoder.decode(bytes.slice(0, pos)).length;
}

function charposToBytepos(input: string, pos: number): number {
return (new TextEncoder()).encode(input.slice(0, pos)).length;
return _encoder.encode(input.slice(0, pos)).length;
}

Deno.test("byteposToCharpos", () => {
Expand Down
65 changes: 47 additions & 18 deletions denops/ddc/ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,21 +204,18 @@ export async function filterItems(
completeStr: string,
cdd: Item[],
): Promise<Item[]> {
// Run a list of filters sequentially on the given items array.
async function callFilters(
userFilters: UserFilter[],
type ResolvedFilter = [
BaseFilter<BaseParams>,
FilterOptions,
BaseParams,
];

// Run a list of pre-resolved filters sequentially on the given items array.
async function callResolvedFilters(
resolved: ResolvedFilter[],
items: Item[],
): Promise<Item[]> {
for (const userFilter of userFilters) {
const [filter, filterOptions, filterParams] = await getFilter(
denops,
loader,
options,
userFilter,
);
if (!filter) {
return [];
}
for (const [filter, filterOptions, filterParams] of resolved) {
items = await callFilterFilter(
filter,
denops,
Expand All @@ -232,10 +229,30 @@ export async function filterItems(
items,
);
}

return items;
}

// Run a list of filters sequentially on the given items array.
async function callFilters(
userFilters: UserFilter[],
items: Item[],
): Promise<Item[]> {
const resolved: ResolvedFilter[] = [];
for (const userFilter of userFilters) {
const [filter, filterOptions, filterParams] = await getFilter(
denops,
loader,
options,
userFilter,
);
if (!filter) {
return [];
}
resolved.push([filter, filterOptions, filterParams]);
}
return callResolvedFilters(resolved, items);
}

// Run matchers concurrently when all of them declare parallelSafe = true and
// matcherConcurrency > 1. Falls back to sequential execution when any
// safety condition is not met or any chunk throws an exception.
Expand All @@ -250,17 +267,29 @@ export async function filterItems(
return callFilters(matcherFilters, items);
}

// Check whether every matcher has opted in to parallel execution
// Resolve filters once and reuse across all branches/chunks.
const resolvedFilters = await Promise.all(
matcherFilters.map((uf) => getFilter(denops, loader, options, uf)),
);

// Check whether every matcher has opted in to parallel execution
const allParallelSafe = resolvedFilters.every(
([filter, filterOptions]) =>
filter !== undefined && filterOptions.parallelSafe,
);

// If any filter could not be resolved, treat the same as callFilters does:
// return an empty array.
const validResolved = resolvedFilters.filter(
(r): r is [BaseFilter<BaseParams>, FilterOptions, BaseParams] =>
r[0] !== undefined,
);
if (validResolved.length !== resolvedFilters.length) {
return [];
}

if (!allParallelSafe) {
return callFilters(matcherFilters, items);
return callResolvedFilters(validResolved, items);
}

// Split items into (at most concurrency) equal-sized chunks
Expand All @@ -273,7 +302,7 @@ export async function filterItems(
let parallelResults: Item[][] | null = null;
try {
parallelResults = await Promise.all(
chunks.map((chunk) => callFilters(matcherFilters, chunk)),
chunks.map((chunk) => callResolvedFilters(validResolved, chunk)),
);
} catch (_e) {
parallelResults = null;
Expand All @@ -290,7 +319,7 @@ export async function filterItems(
}

// Fallback: sequential execution
return callFilters(matcherFilters, items);
return callResolvedFilters(validResolved, items);
}

if (sourceOptions.maxKeywordLength > 0) {
Expand Down
Loading