fix(spelling): keep dictionary saves on read-only documents (#1756)

Typo quick fixes vanished entirely in read-only documents (library sources, stubs such as pyrefly's bundled typeshed), although saving a word to the project or application dictionary only writes settings. Only the in-place Change to…/Rename to… fix now needs a writable document; Save 'word' to dictionary and its per-level submenu are offered everywhere.

Co-authored-by: muutot <[email protected]>
This commit is contained in:
Muuandmuutot authored and GitHub committed 2026-11-07 10:36:09 +08:00
1 parent 438ab158ad
commit 715ebf19e2
2 files changed
+40 -10

No files matched your search

+21
View File
@@ -119,6 +119,27 @@ describe("Typo inspection", () => {
expect(spellingIntentions(view)[0]!.label).toBe("Typo: Rename to…");
});
it("in a read-only document saves to dictionaries but changes nothing in place", async () => {
vi.useFakeTimers();
useProject.setState({ project: { root: "/p" } as never, settings: {} as never });
const view = new EditorView({
state: EditorState.create({
doc: "// mesage here",
extensions: [
lspDoc.of({ doc: 7, path: "/p/a.rs", language: "rust", uiMode: null, readOnly: true }),
spellingExtension(),
],
}),
});
views.push(view);
await settle();
expect(spellCheck).toHaveBeenCalled();
view.dispatch({ selection: { anchor: 5 } });
expect(spellingIntentions(view).map((i) => i.label)).toEqual(["Save 'mesage' to dictionary"]);
await spellingIntentions(view)[0]!.run();
expect(settingsSet).toHaveBeenCalledWith("project", "spelling.projectWords", JSON.stringify(["mesage"]));
});
it("checks only what the Typo options include, and re-checks when they change", async () => {
vi.useFakeTimers();
const options = (o: Record<string, boolean>) =>
+19 -10
View File
@@ -134,11 +134,15 @@ async function changeTo(view: EditorView, typo: PlacedTypo) {
});
}
/** Typo quick fixes at the caret (⌥↵), or none. */
/**
* Typo quick fixes at the caret (⌥↵), or none. Dictionary saves write
* settings, so they are offered in read-only documents too (library sources,
* stubs); only the in-place correction needs a writable file.
*/
export function spellingIntentions(view: EditorView): ListPopupItem[] {
const doc = docOf(view);
const sel = view.state.selection.main;
if (!doc || doc.readOnly || !sel.empty || !spellingEnabled()) return [];
if (!doc || !sel.empty || !spellingEnabled()) return [];
const typo = typoAt(view.state, sel.head);
if (!typo) return [];
const hasProject = !!useProject.getState().project;
@@ -162,15 +166,20 @@ export function spellingIntentions(view: EditorView): ListPopupItem[] {
{ kind: "separator" },
...intentionControls({ id: SPELLING_INSPECTION, name: "Typo" }),
];
const change: ListPopupItem[] = doc.readOnly
? []
: [
{
id: "spelling.change",
label: typo.context === "code" ? "Typo: Rename to…" : "Typo: Change to…",
icon: "bulb-fix",
emphasis: true,
run: () => changeTo(view, typo),
submenu: saveSubmenu,
},
];
return [
{
id: "spelling.change",
label: typo.context === "code" ? "Typo: Rename to…" : "Typo: Change to…",
icon: "bulb-fix",
emphasis: true,
run: () => changeTo(view, typo),
submenu: saveSubmenu,
},
...change,
{
id: "spelling.save",
label: tx`Save '${typo.word}' to dictionary`,