Skip to content

Commit

Permalink
fix: Support Suggestion timeouts (#1668)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S committed Sep 9, 2021
1 parent cd45677 commit 1698aaf
Show file tree
Hide file tree
Showing 18 changed files with 148 additions and 12 deletions.
20 changes: 20 additions & 0 deletions cspell.schema.json
Expand Up @@ -517,6 +517,16 @@
},
"type": "array"
},
"suggestionNumChanges": {
"default": 3,
"description": "The maximum number of changes allowed on a word to be considered a suggestions.\n\nFor example, appending an `s` onto `example` -> `examples` is considered 1 change.\n\nRange: between 1 and 5.",
"type": "number"
},
"suggestionsTimeout": {
"default": 500,
"description": "The maximum amount of time in milliseconds to generate suggestions for a word.",
"type": "number"
},
"usePnP": {
"default": false,
"description": "Packages managers like Yarn 2 use a `.pnp.cjs` file to assist in loading packages stored in the repository.\n\nWhen true, the spell checker will search up the directory structure for the existence of a PnP file and load it.",
Expand Down Expand Up @@ -866,6 +876,16 @@
"description": "Delay in ms after a document has changed before checking it for spelling errors.",
"type": "number"
},
"suggestionNumChanges": {
"default": 3,
"description": "The maximum number of changes allowed on a word to be considered a suggestions.\n\nFor example, appending an `s` onto `example` -> `examples` is considered 1 change.\n\nRange: between 1 and 5.",
"type": "number"
},
"suggestionsTimeout": {
"default": 500,
"description": "The maximum amount of time in milliseconds to generate suggestions for a word.",
"type": "number"
},
"usePnP": {
"default": false,
"description": "Packages managers like Yarn 2 use a `.pnp.cjs` file to assist in loading packages stored in the repository.\n\nWhen true, the spell checker will search up the directory structure for the existence of a PnP file and load it.",
Expand Down
2 changes: 2 additions & 0 deletions packages/cspell-lib/src/Settings/DefaultSettings.ts
Expand Up @@ -113,6 +113,8 @@ export const _defaultSettings: Readonly<CSpellSettingsWithSourceTrace> = {
],
maxNumberOfProblems: 100,
numSuggestions: 10,
suggestionsTimeout: 500,
suggestionNumChanges: 3,
words: [],
userWords: [],
ignorePaths: [],
Expand Down
23 changes: 23 additions & 0 deletions packages/cspell-lib/src/SpellingDictionary/SpellingDictionary.ts
Expand Up @@ -9,10 +9,33 @@ export interface SearchOptions {
}

export interface SuggestOptions {
/**
* Compounding Mode.
* `NONE` is the best option.
*/
compoundMethod?: CompoundWordsMethod;
/**
* The limit on the number of suggestions to generate. If `allowTies` is true, it is possible
* for more suggestions to be generated.
*/
numSuggestions?: number;
/**
* Max number of changes / edits to the word to get to a suggestion matching suggestion.
*/
numChanges?: number;
/**
* Allow for case-ingestive checking.
*/
ignoreCase?: boolean;
/**
* If multiple suggestions have the same edit / change "cost", then included them even if
* it causes more than `numSuggestions` to be returned.
*/
includeTies?: boolean;
/**
* Maximum amount of time to allow for generating suggestions.
*/
timeout?: number;
}

export type FindOptions = SearchOptions;
Expand Down
Expand Up @@ -85,7 +85,9 @@ export class SpellingDictionaryCollection implements SpellingDictionary {
numSuggestions = getDefaultSettings().numSuggestions || defaultNumSuggestions,
numChanges,
compoundMethod,
ignoreCase = true,
ignoreCase,
includeTies,
timeout,
} = suggestOptions;
_suggestOptions.compoundMethod = this.options.useCompounds ? CompoundWordsMethod.JOIN_WORDS : compoundMethod;
const prefixNoCase = CASE_INSENSITIVE_PREFIX;
Expand All @@ -100,8 +102,9 @@ export class SpellingDictionaryCollection implements SpellingDictionary {
numSuggestions,
filter,
changeLimit: numChanges,
includeTies: true,
includeTies,
ignoreCase,
timeout,
});
this.genSuggestions(collector, suggestOptions);
return collector.suggestions.map((r) => ({ ...r, word: r.word }));
Expand Down
Expand Up @@ -152,7 +152,9 @@ export class SpellingDictionaryFromTrie implements SpellingDictionary {
const {
numSuggestions = getDefaultSettings().numSuggestions || defaultNumSuggestions,
numChanges,
ignoreCase = true,
includeTies,
ignoreCase,
timeout,
} = suggestOptions;
function filter(_word: string): boolean {
return true;
Expand All @@ -161,8 +163,9 @@ export class SpellingDictionaryFromTrie implements SpellingDictionary {
numSuggestions,
filter,
changeLimit: numChanges,
includeTies: true,
includeTies,
ignoreCase,
timeout,
});
this.genSuggestions(collector, suggestOptions);
return collector.suggestions.map((r) => ({ ...r, word: r.word }));
Expand Down
Expand Up @@ -119,6 +119,8 @@ export function suggestArgsToSuggestOptions(args: SuggestArgs): SuggestOptions {
compoundMethod,
numChanges,
ignoreCase,
includeTies: undefined,
timeout: undefined,
};
return suggestOptions;
}
Expand Down
11 changes: 10 additions & 1 deletion packages/cspell-lib/src/validator.ts
Expand Up @@ -31,7 +31,16 @@ export async function validateText(
return issues;
}
const withSugs = issues.map((t) => {
const suggestions = dict.suggest(t.text, options.numSuggestions, CompoundWordsMethod.NONE).map((r) => r.word);
const suggestions = dict
.suggest(t.text, {
numSuggestions: options.numSuggestions,
compoundMethod: CompoundWordsMethod.NONE,
includeTies: true,
ignoreCase: !(settings.caseSensitive ?? false),
timeout: settings.suggestionsTimeout,
numChanges: settings.suggestionNumChanges,
})
.map((r) => r.word);
return { ...t, suggestions };
});

