chore(code): add smart tab behaviour

This commit is contained in:
Elian Doran
2025-05-11 17:39:10 +03:00
parent 9bbe111dd9
commit 4bac03570c
5 changed files with 46 additions and 2 deletions

View File

@@ -0,0 +1,43 @@
import { indentLess, indentMore } from "@codemirror/commands";
import type { KeyBinding } from "@codemirror/view";
const smartIndentWithTab: KeyBinding[] = [
{
key: "Tab",
run({ state, dispatch}) {
const { selection } = state;
for (const range of selection.ranges) {
if (!range.empty) {
// Allow default behaviour.
return false;
}
const line = state.doc.lineAt(range.head);
const beforeCursor = state.doc.sliceString(line.from, range.head);
if (/^\s*$/.test(beforeCursor)) {
// Only whitespace before cursor: indent line
return indentMore({state, dispatch});
} else {
// Insert a tab character
const cursor = range.head;
dispatch(state.update({
changes: {
from: cursor,
insert: "\t"
},
selection: { anchor: cursor + 1 },
scrollIntoView: true,
userEvent: "input"
}));
return true;
}
}
return false;
},
shift: indentLess
},
]
export default smartIndentWithTab;

View File

@@ -0,0 +1,72 @@
import { lint as _lint } from "./eslint.js";
import { trimIndentation } from "@triliumnext/commons";
import { describe, expect, it } from "vitest";
async function lint(code: string, mimeType: string) {
const linterData = await _lint(mimeType);
if (!("linter" in linterData)) {
return [];
}
const { linter, config } = linterData;
const result = linter.verify(code, config);
return result;
}
describe("Linter", () => {
it("reports some basic errors", async () => {
const result = await lint(trimIndentation`
for (const i = 0; i<10; i++) {
}
`, "application/javascript;env=frontend");
expect(result).toMatchObject([
{ message: "'i' is constant.", },
{ message: "Empty block statement." }
]);
});
it("reports no error for correct script", async () => {
const result = await lint(trimIndentation`
const foo = "bar";
console.log(foo.toString());
for (const x of [ 1, 2, 3]) {
console.log(x?.toString());
}
api.showMessage("Hi");
`, "application/javascript;env=frontend");
expect(result.length).toBe(0);
});
it("reports unused functions as warnings", async () => {
const result = await lint(trimIndentation`
function hello() { }
function world() { }
console.log("Hello world");
`, "application/javascript;env=frontend");
expect(result).toMatchObject([
{
message: "'hello' is defined but never used.",
severity: 1
},
{
message: "'world' is defined but never used.",
severity: 1
}
]);
});
it("supports JQuery global", async () => {
expect(await lint(`$("<div>");`, "application/javascript;env=backend")).toMatchObject([{ "ruleId": "no-undef" }]);
expect(await lint(`console.log($("<div>"));`, "application/javascript;env=frontend")).toStrictEqual([]);
});
it("supports module.exports", async () => {
expect(await lint(`module.exports("Hi");`, "application/javascript;env=backend")).toStrictEqual([]);
expect(await lint(`module.exports("Hi");`, "application/javascript;env=frontend")).toStrictEqual([]);
});
it("ignores TypeScript file", async () => {
expect(await lint("export async function lint(code: string, mimeType: string) {}", "text/typescript-jsx")).toStrictEqual([]);
});
});

View File

@@ -0,0 +1,46 @@
import type { Linter } from "eslint-linter-browserify";
export async function lint(mimeType: string) {
const Linter = (await import("eslint-linter-browserify")).Linter;
const js = (await import("@eslint/js"));
const globalDefinitions = (await import("globals"));
let globals: Record<string, any> = {
...globalDefinitions.browser,
api: "readonly",
module: "readonly"
};
// Unsupported languages
if (mimeType.startsWith("text/typescript")) {
return [];
}
// Custom globals
if (mimeType === "application/javascript;env=frontend") {
globals = { ...globals, ...globalDefinitions.jquery };
} else if (mimeType === "application/javascript;env=backend") {
}
const config: (Linter.LegacyConfig | Linter.Config | Linter.Config[]) = [
js.configs.recommended,
{
languageOptions: {
parserOptions: {
ecmaVersion: 2024
},
globals
},
rules: {
"no-unused-vars": [ "warn", { vars: "local", args: "after-used" }]
}
}
];
return {
linter: new Linter(),
config
}
}