diff --git a/docs/_includes/generated-docs/configuration.md b/docs/_includes/generated-docs/configuration.md index 45489c552..3c9b00c94 100644 --- a/docs/_includes/generated-docs/configuration.md +++ b/docs/_includes/generated-docs/configuration.md @@ -351,6 +351,8 @@ Default | Setting | Scope | Description | | ------------------------------------------------------------------------------------------------ | -------------------- | ------------------------------------------------------------------------------- | | [`cSpell.autoFormatConfigFile`](#cspellautoformatconfigfile) | window | Auto Format Configuration File | +| [`cSpell.diagnosticLevel`](#cspelldiagnosticlevel) | resource | Set Diagnostic Reporting Level | +| [`cSpell.diagnosticLevelFlaggedWords`](#cspelldiagnosticlevelflaggedwords) | resource | Set Diagnostic Reporting Level for Flagged Words | | [`cSpell.hideAddToDictionaryCodeActions`](#cspellhideaddtodictionarycodeactions) | resource | Hide the options to add words to dictionaries or settings. | | [`cSpell.maxDuplicateProblems`](#cspellmaxduplicateproblems) | resource | The maximum number of times the same word can be flagged as an error in a file. | | [`cSpell.maxNumberOfProblems`](#cspellmaxnumberofproblems) | resource | Controls the maximum number of spelling errors per document. | @@ -388,6 +390,58 @@ Default --- +### `cSpell.diagnosticLevel` + +Name +: `cSpell.diagnosticLevel` -- Set Diagnostic Reporting Level + +Type +: ( `"Error"` \| `"Warning"` \| `"Information"` \| `"Hint"` ) + + | `Error` | Report Spelling Issues as Errors | + | `Warning` | Report Spelling Issues as Warnings | + | `Information` | Report Spelling Issues as Information | + | `Hint` | Report Spelling Issues as Hints, will not show up in Problems | + +Scope +: resource + +Description +: Issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle. + +Default +: _`"Information"`_ + +--- + +### `cSpell.diagnosticLevelFlaggedWords` + +Name +: `cSpell.diagnosticLevelFlaggedWords` -- Set Diagnostic Reporting Level for Flagged Words + +Type +: ( `"Error"` \| `"Warning"` \| `"Information"` \| `"Hint"` ) + + | `Error` | Report Spelling Issues as Errors | + | `Warning` | Report Spelling Issues as Warnings | + | `Information` | Report Spelling Issues as Information | + | `Hint` | Report Spelling Issues as Hints, will not show up in Problems | + +Scope +: resource + +Description +: Flagged word issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle. +By default, flagged words will use the same diagnostic level as general issues. Use this setting to customize them. + +Default +: _- none -_ + +Version +: 4.0.0 + +--- + ### `cSpell.hideAddToDictionaryCodeActions` Name @@ -1286,13 +1340,11 @@ Default # Appearance -| Setting | Scope | Description | -| -------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------- | -| [`cSpell.decorateIssues`](#cspelldecorateissues) | application | Draw custom decorations on Spelling Issues when the `#cSpell.diagnosticLevel#` is `Hint`. | -| [`cSpell.diagnosticLevel`](#cspelldiagnosticlevel) | resource | Set Diagnostic Reporting Level | -| [`cSpell.diagnosticLevelFlaggedWords`](#cspelldiagnosticlevelflaggedwords) | resource | Set Diagnostic Reporting Level for Flagged Words | -| [`cSpell.overviewRulerColor`](#cspelloverviewrulercolor) | application | The CSS color used to show issues in the ruler. | -| [`cSpell.textDecoration`](#cspelltextdecoration) | application | The CSS Style used to decorate spelling issues when `#cSpell.diagnosticLevel#` is `Hint`. | +| Setting | Scope | Description | +| -------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------- | +| [`cSpell.decorateIssues`](#cspelldecorateissues) | application | Draw custom decorations on Spelling Issues when the `#cSpell.diagnosticLevel#` is `Hint`. | +| [`cSpell.overviewRulerColor`](#cspelloverviewrulercolor) | application | The CSS color used to show issues in the ruler. | +| [`cSpell.textDecoration`](#cspelltextdecoration) | application | The CSS Style used to decorate spelling issues when `#cSpell.diagnosticLevel#` is `Hint`. | ## Definitions @@ -1318,58 +1370,6 @@ Version --- -### `cSpell.diagnosticLevel` - -Name -: `cSpell.diagnosticLevel` -- Set Diagnostic Reporting Level - -Type -: ( `"Error"` \| `"Warning"` \| `"Information"` \| `"Hint"` ) - - | `Error` | Report Spelling Issues as Errors | - | `Warning` | Report Spelling Issues as Warnings | - | `Information` | Report Spelling Issues as Information | - | `Hint` | Report Spelling Issues as Hints, will not show up in Problems | - -Scope -: resource - -Description -: Issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle. - -Default -: _`"Information"`_ - ---- - -### `cSpell.diagnosticLevelFlaggedWords` - -Name -: `cSpell.diagnosticLevelFlaggedWords` -- Set Diagnostic Reporting Level for Flagged Words - -Type -: ( `"Error"` \| `"Warning"` \| `"Information"` \| `"Hint"` ) - - | `Error` | Report Spelling Issues as Errors | - | `Warning` | Report Spelling Issues as Warnings | - | `Information` | Report Spelling Issues as Information | - | `Hint` | Report Spelling Issues as Hints, will not show up in Problems | - -Scope -: resource - -Description -: Flagged word issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle. -By default, flagged words will use the same diagnostic level as general issues. Use this setting to customize them. - -Default -: _- none -_ - -Version -: 4.0.0 - ---- - ### `cSpell.overviewRulerColor` Name diff --git a/fixtures/workspaces/jupyter/cspell.json b/fixtures/workspaces/jupyter/cspell.json index 2dbad2e9d..e5441c9b7 100644 --- a/fixtures/workspaces/jupyter/cspell.json +++ b/fixtures/workspaces/jupyter/cspell.json @@ -1,5 +1,8 @@ { - "flagWords": ["forbidd->forbid"], + "flagWords": [ + "forbidd->forbid" + ], + "suggestWords": ["againn->again"], "ignorePaths": [ "package-lock.json", "node_modules", diff --git a/package-lock.json b/package-lock.json index dc8c35d26..d8f3cda18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,13 +30,12 @@ "dependencies": { "@cspell/cspell-bundled-dicts": "^7.3.8", "@cspell/cspell-types": "^7.3.8", - "@tsconfig/node18": "^18.2.2", "@types/react": "^17.0.68", "cspell": "^7.3.8", "regexp-worker": "^2.0.2" }, "devDependencies": { - "@tsconfig/node16": "^16.1.1", + "@tsconfig/node18": "^18.2.2", "@types/jest-when": "^3.5.3", "@types/node": "^18.18.5", "@types/vscode": "1.80.0", @@ -2521,13 +2520,9 @@ "dev": true, "license": "MIT" }, - "node_modules/@tsconfig/node16": { - "version": "16.1.1", - "dev": true, - "license": "MIT" - }, "node_modules/@tsconfig/node18": { "version": "18.2.2", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/svelte": { diff --git a/package.json b/package.json index ecd97da1f..2524719dd 100644 --- a/package.json +++ b/package.json @@ -1970,44 +1970,6 @@ "type": "boolean", "version": "4.0.0" }, - "cSpell.diagnosticLevel": { - "default": "Information", - "enum": [ - "Error", - "Warning", - "Information", - "Hint" - ], - "enumDescriptions": [ - "Report Spelling Issues as Errors", - "Report Spelling Issues as Warnings", - "Report Spelling Issues as Information", - "Report Spelling Issues as Hints, will not show up in Problems" - ], - "markdownDescription": "Issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.", - "scope": "resource", - "title": "Set Diagnostic Reporting Level", - "type": "string" - }, - "cSpell.diagnosticLevelFlaggedWords": { - "enum": [ - "Error", - "Warning", - "Information", - "Hint" - ], - "enumDescriptions": [ - "Report Spelling Issues as Errors", - "Report Spelling Issues as Warnings", - "Report Spelling Issues as Information", - "Report Spelling Issues as Hints, will not show up in Problems" - ], - "markdownDescription": "Flagged word issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.\nBy default, flagged words will use the same diagnostic level as general issues. Use this setting to customize them.", - "scope": "resource", - "title": "Set Diagnostic Reporting Level for Flagged Words", - "type": "string", - "version": "4.0.0" - }, "cSpell.overviewRulerColor": { "default": "#00800080", "markdownDescription": "The CSS color used to show issues in the ruler.\n\n- Supports named colors: [CSS Colors](https://www.w3schools.com/cssref/css_colors.php)\n- Hex colors\n- Use `` (empty string) to disable.\n\nExamples:\n- `green`\n- `DarkYellow`\n- `#ffff0080` - semi-transparent yellow.", @@ -2598,6 +2560,44 @@ "title": "Auto Format Configuration File", "type": "boolean" }, + "cSpell.diagnosticLevel": { + "default": "Information", + "enum": [ + "Error", + "Warning", + "Information", + "Hint" + ], + "enumDescriptions": [ + "Report Spelling Issues as Errors", + "Report Spelling Issues as Warnings", + "Report Spelling Issues as Information", + "Report Spelling Issues as Hints, will not show up in Problems" + ], + "markdownDescription": "Issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.", + "scope": "resource", + "title": "Set Diagnostic Reporting Level", + "type": "string" + }, + "cSpell.diagnosticLevelFlaggedWords": { + "enum": [ + "Error", + "Warning", + "Information", + "Hint" + ], + "enumDescriptions": [ + "Report Spelling Issues as Errors", + "Report Spelling Issues as Warnings", + "Report Spelling Issues as Information", + "Report Spelling Issues as Hints, will not show up in Problems" + ], + "markdownDescription": "Flagged word issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.\nBy default, flagged words will use the same diagnostic level as general issues. Use this setting to customize them.", + "scope": "resource", + "title": "Set Diagnostic Reporting Level for Flagged Words", + "type": "string", + "version": "4.0.0" + }, "cSpell.hideAddToDictionaryCodeActions": { "default": false, "markdownDescription": "Hide the options to add words to dictionaries or settings.", @@ -2744,7 +2744,7 @@ "preinstall": "npx only-allow npm" }, "devDependencies": { - "@tsconfig/node16": "^16.1.1", + "@tsconfig/node18": "^18.2.2", "@types/jest-when": "^3.5.3", "@types/node": "^18.18.5", "@types/vscode": "1.80.0", @@ -2784,7 +2784,6 @@ "dependencies": { "@cspell/cspell-bundled-dicts": "^7.3.8", "@cspell/cspell-types": "^7.3.8", - "@tsconfig/node18": "^18.2.2", "@types/react": "^17.0.68", "cspell": "^7.3.8", "regexp-worker": "^2.0.2" diff --git a/packages/_server/spell-checker-config.schema.json b/packages/_server/spell-checker-config.schema.json index affbe5472..c4d6462b5 100644 --- a/packages/_server/spell-checker-config.schema.json +++ b/packages/_server/spell-checker-config.schema.json @@ -1628,36 +1628,6 @@ "type": "boolean", "version": "4.0.0" }, - "cSpell.diagnosticLevel": { - "default": "Information", - "description": "Issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.", - "enum": ["Error", "Warning", "Information", "Hint"], - "enumDescriptions": [ - "Report Spelling Issues as Errors", - "Report Spelling Issues as Warnings", - "Report Spelling Issues as Information", - "Report Spelling Issues as Hints, will not show up in Problems" - ], - "markdownDescription": "Issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.", - "scope": "resource", - "title": "Set Diagnostic Reporting Level", - "type": "string" - }, - "cSpell.diagnosticLevelFlaggedWords": { - "description": "Flagged word issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle. By default, flagged words will use the same diagnostic level as general issues. Use this setting to customize them.", - "enum": ["Error", "Warning", "Information", "Hint"], - "enumDescriptions": [ - "Report Spelling Issues as Errors", - "Report Spelling Issues as Warnings", - "Report Spelling Issues as Information", - "Report Spelling Issues as Hints, will not show up in Problems" - ], - "markdownDescription": "Flagged word issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.\nBy default, flagged words will use the same diagnostic level as general issues. Use this setting to customize them.", - "scope": "resource", - "title": "Set Diagnostic Reporting Level for Flagged Words", - "type": "string", - "version": "4.0.0" - }, "cSpell.overviewRulerColor": { "default": "#00800080", "description": "The CSS color used to show issues in the ruler.\n\n- Supports named colors: [CSS Colors](https://www.w3schools.com/cssref/css_colors.php)\n- Hex colors\n- Use `` (empty string) to disable.\n\nExamples:\n- `green`\n- `DarkYellow`\n- `#ffff0080` - semi-transparent yellow.", @@ -2251,6 +2221,36 @@ "title": "Auto Format Configuration File", "type": "boolean" }, + "cSpell.diagnosticLevel": { + "default": "Information", + "description": "Issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.", + "enum": ["Error", "Warning", "Information", "Hint"], + "enumDescriptions": [ + "Report Spelling Issues as Errors", + "Report Spelling Issues as Warnings", + "Report Spelling Issues as Information", + "Report Spelling Issues as Hints, will not show up in Problems" + ], + "markdownDescription": "Issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.", + "scope": "resource", + "title": "Set Diagnostic Reporting Level", + "type": "string" + }, + "cSpell.diagnosticLevelFlaggedWords": { + "description": "Flagged word issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle. By default, flagged words will use the same diagnostic level as general issues. Use this setting to customize them.", + "enum": ["Error", "Warning", "Information", "Hint"], + "enumDescriptions": [ + "Report Spelling Issues as Errors", + "Report Spelling Issues as Warnings", + "Report Spelling Issues as Information", + "Report Spelling Issues as Hints, will not show up in Problems" + ], + "markdownDescription": "Flagged word issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of the squiggle.\nBy default, flagged words will use the same diagnostic level as general issues. Use this setting to customize them.", + "scope": "resource", + "title": "Set Diagnostic Reporting Level for Flagged Words", + "type": "string", + "version": "4.0.0" + }, "cSpell.hideAddToDictionaryCodeActions": { "default": false, "description": "Hide the options to add words to dictionaries or settings.", diff --git a/packages/_server/src/api/api.ts b/packages/_server/src/api/api.ts index 6ee9f651f..32b46bf04 100644 --- a/packages/_server/src/api/api.ts +++ b/packages/_server/src/api/api.ts @@ -31,7 +31,7 @@ export interface ServerRequestsAPI { getConfigurationForDocument(req: GetConfigurationForDocumentRequest): GetConfigurationForDocumentResult; isSpellCheckEnabled(req: TextDocumentInfo): IsSpellCheckEnabledResult; splitTextIntoWords(req: string): SplitTextIntoWordsResult; - spellingSuggestions(req: TextDocumentInfo): SpellingSuggestionsResult; + spellingSuggestions(word: string, doc: TextDocumentInfo): SpellingSuggestionsResult; } /** Notifications that can be sent to the server */ diff --git a/packages/_server/src/api/apiModels.ts b/packages/_server/src/api/apiModels.ts index 330a574a2..1ea6a2144 100644 --- a/packages/_server/src/api/apiModels.ts +++ b/packages/_server/src/api/apiModels.ts @@ -1,5 +1,6 @@ import type { ConfigScopeVScode, ConfigTarget } from '../config/configTargets.mjs'; import type * as config from '../config/cspellConfig/index.mjs'; +import { Suggestion } from '../models/Suggestion.mjs'; export type { ConfigKind, @@ -58,7 +59,9 @@ export interface SplitTextIntoWordsResult { words: string[]; } -export interface SpellingSuggestionsResult {} +export interface SpellingSuggestionsResult { + suggestions: Suggestion[]; +} export interface TextDocumentInfo { uri?: UriString; diff --git a/packages/_server/src/config/cspellConfig/cspellConfig.mts b/packages/_server/src/config/cspellConfig/cspellConfig.mts index fe7f43b92..7daebca55 100644 --- a/packages/_server/src/config/cspellConfig/cspellConfig.mts +++ b/packages/_server/src/config/cspellConfig/cspellConfig.mts @@ -136,6 +136,8 @@ type VSConfigReporting = PrefixWithCspell<_VSConfigReporting>; type _VSConfigReporting = Pick< SpellCheckerSettingsVSCodeBase, | 'autoFormatConfigFile' + | 'diagnosticLevel' + | 'diagnosticLevelFlaggedWords' | 'hideAddToDictionaryCodeActions' | 'maxDuplicateProblems' | 'maxNumberOfProblems' @@ -211,10 +213,7 @@ type _VSConfigFilesAndFolders = Pick< * @order 6 */ type VSConfigAppearance = PrefixWithCspell<_VSConfigAppearance>; -type _VSConfigAppearance = Pick< - SpellCheckerSettingsVSCodeBase, - keyof AppearanceSettings | 'diagnosticLevel' | 'diagnosticLevelFlaggedWords' ->; +type _VSConfigAppearance = Pick; /** * @title Legacy diff --git a/packages/_server/src/server.mts b/packages/_server/src/server.mts index 51acc5562..a044e3d96 100644 --- a/packages/_server/src/server.mts +++ b/packages/_server/src/server.mts @@ -405,8 +405,8 @@ export function run(): void { }; } - async function handleSpellingSuggestions(_params: TextDocumentInfo): Promise { - return {}; + async function handleSpellingSuggestions(_text: string, _docRef?: TextDocumentInfo): Promise { + return { suggestions: [] }; } function sendDiagnostics(result: ValidationResult) { diff --git a/packages/_server/src/test/test.api.ts b/packages/_server/src/test/test.api.ts index 1f29053cc..979feb1e4 100644 --- a/packages/_server/src/test/test.api.ts +++ b/packages/_server/src/test/test.api.ts @@ -57,7 +57,7 @@ export function mockHandlers(): ServerSideHandlers { })), isSpellCheckEnabled: vi.fn(() => ({ ...sampleIsSpellCheckEnabledResult })), splitTextIntoWords: vi.fn(() => ({ words: [] })), - spellingSuggestions: vi.fn(), + spellingSuggestions: vi.fn(() => ({ suggestions: [] })), }, }; } diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts index 4e8ffe3dc..e769b570c 100644 --- a/packages/client/src/client/client.ts +++ b/packages/client/src/client/client.ts @@ -1,27 +1,17 @@ import { setOfSupportedSchemes, supportedSchemes } from '@internal/common-utils/uriHelper'; import type { WorkspaceConfigForDocument } from 'code-spell-checker-server/api'; -import type { Command, Diagnostic, DiagnosticCollection, ExtensionContext, Position, Range, TextDocument } from 'vscode'; -import { CodeAction, CodeActionKind, DiagnosticSeverity, Disposable, languages as vsCodeSupportedLanguages, Uri, workspace } from 'vscode'; -import type { - CodeActionParams, - Command as LanguageClientCommand, - ForkOptions, - LanguageClientOptions, - ServerOptions, -} from 'vscode-languageclient/node'; +import type { CodeAction, Diagnostic, DiagnosticCollection, ExtensionContext, Range, TextDocument } from 'vscode'; +import { Disposable, languages as vsCodeSupportedLanguages, Uri, workspace } from 'vscode'; +import type { CodeActionParams, ForkOptions, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; import { - CodeAction as VSCodeLangClientCodeAction, CodeActionContext as VSCodeLangClientCodeActionContext, - Diagnostic as VSCodeLangClientDiagnostic, - DiagnosticSeverity as VSCodeLangClientDiagnosticSeverity, LanguageClient, - Position as VSCodeLangClientPosition, - Range as VSCodeLangClientRange, TextDocumentIdentifier as VSCodeLangClientTextDocumentIdentifier, TransportKind as VSCodeLangClientTransportKind, } from 'vscode-languageclient/node'; import { diagnosticSource } from '../constants'; +import { isLcCodeAction, mapDiagnosticToLc, mapLcCodeAction, mapRangeToLc } from '../languageServer/clientHelpers'; import type { Inspect } from '../settings'; import * as Settings from '../settings'; import { inspectConfigKeys, sectionCSpell } from '../settings'; @@ -235,13 +225,13 @@ export class CSpellClient implements Disposable { public async requestSpellingSuggestions(doc: TextDocument, range: Range, diagnostics: Diagnostic[]): Promise { const params: CodeActionParams = { textDocument: VSCodeLangClientTextDocumentIdentifier.create(doc.uri.toString()), - range: mapRangeToLangClient(range), - context: VSCodeLangClientCodeActionContext.create(diagnostics.map(mapDiagnosticToLangClient)), + range: mapRangeToLc(range), + context: VSCodeLangClientCodeActionContext.create(diagnostics.map(mapDiagnosticToLc)), }; const r = await requestCodeAction(this.client, params); if (!r) return []; - const actions = r.filter(isCodeAction).map(mapCodeAction); + const actions = r.filter(isLcCodeAction).map(mapLcCodeAction); return actions; } @@ -302,50 +292,3 @@ function toConfigTarget(ins: Inspect | undefined, allowFolder: boolean): F folder: allowFolder && (workspaceFolderValue !== undefined || undefined), }; } - -function isCodeAction(c: LanguageClientCommand | VSCodeLangClientCodeAction): c is VSCodeLangClientCodeAction { - return VSCodeLangClientCodeAction.is(c); -} - -function mapCodeAction(c: VSCodeLangClientCodeAction): CodeAction { - const kind = (c.kind !== undefined && CodeActionKind.Empty.append(c.kind)) || undefined; - const action = new CodeAction(c.title, kind); - action.command = c.command && mapCommand(c.command); - return action; -} - -function mapCommand(c: LanguageClientCommand): Command { - return c; -} - -type MapDiagnosticSeverity = { - [key in DiagnosticSeverity]: VSCodeLangClientDiagnosticSeverity; -}; - -const diagSeverityMap: MapDiagnosticSeverity = { - [DiagnosticSeverity.Error]: VSCodeLangClientDiagnosticSeverity.Error, - [DiagnosticSeverity.Warning]: VSCodeLangClientDiagnosticSeverity.Warning, - [DiagnosticSeverity.Information]: VSCodeLangClientDiagnosticSeverity.Information, - [DiagnosticSeverity.Hint]: VSCodeLangClientDiagnosticSeverity.Hint, -}; - -function mapDiagnosticToLangClient(d: Diagnostic): VSCodeLangClientDiagnostic { - const diag = VSCodeLangClientDiagnostic.create( - mapRangeToLangClient(d.range), - d.message, - diagSeverityMap[d.severity], - undefined, - d.source, - ); - return diag; -} - -function mapRangeToLangClient(r: Range): VSCodeLangClientRange { - const { start, end } = r; - return VSCodeLangClientRange.create(mapPositionToLangClient(start), mapPositionToLangClient(end)); -} - -function mapPositionToLangClient(p: Position): VSCodeLangClientPosition { - const { line, character } = p; - return VSCodeLangClientPosition.create(line, character); -} diff --git a/packages/client/src/commands.ts b/packages/client/src/commands.ts index 1b6b5b02b..061035d4d 100644 --- a/packages/client/src/commands.ts +++ b/packages/client/src/commands.ts @@ -8,6 +8,7 @@ import type { QuickPickOptions, TextDocument, TextEdit, + TextEditor, Uri, } from 'vscode'; import { commands, FileType, Position, Range, Selection, TextEditorRevealType, window, workspace, WorkspaceEdit } from 'vscode'; @@ -17,6 +18,8 @@ import type { ClientSideCommandHandlerApi, SpellCheckerSettingsProperties } from import * as di from './di'; import { extractMatchingDiagRanges, extractMatchingDiagTexts, getCSpellDiags } from './diags'; import { toRegExp } from './extensionRegEx/evaluateRegExp'; +import { toRange } from './languageServer/clientHelpers'; +import type { RangeLike } from './languageServer/models'; import type { ConfigTargetLegacy, TargetsAndScopes } from './settings'; import * as Settings from './settings'; import { @@ -502,37 +505,54 @@ const compareStrings = new Intl.Collator().compare; function onCommandUseDiagsSelectionOrPrompt( prompt: string, fnAction: (text: string, uri: Uri | undefined) => Promise, -): () => Promise { - return async function () { - const document = window.activeTextEditor?.document; - const selection = window.activeTextEditor?.selection; - const range = selection && document?.getWordRangeAtPosition(selection.active); - const diags = document ? getCSpellDiags(document.uri) : undefined; - const matchingDiagWords = normalizeWords(extractMatchingDiagTexts(document, selection, diags) || []); - if (matchingDiagWords.length) { - const picked = - selection?.anchor.isEqual(selection.active) && matchingDiagWords.length === 1 - ? matchingDiagWords - : await chooseWords(matchingDiagWords.sort(compareStrings), { title: prompt, placeHolder: 'Choose words' }); - if (!picked) return; - return fnAction(picked.join(' '), document?.uri); - } +): (text?: string, uri?: Uri | string) => Promise { + return async function (text?: string, uri?: Uri | string) { + const selected = await determineTextSelection(prompt, text, uri); + if (!selected) return; + + const editor = window.activeTextEditor; + await fnAction(selected.text, selected.uri); + await (editor?.document && window.showTextDocument(editor.document)); + }; +} - if (!range || !selection || !document || !document.getText(range)) { - const word = await window.showInputBox({ title: prompt, prompt }); - if (!word) return; - return fnAction(word, document?.uri); - } +async function determineTextSelection(prompt: string, text?: string, uri?: Uri | string): Promise<{ text: string; uri?: Uri } | undefined> { + uri = toUri(uri); + if (text) { + return { text, uri: uri || window.activeTextEditor?.document.uri }; + } - const text = selection.contains(range) ? document.getText(selection) : document.getText(range); - const words = normalizeWords(text); + const editor = findEditor(uri); + + const document = editor?.document; + const selection = editor?.selection; + const range = selection && document?.getWordRangeAtPosition(selection.active); + const diags = document ? getCSpellDiags(document.uri) : undefined; + const matchingDiagWords = normalizeWords(extractMatchingDiagTexts(document, selection, diags) || []); + if (matchingDiagWords.length) { const picked = - words.length > 1 - ? await chooseWords(words.sort(compareStrings), { title: prompt, placeHolder: 'Choose words' }) - : [await window.showInputBox({ title: prompt, prompt, value: words[0] })]; + selection?.anchor.isEqual(selection.active) && matchingDiagWords.length === 1 + ? matchingDiagWords + : await chooseWords(matchingDiagWords.sort(compareStrings), { title: prompt, placeHolder: 'Choose words' }); if (!picked) return; - return fnAction(picked.join(' '), document?.uri); - }; + return { text: picked.join(' '), uri: document?.uri }; + } + + if (!range || !selection || !document || !document.getText(range)) { + const word = await window.showInputBox({ title: prompt, prompt }); + if (!word) return; + return { text: word, uri: document?.uri }; + } + + text = selection.contains(range) ? document.getText(selection) : document.getText(range); + + const words = normalizeWords(text); + const picked = + words.length > 1 + ? await chooseWords(words.sort(compareStrings), { title: prompt, placeHolder: 'Choose words' }) + : [await window.showInputBox({ title: prompt, prompt, value: words[0] })]; + if (!picked) return; + return { text: picked.join(' '), uri: document.uri }; } async function chooseWords(words: string[], options: QuickPickOptions): Promise { @@ -557,10 +577,12 @@ interface SuggestionQuickPickItem extends QuickPickItem { _action: CodeAction; } -async function actionSuggestSpellingCorrections(): Promise { - const document = window.activeTextEditor?.document; - const selection = window.activeTextEditor?.selection; - const range = selection && document?.getWordRangeAtPosition(selection.active); +async function actionSuggestSpellingCorrections(docUri?: Uri, rangeLike?: RangeLike, text?: string): Promise { + console.log('Args: %o', { docUri, range: rangeLike, text }); + const editor = findEditor(docUri); + const document = editor?.document; + const selection = editor?.selection; + const range = (rangeLike && toRange(rangeLike)) || (selection && document?.getWordRangeAtPosition(selection.active)); const diags = document ? getCSpellDiags(document.uri) : undefined; const matchingRanges = extractMatchingDiagRanges(document, selection, diags); const r = matchingRanges?.[0] || range; @@ -684,3 +706,17 @@ function toConfigToRegExp(regExStr: string | undefined, flags = 'g'): RegExp | u } return undefined; } + +function findEditor(uri?: Uri): TextEditor | undefined { + if (!uri) return window.activeTextEditor; + + const uriStr = uri.toString(); + + for (const editor of window.visibleTextEditors) { + if (editor.document.uri.toString() === uriStr) { + return editor; + } + } + + return undefined; +} diff --git a/packages/client/src/decorate.ts b/packages/client/src/decorate.ts index 09e6c21cd..6584a01e1 100644 --- a/packages/client/src/decorate.ts +++ b/packages/client/src/decorate.ts @@ -1,5 +1,5 @@ import { createDisposableList } from 'utils-disposables'; -import type { DecorationOptions, Diagnostic, DiagnosticChangeEvent, TextEditor, TextEditorDecorationType, Uri } from 'vscode'; +import type { DecorationOptions, Diagnostic, DiagnosticChangeEvent, TextDocument, TextEditor, TextEditorDecorationType, Uri } from 'vscode'; import vscode, { DiagnosticSeverity, MarkdownString } from 'vscode'; import { getCSpellDiags } from './diags'; @@ -30,11 +30,12 @@ export class SpellingIssueDecorator implements Disposable { refreshDiagnosticsInEditor(editor: TextEditor) { if (!this.decorationType) return; - const diags = getCSpellDiags(editor.document.uri); + const doc = editor.document; + const diags = getCSpellDiags(doc.uri); const decorations: DecorationOptions[] = diags .filter((diag) => diag.severity === DiagnosticSeverity.Hint) - .map(diagToDecorationOptions); + .map((diag) => diagToDecorationOptions(diag, doc)); editor.setDecorations(this.decorationType, decorations); } @@ -56,18 +57,36 @@ export class SpellingIssueDecorator implements Disposable { const overviewRulerColor: string | undefined = vscode.workspace.getConfiguration('cSpell').get('overviewRulerColor') || undefined; const textDecoration: string | undefined = vscode.workspace.getConfiguration('cSpell').get('textDecoration') || undefined; - return vscode.window.createTextEditorDecorationType({ + const decorator = vscode.window.createTextEditorDecorationType({ isWholeLine: false, rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, overviewRulerLane: vscode.OverviewRulerLane.Right, overviewRulerColor: overviewRulerColor, textDecoration: textDecoration, }); + return decorator; } } -function diagToDecorationOptions(diag: Diagnostic): DecorationOptions { +function diagToDecorationOptions(diag: Diagnostic, doc: TextDocument): DecorationOptions { const { range } = diag; - const hoverMessage = new MarkdownString(diag.message); + const text = doc.getText(range); + const commandSuggest = commandUri('cSpell.suggestSpellingCorrections', doc.uri, range, text); + const commandAdd = commandUri('cSpell.addWordToDictionary', text); + const hoverMessage = new MarkdownString(diag.message) + .appendText(' ') + .appendMarkdown(markdownLink('Suggest', commandSuggest, 'Show suggestions.')) + .appendText(', ') + .appendMarkdown(markdownLink('Add', commandAdd, 'Add word to dictionary.')); + hoverMessage.isTrusted = true; return { range, hoverMessage }; } + +function commandUri(command: string, ...params: unknown[]): string { + return `command:${command}?${encodeURIComponent(JSON.stringify(params))}`; +} + +function markdownLink(text: string, uri: string, hover?: string) { + const hoverText = hover ? ` "${hover}"` : ''; + return `[${text}](${uri}${hoverText})`; +} diff --git a/packages/client/src/languageServer/MapDiagnosticSeverity.ts b/packages/client/src/languageServer/MapDiagnosticSeverity.ts new file mode 100644 index 000000000..ae0a4a441 --- /dev/null +++ b/packages/client/src/languageServer/MapDiagnosticSeverity.ts @@ -0,0 +1,14 @@ +import { DiagnosticSeverity } from 'vscode'; +import type { DiagnosticSeverity as DiagnosticSeverityNum } from 'vscode-languageclient/node'; +import { DiagnosticSeverity as LcDiagnosticSeverity } from 'vscode-languageclient/node'; + +type MapDiagnosticSeverity = { + [key in DiagnosticSeverity]: DiagnosticSeverityNum; +}; + +export const diagSeverityMap: MapDiagnosticSeverity = { + [DiagnosticSeverity.Error]: LcDiagnosticSeverity.Error, + [DiagnosticSeverity.Warning]: LcDiagnosticSeverity.Warning, + [DiagnosticSeverity.Information]: LcDiagnosticSeverity.Information, + [DiagnosticSeverity.Hint]: LcDiagnosticSeverity.Hint, +}; diff --git a/packages/client/src/languageServer/clientHelpers.ts b/packages/client/src/languageServer/clientHelpers.ts new file mode 100644 index 000000000..2b3ad6cf2 --- /dev/null +++ b/packages/client/src/languageServer/clientHelpers.ts @@ -0,0 +1,56 @@ +import type { Command, Diagnostic } from 'vscode'; +import { CodeAction, CodeActionKind, Position, Range } from 'vscode'; +import type { Command as LcCommand } from 'vscode-languageclient/node'; +import { + CodeAction as LcCodeAction, + Diagnostic as LcDiagnostic, + Position as LcPosition, + Range as LcRange, +} from 'vscode-languageclient/node'; + +import { diagSeverityMap } from './MapDiagnosticSeverity'; +import type { RangeLike } from './models'; + +export function isLcCodeAction(c: LcCommand | LcCodeAction): c is LcCodeAction { + return LcCodeAction.is(c); +} + +export function mapLcCodeAction(c: LcCodeAction): CodeAction { + const kind = (c.kind !== undefined && CodeActionKind.Empty.append(c.kind)) || undefined; + const action = new CodeAction(c.title, kind); + action.command = c.command && mapLcCommand(c.command); + return action; +} + +function mapLcCommand(c: LcCommand): Command { + return c; +} + +export function mapDiagnosticToLc(d: Diagnostic): LcDiagnostic { + const diag = LcDiagnostic.create(mapRangeToLc(d.range), d.message, diagSeverityMap[d.severity], undefined, d.source); + return diag; +} + +export function mapRangeToLc(r: Range): LcRange { + const { start, end } = r; + return LcRange.create(mapPositionToLangClient(start), mapPositionToLangClient(end)); +} + +export function mapPositionToLangClient(p: Position): LcPosition { + const { line, character } = p; + return LcPosition.create(line, character); +} + +export function toPosition(p: Position | LcPosition | [number, number]): Position { + if (p instanceof Position) return p; + if (Array.isArray(p)) return new Position(p[0], p[1]); + return new Position(p.line, p.character); +} + +export function toRange(r: RangeLike): Range { + if (r instanceof Range) return r; + if (Array.isArray(r)) { + return new Range(toPosition(r[0]), toPosition(r[1])); + } + return new Range(toPosition(r.start), toPosition(r.end)); +} diff --git a/packages/client/src/languageServer/index.ts b/packages/client/src/languageServer/index.ts new file mode 100644 index 000000000..1648a5eb4 --- /dev/null +++ b/packages/client/src/languageServer/index.ts @@ -0,0 +1,2 @@ +export {} from './clientHelpers'; +export type * from './models'; diff --git a/packages/client/src/languageServer/models.ts b/packages/client/src/languageServer/models.ts new file mode 100644 index 000000000..1e187c196 --- /dev/null +++ b/packages/client/src/languageServer/models.ts @@ -0,0 +1,4 @@ +import type { Range } from 'vscode'; +import type { Position as LcPosition, Range as LcRange } from 'vscode-languageclient/node'; + +export type RangeLike = Range | LcRange | [LcPosition, LcPosition]; diff --git a/tsconfig.base.json b/tsconfig.base.json index d225e5217..69819eb75 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/tsconfig", "display": "Spell Checker Base Config", - "extends": "@tsconfig/node16/tsconfig.json", + "extends": "@tsconfig/node18/tsconfig.json", "compilerOptions": { "strict": true, "strictFunctionTypes": true,