Skip to content

Commit

Permalink
feat: Add command to autofix spelling issues. (#2906)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S authored Oct 29, 2023
1 parent a965d6b commit 356fae5
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 29 deletions.
2 changes: 1 addition & 1 deletion docs/_includes/generated-docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ Description
: The maximum number of times the same word can be flagged as an error in a file.

Default
: _`5`_
: _`20`_

---

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2769,7 +2769,7 @@
"type": "boolean"
},
"cSpell.maxDuplicateProblems": {
"default": 5,
"default": 20,
"markdownDescription": "The maximum number of times the same word can be flagged as an error in a file.",
"scope": "resource",
"type": "number"
Expand Down
2 changes: 1 addition & 1 deletion packages/_server/spell-checker-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2389,7 +2389,7 @@
"type": "boolean"
},
"cSpell.maxDuplicateProblems": {
"default": 5,
"default": 20,
"description": "The maximum number of times the same word can be flagged as an error in a file.",
"markdownDescription": "The maximum number of times the same word can be flagged as an error in a file.",
"scope": "resource",
Expand Down
5 changes: 1 addition & 4 deletions packages/_server/src/api/apiModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { PublishDiagnosticsParams } from 'vscode-languageserver';
import type { ConfigScopeVScode, ConfigTarget } from '../config/configTargets.mjs';
import type * as config from '../config/cspellConfig/index.mjs';
import type { Suggestion } from './models/Suggestion.mjs';
import type { ExtensionId } from './models/types.mjs';

export type {
ConfigKind,
Expand Down Expand Up @@ -199,10 +200,6 @@ export type {
SpellCheckerSettingsProperties,
} from '../config/cspellConfig/index.mjs';

export type ExtensionId = 'cSpell';

export type DiagnosticSource = ExtensionId;

export type VSCodeSettingsCspell = {
[key in ExtensionId]?: config.CSpellUserSettings;
};
Expand Down
6 changes: 3 additions & 3 deletions packages/_server/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from './api.js';
// export * from './api.old.js';
export * from './apiModels.js';
export type * from './apiModels.js';
export * from './CommandsToClient.js';
export { SpellCheckerDiagnosticData } from './models/DiagnosticData.mjs';
export { Suggestion } from './models/Suggestion.mjs';
export type * from './models/index.mjs';
export type { Suggestion } from './models/Suggestion.mjs';
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import type { IssueType } from 'cspell-lib';
import type { IssueType } from '@cspell/cspell-types';
import type { Diagnostic } from 'vscode-languageserver-types';

import type { Suggestion } from './Suggestion.mjs';
import type { DiagnosticSource } from './types.mjs';

export interface SpellCheckerDiagnosticData {
/** The text of the issue. It is expected to match `document.getText(diag.range)` */
text?: string;
issueType?: IssueType | undefined;
/** The issue indicates that the word has been flagged as an error. */
isFlagged?: boolean | undefined;
/** The issue is a suggested change, but is not considered an error. */
isSuggestion?: boolean | undefined;
suggestions?: Suggestion[] | undefined;
}

export interface SpellingDiagnostic extends Diagnostic {
source: DiagnosticSource;
data: SpellCheckerDiagnosticData;
}
2 changes: 2 additions & 0 deletions packages/_server/src/api/models/index.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type { SpellCheckerDiagnosticData, SpellingDiagnostic } from './Diagnostic.mjs';
export type { DiagnosticSource, ExtensionId } from './types.mjs';
3 changes: 3 additions & 0 deletions packages/_server/src/api/models/types.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type ExtensionId = 'cSpell';

export type DiagnosticSource = ExtensionId;
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface CSpellSettingsPackageProperties extends CSpellSettings {

/**
* @scope resource
* @default 5
* @default 20
*/
maxDuplicateProblems?: number;

Expand Down
6 changes: 4 additions & 2 deletions packages/_server/src/validator.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { TextDocument } from 'vscode-languageserver-textdocument';
import type { Diagnostic } from 'vscode-languageserver-types';
import { DiagnosticSeverity } from 'vscode-languageserver-types';

import type { SpellCheckerDiagnosticData, Suggestion } from './api.js';
import type { SpellCheckerDiagnosticData, SpellingDiagnostic, Suggestion } from './api.js';
import type { CSpellUserSettings } from './config/cspellConfig/index.mjs';
import { isScmUri } from './config/docUriHelper.mjs';
import { diagnosticSource } from './constants.mjs';
Expand Down Expand Up @@ -53,12 +53,14 @@ export async function validateTextDocument(textDocument: TextDocument, options:
const diagMessage = `"${text}": ${message ?? `${isFlagged ? 'Forbidden' : 'Unknown'} word`}.`;
const sugs = suggestionsEx || suggestions?.map((word) => ({ word }));
const data: SpellCheckerDiagnosticData = {
text,
issueType,
isFlagged,
isSuggestion: undefined, // This is a future enhancement to CSpell.
suggestions: haveSuggestionsMatchCase(text, sugs),
};
return { severity, range, message: diagMessage, source: diagSource, data };
const diag: SpellingDiagnostic = { severity, range, message: diagMessage, source: diagSource, data };
return diag;
})
.filter((diag) => !!diag.severity);
return diags;
Expand Down
58 changes: 53 additions & 5 deletions packages/client/src/applyCorrections.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { uriToName } from '@internal/common-utils';
import type { Location, Range, TextDocument, Uri } from 'vscode';
import { commands, TextEdit, window, workspace, WorkspaceEdit } from 'vscode';
import type { Converter } from 'vscode-languageclient/lib/common/protocolConverter';
Expand All @@ -6,7 +7,7 @@ import type { LanguageClient, TextEdit as LsTextEdit } from 'vscode-languageclie
import * as di from './di';
import { toRegExp } from './extensionRegEx/evaluateRegExp';
import * as Settings from './settings';
import { findTextDocument } from './util/findEditor';
import { findEditor, findTextDocument } from './util/findEditor';
import { pVoid } from './util/pVoid';

const propertyFixSpellingWithRenameProvider = Settings.ConfigFields.fixSpellingWithRenameProvider;
Expand Down Expand Up @@ -36,14 +37,17 @@ async function findEditBounds(document: TextDocument, range: Range, useReference
return wordRange;
}

async function applyTextEdits(client: LanguageClient, uri: Uri, edits: LsTextEdit[]): Promise<boolean> {
async function applyLsTextEdits(client: LanguageClient, uri: Uri, edits: LsTextEdit[]): Promise<boolean> {
function toTextEdit(edit: LsTextEdit): TextEdit {
return client.protocol2CodeConverter.asTextEdit(edit);
}

return applyTextEdits(uri, edits.map(toTextEdit));
}

async function applyTextEdits(uri: Uri, edits: TextEdit[]): Promise<boolean> {
const wsEdit = new WorkspaceEdit();
const textEdits: TextEdit[] = edits.map(toTextEdit);
wsEdit.set(uri, textEdits);
wsEdit.set(uri, edits);
try {
return await workspace.applyEdit(wsEdit);
} catch (e) {
Expand Down Expand Up @@ -114,7 +118,7 @@ export async function handleApplyTextEdits(uri: string, documentVersion: number,
}
}

const success = await applyTextEdits(client, doc.uri, edits);
const success = await applyLsTextEdits(client, doc.uri, edits);
return success
? undefined
: pVoid(window.showErrorMessage('Failed to apply spelling changes to the document.'), 'handlerApplyTextEdits2');
Expand Down Expand Up @@ -157,3 +161,47 @@ export async function handleFixSpellingIssue(docUri: Uri, text: string, withText
return pVoid(window.showErrorMessage('Failed to apply spelling changes to the document.'), 'handleFixSpellingIssue');
}
}

export async function actionAutoFixSpellingIssues(uri?: Uri) {
// console.error('actionAutoFixSpellingIssues %o', { uri });
uri ??= window.activeTextEditor?.document.uri;
const doc = findEditor(uri)?.document || findTextDocument(uri);
if (!uri || !doc) {
return pVoid(
window.showInformationMessage('Unable to fix spelling issues in current document, document not found.'),
'actionAutoFixSpellingIssues',
);
}

const issueTracker = di.get('issueTracker');

const autoFixes = issueTracker
.getDiagnostics(uri)
.map((diag) => ({ range: diag.range, ...diag.data }))
.filter(
(fix) =>
fix.suggestions?.[0]?.isPreferred && !fix.suggestions?.[1]?.isPreferred && fix.text && !fix.issueType && !fix.isSuggestion,
)
.filter((fix) => doc.getText(fix.range) === fix.text)
.map((fix) => {
const sug = fix.suggestions?.[0].word;
assert(sug !== undefined);
return new TextEdit(fix.range, sug);
});

if (!autoFixes.length) {
const name = uriToName(uri);
return pVoid(window.showInformationMessage(`No auto fixable spelling issues found in ${name}.`), 'actionAutoFixSpellingIssues');
}

const success = applyTextEdits(uri, autoFixes);
if (!success) {
return pVoid(window.showInformationMessage('Unable to apply fixes.'), 'actionAutoFixSpellingIssues');
}
}

function assert(x: unknown, msg = 'A truthy value is expected.'): asserts x {
if (!x) {
throw Error(msg);
}
}
17 changes: 7 additions & 10 deletions packages/client/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Command, ConfigurationScope, Diagnostic, Disposable, QuickPickOpti
import { commands, FileType, Position, Range, Selection, TextEditorRevealType, window, workspace } from 'vscode';
import type { Position as LsPosition, Range as LsRange, TextEdit as LsTextEdit } from 'vscode-languageclient/node';

import { handleApplyTextEdits, handleFixSpellingIssue } from './applyCorrections';
import { actionAutoFixSpellingIssues, handleApplyTextEdits, handleFixSpellingIssue } from './applyCorrections';
import type { ClientSideCommandHandlerApi } from './client';
import { actionSuggestSpellingCorrections } from './codeActions/actionSuggestSpellingCorrections';
import * as di from './di';
Expand Down Expand Up @@ -591,14 +591,11 @@ function toLsTextEdit(edit: LsTextEdit | TextEdit): LsTextEdit {
};
}

function handleSelectRange(uri?: Uri, range?: Range) {
async function handleSelectRange(uri?: Uri, range?: Range): Promise<void> {
if (!uri || !range) return;
const editor = findEditor(uri);
if (!editor) return;
editor.revealRange(range);
editor.selection = new Selection(range.start, range.end);
}

function actionAutoFixSpellingIssues(...params: unknown[]) {
console.error('actionAutoFixSpellingIssues %o', params);
// const editor = findEditor(uri);
// if (!editor) return;
// editor.revealRange(range);
// editor.selection = new Selection(range.start, range.end);
await window.showTextDocument(uri, { selection: range });
}

0 comments on commit 356fae5

Please sign in to comment.