diff --git a/packages/lg-solution-formatter/index.d.ts b/packages/lg-solution-formatter/index.d.ts index 9f4b348..780beec 100644 --- a/packages/lg-solution-formatter/index.d.ts +++ b/packages/lg-solution-formatter/index.d.ts @@ -1,4 +1,26 @@ export default formatSolution; - -export function formatSolution(sourceStr: string): Promise; +export type Config = { + clang?: { + enabled?: boolean; + config?: string; + }; +}; +/** + * @typedef {{ + * clang?: { + * enabled?: boolean, + * config?: string + * } + * }} Config + */ +/** + * @param {string} sourceStr + * @param {Config} config + */ +export function formatSolution(sourceStr: string, config?: Config): Promise; +/** + * clang-format is not supported. + * + * @param {string} sourceStr + */ export function formatSolutionSync(sourceStr: string): string; diff --git a/packages/lg-solution-formatter/index.js b/packages/lg-solution-formatter/index.js index ca7f96d..bb9f892 100644 --- a/packages/lg-solution-formatter/index.js +++ b/packages/lg-solution-formatter/index.js @@ -11,26 +11,48 @@ import remarkMath from "remark-math"; import remarkStringify from "remark-stringify"; import remarkLfmFmt from "@imkdown/remark-lfm-fmt"; import remarkGfm from "remark-gfm"; +import remarkClangFmtWasm from "@imkdown/remark-clang-fmt-wasm"; -const solFmtRemark = remark() - .use(remarkMath, { singleDollarTextMath: true }) - .use(remarkGfm) - .use(remarkLfmFmt) - .use(remarkStringify, { bullet: "-", rule: "-" }); +/** + * @typedef {{ + * clang?: { + * enabled?: boolean, + * config?: string + * } + * }} Config + */ /** * @param {string} sourceStr + * @param {Config} config */ -const formatSolution = async (sourceStr) => { - const file = await solFmtRemark.process(sourceStr); +const formatSolution = async (sourceStr, config = {}) => { + let rem = remark() + .use(remarkMath, { singleDollarTextMath: true }) + .use(remarkGfm) + .use(remarkLfmFmt) + .use(remarkStringify, { bullet: "-", rule: "-" }); + + if (config.clang?.enabled) { + rem = rem.use(remarkClangFmtWasm, config.clang.config); + } + + const file = await rem.process(sourceStr); return String(file); }; /** + * clang-format is not supported. + * * @param {string} sourceStr */ const formatSolutionSync = (sourceStr) => { - const file = solFmtRemark.processSync(sourceStr); + const file = remark() + .use(remarkMath, { singleDollarTextMath: true }) + .use(remarkGfm) + .use(remarkLfmFmt) + .use(remarkStringify, { bullet: "-", rule: "-" }) + .processSync(sourceStr); return String(file); }; diff --git a/packages/lg-solution-formatter/package.json b/packages/lg-solution-formatter/package.json index 74e4900..323512b 100644 --- a/packages/lg-solution-formatter/package.json +++ b/packages/lg-solution-formatter/package.json @@ -19,7 +19,8 @@ "remark": "^15.0.1", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", - "remark-stringify": "^11.0.0" + "remark-stringify": "^11.0.0", + "@imkdown/remark-clang-fmt-wasm": "workspace:^" }, "exports": { "default": "./index.js" diff --git a/packages/remark-clang-fmt-wasm/index.d.ts b/packages/remark-clang-fmt-wasm/index.d.ts new file mode 100644 index 0000000..2548730 --- /dev/null +++ b/packages/remark-clang-fmt-wasm/index.d.ts @@ -0,0 +1,13 @@ +declare module "@wasm-fmt/clang-format/clang-format-vite.js" { + export * from "@wasm-fmt/clang-format"; +} + +/** + * @param {string} config + */ +export default function remarkClangFmtWasm( + config?: string +): (tree: Root) => Promise; +export type Root = import("mdast").Root; +export type RootContent = import("mdast").RootContent; +export type VFile = import("vfile").VFile; diff --git a/packages/remark-clang-fmt-wasm/index.js b/packages/remark-clang-fmt-wasm/index.js new file mode 100644 index 0000000..941186b --- /dev/null +++ b/packages/remark-clang-fmt-wasm/index.js @@ -0,0 +1,35 @@ +/** + * @typedef {import('mdast').Root} Root + * @typedef {import('mdast').RootContent} RootContent + * @typedef {import('vfile').VFile} VFile + */ + + +// @ts-ignore +import init, { format } from "@wasm-fmt/clang-format/clang-format-vite.js"; +import { visit } from "unist-util-visit"; + +/** + * @param {string} config + */ +export default function remarkClangFmtWasm(config = "WebKit") { + /** + * The plugin. + * + * @param {Root} tree + * Tree. + * @returns + * Nothing. + */ + return async (tree) => { + await init(); + + visit(tree, "code", (node) => { + if (node.lang === "cpp" || node.lang === "c++") { + node.value = format(node.value, "main.cc", config); + } else if (node.lang === "c") { + node.value = format(node.value, "main.c", config); + } + }); + }; +} diff --git a/packages/remark-clang-fmt-wasm/package.json b/packages/remark-clang-fmt-wasm/package.json new file mode 100644 index 0000000..3a27c29 --- /dev/null +++ b/packages/remark-clang-fmt-wasm/package.json @@ -0,0 +1,26 @@ +{ + "name": "@imkdown/remark-clang-fmt-wasm", + "private": true, + "version": "1.0.0", + "type": "module", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "exports": { + "default": "./index.js" + }, + "types": "index.d.ts", + "dependencies": { + "@wasm-fmt/clang-format": "^18.1.8", + "unist-util-visit": "^5.0.0" + }, + "devDependencies": { + "@types/mdast": "^4.0.3", + "vfile": "^6.0.3" + } +} diff --git a/packages/remark-clang-fmt-wasm/tsconfig.json b/packages/remark-clang-fmt-wasm/tsconfig.json new file mode 100644 index 0000000..6b28998 --- /dev/null +++ b/packages/remark-clang-fmt-wasm/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["index.js"], +} diff --git a/packages/solfmt-web/package.json b/packages/solfmt-web/package.json index c2bdc6b..2cd2a73 100644 --- a/packages/solfmt-web/package.json +++ b/packages/solfmt-web/package.json @@ -17,6 +17,7 @@ "@codemirror/state": "^6.4.1", "@imkdown/lg-solution-formatter": "workspace:^", "codemirror": "^6.0.1", + "pinia": "^2.2.2", "vue": "^3.4.38", "vue-codemirror6": "^1.3.4" }, diff --git a/packages/solfmt-web/src/App.vue b/packages/solfmt-web/src/App.vue index 99a06ff..0c398c1 100644 --- a/packages/solfmt-web/src/App.vue +++ b/packages/solfmt-web/src/App.vue @@ -6,28 +6,45 @@ import { MergeView } from "@codemirror/merge"; import { markdown } from "@codemirror/lang-markdown"; import { languages } from "@codemirror/language-data"; -import formatSolution, { - formatSolutionSync, -} from "@imkdown/lg-solution-formatter"; +import { formatSolution } from "@imkdown/lg-solution-formatter"; import { ref, watch } from "vue"; import { EditorState } from "@codemirror/state"; +import { useStore } from "./store"; +import { storeToRefs } from "pinia"; +import Config from "./components/Config.vue"; let lastTimeout: number = 0; -const code = ref(""); +const store = useStore(); +const refStore = storeToRefs(store); + +store.$subscribe((_mutation, state) => { + localStorage.setItem("state", JSON.stringify(state)); +}); + +const code = refStore.code; const showFormatted = ref(false), + formatting = ref(false), showDone = ref(false); const mergedEditor = ref(null); -const formatRaw = () => { - code.value = formatSolutionSync(code.value); +const formatRaw = async () => { + formatting.value = true; + + code.value = await formatSolution(code.value, { + clang: { + enabled: refStore.clangEnabled.value, + config: refStore.clangConfig.value, + }, + }); showDone.value = true; clearTimeout(lastTimeout); lastTimeout = setTimeout(() => (showDone.value = false), 1000); + formatting.value = false; }; const formatAndCopy = () => { @@ -37,11 +54,23 @@ const formatAndCopy = () => { const COMMIT_HASH: string = import.meta.env.VITE_COMMIT_HASH; const MODE = import.meta.env.MODE; +const showConfig = ref(0); watch(showFormatted, async () => { if (!showFormatted.value) return; + formatting.value = true; while (!mergedEditor.value) await new Promise((resolve) => setTimeout(resolve, 20)); + + const solution = await formatSolution(code.value, { + clang: { + enabled: refStore.clangEnabled.value, + config: refStore.clangConfig.value, + }, + }); + + formatting.value = false; + new MergeView({ a: { doc: code.value, @@ -52,7 +81,7 @@ watch(showFormatted, async () => { ], }, b: { - doc: await formatSolution(code.value), + doc: solution, extensions: [ EditorState.readOnly.of(true), basicSetup, @@ -69,6 +98,10 @@ watch(showFormatted, async () => {

洛谷题解格式化工具

| 差异对比 + + | By + Imken +
@@ -87,15 +120,23 @@ watch(showFormatted, async () => {
@@ -103,9 +144,18 @@ watch(showFormatted, async () => { class="btn join-item btn-sm" v-on:click="formatRaw" v-if="!showFormatted" + :disabled="formatting" > 仅格式化 +
+ +