Skip to content

Commit

Permalink
fix: Fix a few issue with updating configuration (#1068)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S authored Jul 20, 2021
1 parent 506a388 commit d112828
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 37 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@
"cSpell.showStatusAlignment": {
"type": "string",
"scope": "resource",
"default": "Left",
"default": "Right",
"enum": [
"Left",
"Right"
Expand Down
24 changes: 13 additions & 11 deletions packages/client/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
} from './settings';
import * as CSpellSettings from './settings/CSpellSettings';
import {
DictionaryHelperTarget,
TargetMatchFn,
dictionaryTargetBestMatch,
dictionaryTargetBestMatchFolder,
dictionaryTargetBestMatchUser,
Expand All @@ -51,7 +51,7 @@ import {
} from './settings/DictionaryTargets';
import { writeWordsToDictionary } from './settings/DictionaryWriter';
import { isDefined, toUri } from './util';
import { catchErrors, handleErrors, silenceErrors } from './util/errors';
import { catchErrors, handleErrors, logErrors } from './util/errors';
import { performance, toMilliseconds } from './util/perf';

export { disableCurrentLanguage, enableCurrentLanguage, toggleEnableSpellChecker } from './settings';
Expand Down Expand Up @@ -131,15 +131,13 @@ const commandHandlers: CommandHandler = {
'cSpell.logPerfTimeline': dumpPerfTimeline,

'cSpell.addWordToCSpellConfig': actionAddWordToCSpell,
'cSpell.addIssuesToDictionary': notImplemented('cSpell.addIssuesToDictionary'),
'cSpell.addIssuesToDictionary': addAllIssuesFromDocument,
'cSpell.createCustomDictionary': notImplemented('cSpell.createCustomDictionary'),
'cSpell.createCSpellConfig': createCSpellConfig,
};

function pVoid<T>(p: Promise<T> | Thenable<T>): Promise<void> {
return Promise.resolve(p)
.then(() => {})
.catch((e) => window.showErrorMessage(e.toString()).then());
function pVoid<T>(p: Promise<T> | Thenable<T>, errorHandler = handleErrors): Promise<void> {
return errorHandler(Promise.resolve(p).then(() => {}));
}

function notImplemented(cmd: string) {
Expand Down Expand Up @@ -228,15 +226,19 @@ export function addWordToUserDictionary(word: string): Promise<void> {
return addWordToTarget(word, dictionaryTargetBestMatchUser, undefined);
}

function addWordToTarget(word: string, target: DictionaryHelperTarget, docUri: string | null | Uri | undefined) {
function addWordToTarget(word: string, target: TargetMatchFn, docUri: string | null | Uri | undefined) {
return handleErrors(_addWordToTarget(word, target, docUri));
}

function _addWordToTarget(word: string, target: DictionaryHelperTarget, docUri: string | null | Uri | undefined) {
function _addWordToTarget(word: string, target: TargetMatchFn, docUri: string | null | Uri | undefined) {
docUri = parseOptionalUri(docUri);
return di.get('dictionaryHelper').addWordsToTarget(word, target, docUri);
}

function addAllIssuesFromDocument(): Promise<void> {
return handleErrors(di.get('dictionaryHelper').addIssuesToDictionary());
}

function addIgnoreWordToTarget(word: string, target: ConfigurationTarget, uri: string | null | Uri | undefined): Promise<void> {
return handleErrors(_addIgnoreWordToTarget(word, target, uri));
}
Expand Down Expand Up @@ -314,12 +316,12 @@ async function actionSuggestSpellingCorrections(): Promise<void> {
const matchingRanges = extractMatchingDiagRanges(document, selection, diags);
const r = matchingRanges?.[0] || range;
if (!document || !r || !diags) {
return silenceErrors(window.showWarningMessage('Nothing to suggest.')).then();
return pVoid(window.showWarningMessage('Nothing to suggest.'), logErrors);
}

const actions = await di.get('client').requestSpellingSuggestions(document, r, diags);
if (!actions || !actions.length) {
return silenceErrors(window.showWarningMessage(`No Suggestions Found for ${document.getText(r)}`)).then();
return pVoid(window.showWarningMessage(`No Suggestions Found for ${document.getText(r)}`), logErrors);
}
const items: SuggestionQuickPickItem[] = actions.map((a) => ({ label: a.title, _action: a }));
const picked = await window.showQuickPick(items);
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export async function updateDocumentRelatedContext(client: CSpellClient, doc: Te

context.editorMenuContext.addWordToDictionary = show && hasIssues && !!matrix.dictionary.unknown;
context.editorMenuContext.addWordToCSpellConfig = show && hasIssues && usesConfigFile && !usesCustomDictionary;

context.editorMenuContext.addIssuesToDictionary = show && hasIssues && hasMultipleIssues;
context.editorMenuContext.createCustomDictionary = show && showCreateDictionary;
context.editorMenuContext.createCSpellConfig = show && !usesConfigFile;
Expand Down
75 changes: 52 additions & 23 deletions packages/client/src/settings/DictionaryHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,25 @@ export interface DictionaryHelperTarget {
const matchKindNone: ConfigKindMask = { dictionary: false, cspell: false, vscode: false };
const matchKindAll: ConfigKindMask = { dictionary: true, cspell: true, vscode: true };
const matchKindCSpell: ConfigKindMask = { dictionary: false, cspell: true, vscode: false };
const matchKindVSCode: ConfigKindMask = { dictionary: false, cspell: false, vscode: true };

const matchScopeNone: ConfigScopeMask = { unknown: false, folder: false, workspace: false, user: false };
const matchScopeAll: ConfigScopeMask = { unknown: true, folder: true, workspace: true, user: true };
const matchScopeAllButUser: ConfigScopeMask = { unknown: true, folder: true, workspace: true, user: false };
const matchScopeUser: ConfigScopeMask = { unknown: false, folder: false, workspace: false, user: true };
const matchScopeWorkspace: ConfigScopeMask = { unknown: false, folder: false, workspace: true, user: false };
const matchScopeFolder: ConfigScopeMask = { unknown: false, folder: true, workspace: false, user: false };

export const dictionaryTargetBestMatch = buildTarget(matchKindAll, matchScopeAllButUser);
export const dictionaryTargetBestMatchUser = buildTarget(matchKindAll, matchScopeUser);
export const dictionaryTargetBestMatchWorkspace = buildTarget(matchKindAll, matchScopeWorkspace);
export const dictionaryTargetBestMatchFolder = buildTarget(matchKindAll, matchScopeFolder);
export const dictionaryTargetCSpell = buildTarget(matchKindCSpell, matchScopeAllButUser);
export const dictionaryTargetVSCodeUser = buildTarget(matchKindCSpell, matchScopeUser);
export const dictionaryTargetVSCodeWorkspace = buildTarget(matchKindCSpell, matchScopeWorkspace);
export const dictionaryTargetVSCodeFolder = buildTarget(matchKindCSpell, matchScopeFolder);
export type TargetMatchFn = (configTargets: ConfigTarget[]) => Promise<ConfigTarget | undefined> | ConfigTarget | undefined;

export const dictionaryTargetBestMatch = buildMatchTargetFn(matchKindAll, matchScopeAllButUser);
export const dictionaryTargetBestMatchUser = buildMatchTargetFn(matchKindAll, matchScopeUser);
export const dictionaryTargetBestMatchWorkspace = buildMatchTargetFn(matchKindAll, matchScopeWorkspace);
export const dictionaryTargetBestMatchFolder = buildMatchTargetFn(matchKindAll, matchScopeFolder);
export const dictionaryTargetCSpell = buildMatchTargetFn(matchKindCSpell, matchScopeAll);
export const dictionaryTargetVSCodeUser = buildMatchTargetFn(matchKindVSCode, matchScopeUser);
export const dictionaryTargetVSCodeWorkspace = buildMatchTargetFn(matchKindVSCode, matchScopeWorkspace);
export const dictionaryTargetVSCodeFolder = buildMatchTargetFn(matchKindVSCode, matchScopeFolder);

export class DictionaryHelper {
constructor(public client: CSpellClient) {}
Expand All @@ -52,12 +56,11 @@ export class DictionaryHelper {
* @param docUri - the related document (helps to determine the configuration location)
* @returns the promise resolves upon completion.
*/
public async addWordsToTarget(words: string | string[], target: DictionaryHelperTarget, docUri: Uri | undefined): Promise<void> {
public async addWordsToTarget(words: string | string[], target: ConfigTarget | TargetMatchFn, docUri: Uri | undefined): Promise<void> {
words = normalizeWords(words);
const docConfig = await this.getDocConfig(docUri);
const cfgTarget = findMatchingConfigTarget(target, docConfig.configTargets);

const result = cfgTarget && (await this.addToTarget(words, cfgTarget));
const cfgTarget = await this.resolveTarget(target, docUri);
if (!cfgTarget) return;
const result = await this._addWordsToTarget(words, cfgTarget);
if (!result) {
throw new UnableToAddWordError(`Unable to add "${words}"`, words);
}
Expand All @@ -78,7 +81,7 @@ export class DictionaryHelper {
return this.addWordsToTarget([...words], dictionaryTargetBestMatch, doc.uri);
}

private addToTarget(words: string[], target: ConfigTarget): Promise<boolean> {
private _addWordsToTarget(words: string[], target: ConfigTarget): Promise<boolean> {
switch (target.kind) {
case 'dictionary':
return this.addToDictionary(words, target);
Expand Down Expand Up @@ -130,28 +133,48 @@ export class DictionaryHelper {
.map((p) => p.catch((e: Error) => vscode.window.showWarningMessage(e.message)));
await Promise.all(process);
}

private async resolveTarget(target: ConfigTarget | TargetMatchFn, docUri: Uri | undefined): Promise<ConfigTarget | undefined> {
if (typeof target !== 'function') return target;

const docConfig = await this.getDocConfig(docUri);
return target(docConfig.configTargets);
}
}

function isTextDocument(d: vscode.TextDocument | vscode.Uri): d is vscode.TextDocument {
return !!(<vscode.TextDocument>d).uri;
}

function findMatchingConfigTarget(target: DictionaryHelperTarget, configTargets: ConfigTarget[]): ConfigTarget | undefined {
function findMatchingConfigTarget(target: DictionaryHelperTarget, configTargets: ConfigTarget[]): ConfigTarget[] {
const matches: ConfigTarget[] = [];

for (const t of configTargets) {
if (target.kind[t.kind] && target.scope[t.scope]) return t;
if (!target.kind[t.kind] || !target.scope[t.scope]) continue;
if (matches.length && (matches[0].kind !== t.kind || matches[0].scope !== t.scope)) break;
matches.push(t);
}

return undefined;
return matches;
}

export function buildTarget(
kind: Partial<DictionaryHelperTargetKind>,
scope: Partial<DictionaryHelperTargetScope>
): DictionaryHelperTarget {
return Object.freeze({
export function buildMatchTargetFn(kind: Partial<DictionaryHelperTargetKind>, scope: Partial<DictionaryHelperTargetScope>): TargetMatchFn {
const match = {
kind: fillKind(kind),
scope: fillScope(scope),
});
};

return async function (configTargets: ConfigTarget[]) {
const found = findMatchingConfigTarget(match, configTargets);
if (!found.length) throw new UnableToFindTarget('No matching configuration found.');
if (found.length === 1) return found[0];

const sel = await vscode.window.showQuickPick(
found.map((f) => ({ label: f.name, _found: f })),
{ title: 'Choose Destination' }
);
return sel?._found;
};
}

function fillKind(kind: Partial<DictionaryHelperTargetKind>): DictionaryHelperTargetKind {
Expand All @@ -168,6 +191,12 @@ export class UnableToAddWordError extends Error {
}
}

export class UnableToFindTarget extends Error {
constructor(msg: string) {
super(msg);
}
}

export const __testing__ = {
isTextDocument,
};
4 changes: 2 additions & 2 deletions packages/client/src/settings/configFileReadWrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export function parseJson(content: string): CSpellSettings {
export function stringifyJson(obj: Object, spaces?: string | number | undefined, keepComments = true): string {
const formatting = retrieveFormatting(obj);
spaces = formatting?.spaces || spaces || spacesJson;
const { newlineAtEndOfFile = true } = formatting || {};
const newlineAtEndOfFile = formatting?.newlineAtEndOfFile ?? true;
const json = keepComments ? stringifyJsonc(obj, null, spaces) : JSON.stringify(obj, null, spaces);
return newlineAtEndOfFile ? json + '\n' : json;
}
Expand All @@ -208,7 +208,7 @@ function hasTrailingNewline(content: string): boolean {
}

function detectIndent(json: string): string | undefined {
const s = json.match(/^\s+(?=")/m);
const s = json.match(/^[ \t]+(?=")/m);
if (!s) return undefined;
return s[0];
}
Expand Down

0 comments on commit d112828

Please sign in to comment.