mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(format): re-detect indents after Reformat Code (#957)
After Reformat Code rewrote a file's indents (e.g. spaces to tabs per Code Style), the editor kept the indents detected when the file was opened: tabs rendered at the old width, the status bar showed the old indent, and Enter inserted the old indent, mixing tabs and spaces until the file was reopened. Whole-file format edits from language servers and contributed formatters are now tagged `format.reformat`, and after such a transaction the document runs the open-time indent detection again and reapplies its settings, so every split picks up the new indent. Refs #935. Co-authored-by: jaw52 <53564781+jaw52@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { language as languageFacet } from "@codemirror/language";
|
||||
import { indentUnit, language as languageFacet } from "@codemirror/language";
|
||||
import { registerSaveParticipant, remoteChange } from "@/editor/api";
|
||||
import { createMemoryBackend, type MemoryBackend } from "../backend";
|
||||
import { _resetLanguages } from "../cm/languages";
|
||||
@@ -56,6 +56,33 @@ describe("DocumentManager + slots", () => {
|
||||
expect(formatOptionsOf(slot.currentState())).toEqual({ tabSize: 1, insertSpaces: false });
|
||||
});
|
||||
|
||||
it("detects the indents again after Reformat Code rewrote them", async () => {
|
||||
const settings = {
|
||||
editor: {
|
||||
codeStyle: {
|
||||
languages: { java: { useTab: true, tabSize: 1, indent: 1 } },
|
||||
detectIndents: true,
|
||||
editorconfig: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const { manager } = setup({ "/p/A.java": "class A {\n int x;\n}\n" }, settings);
|
||||
const model = await manager.acquire("/p/A.java");
|
||||
const first = new EditorSlot(model, "t1", host);
|
||||
const split = new EditorSlot(model, "t2", host, { from: first });
|
||||
expect(first.currentState().tabSize).toBe(4);
|
||||
// Typing keeps the indents detected when the file was opened.
|
||||
first.dispatchLocal({ changes: { from: 10, to: 14, insert: "\t" }, userEvent: "input.type" });
|
||||
expect(first.currentState().tabSize).toBe(4);
|
||||
first.dispatchLocal({ changes: { from: 10, to: 11, insert: " " }, userEvent: "undo" });
|
||||
first.dispatchLocal({ changes: { from: 10, to: 14, insert: "\t" }, userEvent: "format.reformat" });
|
||||
for (const slot of [first, split]) {
|
||||
expect(slot.currentState().tabSize).toBe(1);
|
||||
expect(slot.currentState().facet(indentUnit)).toBe("\t");
|
||||
}
|
||||
expect(useDocMeta.getState()["/p/A.java"]?.indent).toEqual({ tabSize: 1, insertSpaces: false });
|
||||
});
|
||||
|
||||
it("shares one document between split slots and syncs edits to the backend", async () => {
|
||||
const { backend, manager, frame } = setup({ "/p/a.txt": "hello\n" });
|
||||
const model = await manager.acquire("/p/a.txt");
|
||||
|
||||
@@ -392,7 +392,7 @@ export class DocModel {
|
||||
|
||||
/** Per-file indentation chosen from the status bar; wins over settings until the file is closed. */
|
||||
indentOverride: { tabSize: number; insertSpaces: boolean } | null = null;
|
||||
/** Indentation detected in the file's text at open time. */
|
||||
/** Indentation detected in the file's text at open time and after Reformat Code. */
|
||||
detectedIndent: { insertSpaces: boolean; size?: number } | null = null;
|
||||
/** Last tree-sitter highlighting, for slots opened before the next refresh. */
|
||||
private highlights: { counter: number; value: SyntaxHighlights } | null = null;
|
||||
@@ -418,6 +418,19 @@ export class DocModel {
|
||||
return effectiveIndent(this.settings(), this.info, this.editorconfig, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the indents again after Reformat Code rewrote them: tabs written by
|
||||
* the formatter then render at the Code Style tab size and new lines indent
|
||||
* like them, instead of following the indents detected at open time.
|
||||
*/
|
||||
redetectIndent() {
|
||||
const detected = detectIndent(this.sync.localText.sliceString(0, 200_000));
|
||||
const current = this.detectedIndent;
|
||||
if (detected?.insertSpaces === current?.insertSpaces && detected?.size === current?.size) return;
|
||||
this.detectedIndent = detected;
|
||||
this.applySettings();
|
||||
}
|
||||
|
||||
/** Soft wraps toggled for this file (wins over settings until the file is closed). */
|
||||
softWrapOverride: boolean | null = null;
|
||||
|
||||
|
||||
@@ -199,6 +199,7 @@ export class EditorSlot {
|
||||
private onTransactions(trs: readonly Transaction[], view: EditorView) {
|
||||
view.update(trs);
|
||||
let selection = false;
|
||||
let reformatted = false;
|
||||
let navigatedFrom: { line: number; col: number } | undefined;
|
||||
for (const tr of trs) {
|
||||
if (!navigatedFrom && tr.selection && isNavigation(tr)) {
|
||||
@@ -217,8 +218,10 @@ export class EditorSlot {
|
||||
)
|
||||
this.host.onUserEdit(this, tr);
|
||||
}
|
||||
if (tr.docChanged && tr.isUserEvent("format.reformat")) reformatted = true;
|
||||
if (tr.selection || tr.docChanged) selection = true;
|
||||
}
|
||||
if (reformatted) this.model.redetectIndent();
|
||||
if (selection) this.host.onSelection(this, navigatedFrom);
|
||||
}
|
||||
|
||||
@@ -231,6 +234,7 @@ export class EditorSlot {
|
||||
const tr = this.state.update(spec);
|
||||
this.state = tr.state;
|
||||
if (tr.docChanged) this.model.onLocal(tr.changes, this);
|
||||
if (tr.docChanged && tr.isUserEvent("format.reformat")) this.model.redetectIndent();
|
||||
}
|
||||
|
||||
/** Applies changes that are already in the model (other slot / backend). */
|
||||
|
||||
@@ -161,7 +161,7 @@ async function format(
|
||||
}
|
||||
view.dispatch({
|
||||
changes,
|
||||
userEvent: "format",
|
||||
userEvent: "format.reformat",
|
||||
annotations: isolateHistory.of("full"),
|
||||
scrollIntoView: false,
|
||||
});
|
||||
@@ -235,7 +235,12 @@ function applyContributedFormat(
|
||||
if (opts.explicit) showHint(view, "No lines changed: content is already properly formatted");
|
||||
return NOTHING;
|
||||
}
|
||||
view.dispatch({ changes, userEvent: "format", annotations: isolateHistory.of("full"), scrollIntoView: false });
|
||||
view.dispatch({
|
||||
changes,
|
||||
userEvent: "format.reformat",
|
||||
annotations: isolateHistory.of("full"),
|
||||
scrollIntoView: false,
|
||||
});
|
||||
return { applied: true, changes: changes.length, lines: formattedLines(state.doc, changes) };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user