mirror of
https://github.com/zadam/trilium.git
synced 2025-12-21 23:59:59 +01:00
chore(code): add smart tab behaviour
This commit is contained in:
43
packages/codemirror/src/extensions/custom_tab.ts
Normal file
43
packages/codemirror/src/extensions/custom_tab.ts
Normal 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;
|
||||
72
packages/codemirror/src/extensions/eslint.spec.ts
Normal file
72
packages/codemirror/src/extensions/eslint.spec.ts
Normal 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([]);
|
||||
});
|
||||
});
|
||||
46
packages/codemirror/src/extensions/eslint.ts
Normal file
46
packages/codemirror/src/extensions/eslint.ts
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user