Expand Down
1 change: 1 addition & 0 deletions packages/cspell-trie-lib/src/lib/suggest-en-a-star.test.ts
Expand Up @@ -241,6 +241,7 @@ function opts(
changeLimit,
includeTies,
ignoreCase,
timeout,
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/cspell-trie-lib/src/lib/suggest-en.test.ts
Expand Up @@ -179,6 +179,7 @@ function opts(
changeLimit,
includeTies,
ignoreCase,
timeout,
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/cspell-trie-lib/src/lib/suggest.test.ts
Expand Up @@ -14,6 +14,7 @@ const defaultOptions: SuggestionCollectorOptions = {
numSuggestions: 10,
ignoreCase: undefined,
changeLimit: undefined,
timeout: undefined,
};

describe('Validate Suggest', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/cspell-trie-lib/src/lib/suggest.ts
Expand Up @@ -25,6 +25,7 @@ export function suggest(
changeLimit: opts.maxNumChanges,
includeTies: opts.allowTies,
ignoreCase: opts.ignoreCase,
timeout: opts.timeout,
});
collector.collect(genSuggestions(root, word, opts));
return collector.suggestions;
Expand Down
1 change: 1 addition & 0 deletions packages/cspell-trie-lib/src/lib/suggestAStar.test.ts
Expand Up @@ -10,6 +10,7 @@ const defaultOptions: SuggestionCollectorOptions = {
ignoreCase: undefined,
changeLimit: undefined,
includeTies: true,
timeout: undefined,
};

const stopHere = true;
Expand Down
1 change: 1 addition & 0 deletions packages/cspell-trie-lib/src/lib/suggestAStar.ts
Expand Up @@ -478,6 +478,7 @@ export function suggest(root: TrieRoot | TrieRoot[], word: string, options: Sugg
changeLimit: opts.maxNumChanges,
includeTies: true,
ignoreCase: opts.ignoreCase,
timeout: opts.timeout,
});
collector.collect(genSuggestions(root, word, opts));
return collector.suggestions;
Expand Down
1 change: 1 addition & 0 deletions packages/cspell-trie-lib/src/lib/suggestCollector.test.ts
Expand Up @@ -4,6 +4,7 @@ const defaultOptions: SuggestionCollectorOptions = {
numSuggestions: 10,
ignoreCase: undefined,
changeLimit: undefined,
timeout: undefined,
};

describe('Validate suggestCollector', () => {
Expand Down
30 changes: 28 additions & 2 deletions packages/cspell-trie-lib/src/lib/suggestCollector.ts
Expand Up @@ -87,29 +87,40 @@ export interface SuggestionCollector {
export interface SuggestionCollectorOptions {
/**
* number of best matching suggestions.
* @default 10
*/
numSuggestions: number;

/**
* An optional filter function that can be used to limit remove unwanted suggestions.
* I.E. to remove forbidden terms.
* @default () => true
*/
filter?: (word: string, cost: number) => boolean;

/**
* The number of letters that can be changed when looking for a match
* @default 5
*/
changeLimit: number | undefined;

/**
* Include suggestions with tied cost even if the number is greater than `numSuggestions`.
* @default true
*/
includeTies?: boolean;

/**
* specify if case / accents should be ignored when looking for suggestions.
* @default true
*/
ignoreCase: boolean | undefined;

/**
* the total amount of time to allow for suggestions.
* @default 1000
*/
timeout?: number | undefined;
}

export const defaultSuggestionCollectorOptions: SuggestionCollectorOptions = {
Expand All @@ -118,14 +129,23 @@ export const defaultSuggestionCollectorOptions: SuggestionCollectorOptions = {
changeLimit: maxNumChanges,
includeTies: true,
ignoreCase: true,
timeout: defaultCollectorTimeout,
};

export function suggestionCollector(wordToMatch: string, options: SuggestionCollectorOptions): SuggestionCollector {
const { filter = () => true, changeLimit = maxNumChanges, includeTies = false, ignoreCase = true } = options;
const {
filter = () => true,
changeLimit = maxNumChanges,
includeTies = false,
ignoreCase = true,
timeout = defaultCollectorTimeout,
} = options;
const numSuggestions = Math.max(options.numSuggestions, 0) || 0;
const sugs = new Map<string, SuggestionResult>();
let maxCost: number = baseCost * Math.min(wordToMatch.length * maxAllowedCostScale, changeLimit);

let timeRemaining = timeout;

function dropMax() {
if (sugs.size < 2) {
sugs.clear();
Expand Down Expand Up @@ -175,8 +195,12 @@ export function suggestionCollector(wordToMatch: string, options: SuggestionColl
* @param src - the SuggestionIterator used to generate suggestions.
* @param timeout - the amount of time in milliseconds to allow for suggestions.
*/
function collect(src: SuggestionGenerator, timeout = defaultCollectorTimeout) {
function collect(src: SuggestionGenerator, timeout?: number) {
let stop: false | symbol = false;
timeout = timeout ?? timeRemaining;
timeout = Math.min(timeout, timeRemaining);
if (timeout < 0) return;

const timer = createTimer();

let ir: IteratorResult<SuggestionResult | Progress | undefined>;
Expand All @@ -192,6 +216,8 @@ export function suggestionCollector(wordToMatch: string, options: SuggestionColl
}
handleProgress(value);
}

timeRemaining -= timer.elapsed();
}

function suggestions() {
Expand Down
1 change: 1 addition & 0 deletions packages/cspell-trie-lib/src/lib/trie.test.ts
Expand Up @@ -359,5 +359,6 @@ function opts(
changeLimit,
includeTies,
ignoreCase,
timeout: undefined,
};
}
20 changes: 20 additions & 0 deletions packages/cspell-types/cspell.schema.json
Expand Up @@ -517,6 +517,16 @@
},
"type": "array"
},
"suggestionNumChanges": {
"default": 3,
"description": "The maximum number of changes allowed on a word to be considered a suggestions.\n\nFor example, appending an `s` onto `example` -> `examples` is considered 1 change.\n\nRange: between 1 and 5.",
"type": "number"
},
"suggestionsTimeout": {
"default": 500,
"description": "The maximum amount of time in milliseconds to generate suggestions for a word.",
"type": "number"
},
"usePnP": {
"default": false,
"description": "Packages managers like Yarn 2 use a `.pnp.cjs` file to assist in loading packages stored in the repository.\n\nWhen true, the spell checker will search up the directory structure for the existence of a PnP file and load it.",
Expand Down Expand Up @@ -866,6 +876,16 @@
"description": "Delay in ms after a document has changed before checking it for spelling errors.",
"type": "number"
},
"suggestionNumChanges": {
"default": 3,
"description": "The maximum number of changes allowed on a word to be considered a suggestions.\n\nFor example, appending an `s` onto `example` -> `examples` is considered 1 change.\n\nRange: between 1 and 5.",
"type": "number"
},
"suggestionsTimeout": {
"default": 500,
"description": "The maximum amount of time in milliseconds to generate suggestions for a word.",
"type": "number"
},
"usePnP": {
"default": false,
"description": "Packages managers like Yarn 2 use a `.pnp.cjs` file to assist in loading packages stored in the repository.\n\nWhen true, the spell checker will search up the directory structure for the existence of a PnP file and load it.",
Expand Down
30 changes: 25 additions & 5 deletions packages/cspell-types/src/settings/CSpellSettingsDef.ts
Expand Up @@ -86,7 +86,7 @@ export interface ExtendableSettings extends Settings {
overrides?: OverrideSettings[];
}

export interface Settings extends BaseSetting, PnPSettings {
export interface Settings extends ReportingConfiguration, BaseSetting, PnPSettings {
/**
* Current active spelling language.
*
Expand Down Expand Up @@ -118,6 +118,14 @@ export interface Settings extends BaseSetting, PnPSettings {
*/
enableFiletypes?: LanguageIdSingle[];

/** Additional settings for individual languages. */
languageSettings?: LanguageSetting[];

/** Forces the spell checker to assume a give language id. Used mainly as an Override. */
languageId?: LanguageId;
}

export interface ReportingConfiguration extends SuggestionsConfiguration {
/**
* The maximum number of problems to report in a file.
* @default 100
Expand All @@ -135,18 +143,30 @@ export interface Settings extends BaseSetting, PnPSettings {
* @default 4
*/
minWordLength?: number;
}

export interface SuggestionsConfiguration {
/**
* Number of suggestions to make
* @default 10
*/
numSuggestions?: number;

/** Additional settings for individual languages. */
languageSettings?: LanguageSetting[];
/**
* The maximum amount of time in milliseconds to generate suggestions for a word.
* @default 500
*/
suggestionsTimeout?: number;

/** Forces the spell checker to assume a give language id. Used mainly as an Override. */
languageId?: LanguageId;
/**
* The maximum number of changes allowed on a word to be considered a suggestions.
*
* For example, appending an `s` onto `example` -> `examples` is considered 1 change.
*
* Range: between 1 and 5.
* @default 3
*/
suggestionNumChanges?: number;
}

/**
Expand Down

0 comments on commit 1698aaf

Please sign in to comment.