diff --git a/frontend/public/langdetect-worker.js b/frontend/public/langdetect-worker.js index c98a361..9fd4de1 100644 --- a/frontend/public/langdetect-worker.js +++ b/frontend/public/langdetect-worker.js @@ -1,7 +1,5 @@ importScripts("guesslang.min.js") -const LANGUAGES = ["json", "py", "html", "sql", "md", "java", "php", "css", "xml", "cpp", "rs", "cs", "rb", "sh", "yaml", "toml", "go", "clj", "ex", "erl", "js", "ts", "swift", "kt", "groovy", "ps1", "dart", "scala"] - const guessLang = new self.GuessLang() function sendResult(language, confidence, idx) { @@ -27,20 +25,13 @@ onmessage = (event) => { guessLang.runModel(content).then((result) => { if (result.length > 0) { - const lang = result[0] - if (LANGUAGES.includes(lang.languageId) && lang.confidence > 0.15) { - sendResult(lang.languageId, lang.confidence, idx) + // 返回置信度最高的结果 + const bestResult = result[0] + if (bestResult.confidence > 0.15) { + sendResult(bestResult.languageId, bestResult.confidence, idx) return } } - - for (let lang of result) { - if (LANGUAGES.includes(lang.languageId) && lang.confidence > 0.5) { - sendResult(lang.languageId, lang.confidence, idx) - return - } - } - sendResult("text", 0.0, idx) }).catch(() => { sendResult("text", 0.0, idx) diff --git a/frontend/src/common/prettier/plugins/dart/extra/.npmignore b/frontend/src/common/prettier/plugins/dart/extra/.npmignore new file mode 100644 index 0000000..826d6ab --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/extra/.npmignore @@ -0,0 +1,3 @@ +*.tgz +*.unopt.wasm +jsr.jsonc \ No newline at end of file diff --git a/frontend/src/common/prettier/plugins/dart/extra/dart_fmt.d.ts b/frontend/src/common/prettier/plugins/dart/extra/dart_fmt.d.ts new file mode 100644 index 0000000..5850092 --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/extra/dart_fmt.d.ts @@ -0,0 +1,32 @@ +export function format(input: string, filename: string, config?: LayoutConfig): string; + +interface LayoutConfig { + line_width?: number; + line_ending?: "lf" | "crlf"; + language_version?: string; +} + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export type InitOutput = unknown; + +// export type SyncInitInput = BufferSource | WebAssembly.Module; +// /** +// * Instantiates the given `module`, which can either be bytes or +// * a precompiled `WebAssembly.Module`. +// * +// * @param {SyncInitInput} module +// * +// * @returns {InitOutput} +// */ +// export function initSync(module: SyncInitInput): InitOutput; + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {InitInput | Promise} module_or_path +* +* @returns {Promise} +*/ +export default function init(module_or_path?: InitInput | Promise): Promise; diff --git a/frontend/src/common/prettier/plugins/dart/extra/dart_fmt.js b/frontend/src/common/prettier/plugins/dart/extra/dart_fmt.js new file mode 100644 index 0000000..57bf65b --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/extra/dart_fmt.js @@ -0,0 +1,84 @@ +import { format as dart_fmt, instantiate, invoke } from "./dart_fmt.mjs"; + +let wasm; + +function get_imports() {} +function init_memory() {} + +function normalize(module) { + if (!(module instanceof WebAssembly.Module)) { + return new WebAssembly.Module(module); + } + return module; +} + +export default async function (input) { + if (wasm !== undefined) return wasm; + + if (typeof input === "undefined") { + input = new URL("dart_fmt.wasm", import.meta.url); + } + const imports = get_imports(); + + if ( + typeof input === "string" || + (typeof Request === "function" && input instanceof Request) || + (typeof URL === "function" && input instanceof URL) + ) { + input = fetch(input); + } + + init_memory(imports); + + wasm = await load(await input) + .then(normalize) + .then(instantiate); + + invoke(wasm); + + return wasm; +} + +async function load(module) { + if (typeof Response === "function" && module instanceof Response) { + if ("compileStreaming" in WebAssembly) { + try { + return await WebAssembly.compileStreaming(module); + } catch (e) { + if (module.headers.get("Content-Type") != "application/wasm") { + console.warn( + "`WebAssembly.compileStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", + e, + ); + } else { + throw e; + } + } + } + + return module.arrayBuffer(); + } + + return module; +} + +export function format(source, filename = "stdin.dart", config = {}) { + const options = { lineEnding: "\n" }; + if (config.line_width) { + options.pageWidth = config.line_width; + } + if (options.line_ending === "crlf") { + options.lineEnding = "\r\n"; + } + if(options.language_version) { + options.languageVersion = options.language_version; + } + + const result = dart_fmt(source, filename, JSON.stringify(options)); + const err = result[0] === "x"; + const output = result.slice(1); + if (err) { + throw new Error(output); + } + return output; +} diff --git a/frontend/src/common/prettier/plugins/dart/extra/dart_fmt_node.js b/frontend/src/common/prettier/plugins/dart/extra/dart_fmt_node.js new file mode 100644 index 0000000..1d809ae --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/extra/dart_fmt_node.js @@ -0,0 +1,10 @@ +import fs from "node:fs/promises"; +import initAsync from "./dart_fmt.js"; + +const wasm = new URL("./dart_fmt.wasm", import.meta.url); + +export default function __wbg_init(init = fs.readFile(wasm)) { + return initAsync(init); +} + +export * from "./dart_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/dart/extra/dart_fmt_vite.js b/frontend/src/common/prettier/plugins/dart/extra/dart_fmt_vite.js new file mode 100644 index 0000000..070451c --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/extra/dart_fmt_vite.js @@ -0,0 +1,8 @@ +import initAsync from "./dart_fmt.js"; +import wasm from "./dart_fmt.wasm?url"; + +export default function __wbg_init(input = wasm) { + return initAsync(input); +} + +export * from "./dart_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/dart/lib/binding.dart b/frontend/src/common/prettier/plugins/dart/lib/binding.dart new file mode 100644 index 0000000..840464f --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/lib/binding.dart @@ -0,0 +1,31 @@ +import 'dart:convert'; +import 'dart:js_interop'; +import 'package:dart_fmt/dart_fmt.dart'; + +class Options { + final int? pageWidth; + final String? lineEnding; + final String? languageVersion; + + Options.fromJson(Map json) + : pageWidth = json['pageWidth'] as int?, + lineEnding = json['lineEnding'] as String?, + languageVersion = json['languageVersion'] as String?; +} + +String formatWrapper(String source, String filename, String options) { + final config = Options.fromJson(jsonDecode(options)); + + try { + return "o${format(source, filename, pageWidth: config.pageWidth, lineEnding: config.lineEnding, version: config.languageVersion)}"; + } catch (e) { + return "x$e"; + } +} + +@JS('format') +external set formatExport(JSFunction handler); + +void main(List arguments) { + formatExport = formatWrapper.toJS; +} diff --git a/frontend/src/common/prettier/plugins/dart/lib/dart_fmt.dart b/frontend/src/common/prettier/plugins/dart/lib/dart_fmt.dart new file mode 100644 index 0000000..a338df8 --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/lib/dart_fmt.dart @@ -0,0 +1,14 @@ +import 'package:dart_style/dart_style.dart'; +import 'package:pub_semver/pub_semver.dart'; + +String format(String source, String filename, + {int? pageWidth, String? lineEnding, String? version}) { + final languageVersion = version != null + ? Version.parse(version) + : DartFormatter.latestLanguageVersion; + final formatter = DartFormatter( + pageWidth: pageWidth, + lineEnding: lineEnding, + languageVersion: languageVersion); + return formatter.format(source, uri: filename); +} diff --git a/frontend/src/common/prettier/plugins/dart/scripts/build.sh b/frontend/src/common/prettier/plugins/dart/scripts/build.sh new file mode 100644 index 0000000..86a8ce9 --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/scripts/build.sh @@ -0,0 +1,7 @@ +cd $(dirname $0)/.. + +dart compile wasm ./lib/binding.dart -o ./build/dart_fmt.wasm +cp -LR ./extra/. ./build/ + +./scripts/patch.mjs ./build/dart_fmt.mjs +./scripts/package.mjs ./package.json diff --git a/frontend/src/common/prettier/plugins/dart/scripts/package.mjs b/frontend/src/common/prettier/plugins/dart/scripts/package.mjs new file mode 100644 index 0000000..210f62d --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/scripts/package.mjs @@ -0,0 +1,16 @@ +#!/usr/bin/env node +import process from "node:process"; +import path from "node:path"; +import fs from "node:fs"; + +const pkg_path = path.resolve(process.cwd(), process.argv[2]); +const pkg_text = fs.readFileSync(pkg_path, { encoding: "utf-8" }); +const pkg_json = JSON.parse(pkg_text); + +// JSR + +const jsr_path = path.resolve(pkg_path, "..", "build", "jsr.jsonc"); +pkg_json.name = "@fmt/dart-fmt"; +pkg_json.exports = "./dart_fmt.js"; +pkg_json.exclude = ["!../build", "*.tgz", ".npmignore"]; +fs.writeFileSync(jsr_path, JSON.stringify(pkg_json, null, 4)); diff --git a/frontend/src/common/prettier/plugins/dart/scripts/patch.mjs b/frontend/src/common/prettier/plugins/dart/scripts/patch.mjs new file mode 100644 index 0000000..39c50a2 --- /dev/null +++ b/frontend/src/common/prettier/plugins/dart/scripts/patch.mjs @@ -0,0 +1,13 @@ +#!/usr/bin/env node +import process from "node:process"; +import path from "node:path"; +import fs from "node:fs"; + +const file_path = path.resolve(process.cwd(), process.argv[2]); +let file_text = fs.readFileSync(file_path, { encoding: "utf-8" }); + +file_text = file_text.replace(`"length": (s) => s.length`, '"length": (s) => s?.length||0'); +file_text = file_text.replace('globalThis.format', 'format'); +file_text += "\nexport let format;"; + +fs.writeFileSync(file_path, file_text); \ No newline at end of file diff --git a/frontend/src/common/prettier/plugins/lua/Cargo.toml b/frontend/src/common/prettier/plugins/lua/Cargo.toml new file mode 100644 index 0000000..7450537 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "lua_fmt" + +authors.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + + [package.metadata.wasm-pack.profile.release] + wasm-opt = ["-Os"] + + +[dependencies] +serde = { workspace = true, features = ["derive"] } +serde-wasm-bindgen = { workspace = true } +serde_json = { workspace = true, features = ["preserve_order"] } +stylua = { workspace = true, features = ["lua52", "lua53", "lua54"] } +wasm-bindgen = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/frontend/src/common/prettier/plugins/lua/extra/.npmignore b/frontend/src/common/prettier/plugins/lua/extra/.npmignore new file mode 100644 index 0000000..1ee7305 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/extra/.npmignore @@ -0,0 +1,2 @@ +*.tgz +jsr.jsonc \ No newline at end of file diff --git a/frontend/src/common/prettier/plugins/lua/extra/lua_fmt_node.js b/frontend/src/common/prettier/plugins/lua/extra/lua_fmt_node.js new file mode 100644 index 0000000..8828ec5 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/extra/lua_fmt_node.js @@ -0,0 +1,10 @@ +import fs from "node:fs/promises"; +import initAsync from "./lua_fmt.js"; + +const wasm = new URL("./lua_fmt_bg.wasm", import.meta.url); + +export default function __wbg_init(init = { module_or_path: fs.readFile(wasm) }) { + return initAsync(init); +} + +export * from "./lua_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/lua/extra/lua_fmt_vite.js b/frontend/src/common/prettier/plugins/lua/extra/lua_fmt_vite.js new file mode 100644 index 0000000..770a144 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/extra/lua_fmt_vite.js @@ -0,0 +1,8 @@ +import initAsync from "./lua_fmt.js"; +import wasm from "./lua_fmt_bg.wasm?url"; + +export default function __wbg_init(input = { module_or_path: wasm }) { + return initAsync(input); +} + +export * from "./lua_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/lua/index.ts b/frontend/src/common/prettier/plugins/lua/index.ts new file mode 100644 index 0000000..cb443f6 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/index.ts @@ -0,0 +1,178 @@ +/** + * Prettier Plugin for Lua formatting using StyLua WebAssembly + * + * This plugin provides support for formatting Lua files using the StyLua WASM implementation. + * StyLua is a fast Lua code formatter written in Rust. + */ +import type { Plugin, Parser, Printer } from 'prettier'; + +// Import the StyLua WASM module +import luaInit, { format, type Config } from './lua_fmt_vite.js'; + +const parserName = 'lua'; + +// Language configuration +const languages = [ + { + name: 'Lua', + aliases: ['lua'], + parsers: [parserName], + extensions: ['.lua'], + aceMode: 'lua', + tmScope: 'source.lua', + linguistLanguageId: 213, + vscodeLanguageIds: ['lua'] + } +]; + +// Parser configuration +const luaParser: Parser = { + astFormat: parserName, + parse: (text: string) => text, + locStart: () => 0, + locEnd: (node: string) => node.length, +}; + +// Initialize StyLua WASM module +let initPromise: Promise | null = null; +let isInitialized = false; + +function initStyLua(): Promise { + if (initPromise) { + return initPromise; + } + + initPromise = (async () => { + if (!isInitialized) { + await luaInit(); + isInitialized = true; + } + })(); + + return initPromise; +} + +// Printer configuration +const luaPrinter: Printer = { + print: (path, options) => { + try { + if (!isInitialized) { + console.warn('StyLua WASM module not initialized, returning original text'); + return (path as any).getValue ? (path as any).getValue() : path.node; + } + + const text = (path as any).getValue ? (path as any).getValue() : path.node; + const config = getStyLuaConfig(options); + + // Format using StyLua (synchronous call) + const formatted = format(text, "input.lua", config); + + return formatted.trim(); + } catch (error) { + console.warn('StyLua formatting failed:', error); + // Return original text if formatting fails + return (path as any).getValue ? (path as any).getValue() : path.node; + } + }, +}; + +// Helper function to create StyLua config from Prettier options +function getStyLuaConfig(options: any): Config { + const config: Config = {}; + + // Map Prettier options to StyLua config + if (options.useTabs !== undefined) { + config.indent_style = options.useTabs ? 'tab' : 'space'; + } + + if (options.tabWidth !== undefined) { + config.indent_width = options.tabWidth; + } + + if (options.printWidth !== undefined) { + config.line_width = options.printWidth; + } + + if (options.endOfLine !== undefined) { + config.line_ending = options.endOfLine === 'crlf' ? 'crlf' : 'lf'; + } + + if (options.singleQuote !== undefined) { + config.quote_style = options.singleQuote ? 'AutoPreferSingle' : 'AutoPreferDouble'; + } + + // StyLua specific options + if (options.luaCallParentheses !== undefined) { + config.call_parentheses = options.luaCallParentheses; + } + + if (options.luaCollapseSimpleStatement !== undefined) { + config.collapse_simple_statement = options.luaCollapseSimpleStatement; + } + + if (options.luaSortRequires !== undefined) { + config.sort_requires = options.luaSortRequires; + } + + return config; +} + +// Plugin options +const options = { + luaCallParentheses: { + since: '0.0.1', + category: 'Format' as const, + type: 'choice' as const, + default: 'Always', + description: 'How to handle function call parentheses', + choices: [ + { value: 'Always', description: 'Always use parentheses' }, + { value: 'NoSingleString', description: 'Remove parentheses for single string argument' }, + { value: 'NoSingleTable', description: 'Remove parentheses for single table argument' }, + { value: 'None', description: 'Remove parentheses when possible' }, + { value: 'Input', description: 'Keep input formatting' } + ] + }, + luaCollapseSimpleStatement: { + since: '0.0.1', + category: 'Format' as const, + type: 'choice' as const, + default: 'Never', + description: 'How to handle simple statement collapsing', + choices: [ + { value: 'Never', description: 'Never collapse simple statements' }, + { value: 'FunctionOnly', description: 'Collapse function statements only' }, + { value: 'ConditionalOnly', description: 'Collapse conditional statements only' }, + { value: 'Always', description: 'Always collapse simple statements' } + ] + }, + luaSortRequires: { + since: '0.0.1', + category: 'Format' as const, + type: 'boolean' as const, + default: false, + description: 'Sort require statements alphabetically' + } +}; + +// Plugin object +const luaPlugin: Plugin = { + languages, + parsers: { + [parserName]: luaParser, + }, + printers: { + [parserName]: luaPrinter, + }, + options, +}; + +// Initialize WASM module when plugin loads +initStyLua().catch(error => { + console.warn('Failed to initialize StyLua WASM module:', error); +}); + +export default luaPlugin; +export { languages }; +export const parsers = luaPlugin.parsers; +export const printers = luaPlugin.printers; diff --git a/frontend/src/common/prettier/plugins/lua/lua_fmt.d.ts b/frontend/src/common/prettier/plugins/lua/lua_fmt.d.ts new file mode 100644 index 0000000..714a3cc --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/lua_fmt.d.ts @@ -0,0 +1,52 @@ +/* tslint:disable */ +/* eslint-disable */ +export function format(input: string, filename: string, config?: Config): string; + +interface LayoutConfig { + indent_style?: "tab" | "space"; + indent_width?: number; + line_width?: number; + line_ending?: "lf" | "crlf"; +} + + +export interface Config extends LayoutConfig { + quote_style?: "AutoPreferDouble" | "AutoPreferSingle" | "ForceDouble" | "ForceSingle"; + call_parentheses?: "Always" | "NoSingleString" | "NoSingleTable" | "None" | "Input"; + collapse_simple_statement?: | "Never" | "FunctionOnly" | "ConditionalOnly" | "Always"; + sort_requires?: boolean; +} + + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly format: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly __wbindgen_export_0: (a: number, b: number) => number; + readonly __wbindgen_export_1: (a: number, b: number, c: number, d: number) => number; + readonly __wbindgen_export_2: (a: number) => void; + readonly __wbindgen_add_to_stack_pointer: (a: number) => number; + readonly __wbindgen_export_3: (a: number, b: number, c: number) => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; +/** +* Instantiates the given `module`, which can either be bytes or +* a precompiled `WebAssembly.Module`. +* +* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. +* +* @returns {InitOutput} +*/ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. +* +* @returns {Promise} +*/ +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/frontend/src/common/prettier/plugins/lua/lua_fmt.js b/frontend/src/common/prettier/plugins/lua/lua_fmt.js new file mode 100644 index 0000000..6b26349 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/lua_fmt.js @@ -0,0 +1,524 @@ +let wasm; + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let WASM_VECTOR_LEN = 0; + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_export_2(addHeapObject(e)); + } +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} +/** + * @param {string} input + * @param {string} filename + * @param {Config | undefined} [config] + * @returns {string} + */ +export function format(input, filename, config) { + let deferred4_0; + let deferred4_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(input, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(filename, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + wasm.format(retptr, ptr0, len0, ptr1, len1, isLikeNone(config) ? 0 : addHeapObject(config)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr3 = r0; + var len3 = r1; + if (r3) { + ptr3 = 0; len3 = 0; + throw takeObject(r2); + } + deferred4_0 = ptr3; + deferred4_1 = len3; + return getStringFromWasm0(ptr3, len3); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred4_0, deferred4_1, 1); + } +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_String_8f0eb39a4a4c2f66 = function(arg0, arg1) { + const ret = String(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_buffer_71667b1101df19da = function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_call_d68488931693e6ee = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_done_3ca5b09e8598078d = function(arg0) { + const ret = getObject(arg0).done; + return ret; + }; + imports.wbg.__wbg_entries_d873dde863e50b8c = function(arg0) { + const ret = Object.entries(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_c122b1d576cf1fdb = function(arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_ddd82e34e6366fb9 = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_instanceof_ArrayBuffer_36214dbc6ea8dd3d = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_Map_52afb7722e323e8b = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Map; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_Uint8Array_0d898f7981fe0a2d = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_isArray_435f9cb9abc7eccc = function(arg0) { + const ret = Array.isArray(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_isSafeInteger_2817b2c8ebdd29d2 = function(arg0) { + const ret = Number.isSafeInteger(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_iterator_2a6b115668862130 = function() { + const ret = Symbol.iterator; + return addHeapObject(ret); + }; + imports.wbg.__wbg_length_b52c3d528b88468e = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_length_e9123d1e4db12534 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_new_9ed4506807911440 = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_next_86c8f7dfb19a94eb = function() { return handleError(function (arg0) { + const ret = getObject(arg0).next(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_next_b39104aeda52ac60 = function(arg0) { + const ret = getObject(arg0).next; + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_e8d9380e866a1e41 = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbg_value_f82ca5432417c8ff = function(arg0) { + const ret = getObject(arg0).value; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_bigint_from_i64 = function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_bigint_from_u64 = function(arg0) { + const ret = BigInt.asUintN(64, arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_bigint_get_as_i64 = function(arg0, arg1) { + const v = getObject(arg1); + const ret = typeof(v) === 'bigint' ? v : undefined; + getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }; + imports.wbg.__wbindgen_boolean_get = function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_error_new = function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_in = function(arg0, arg1) { + const ret = getObject(arg0) in getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_is_bigint = function(arg0) { + const ret = typeof(getObject(arg0)) === 'bigint'; + return ret; + }; + imports.wbg.__wbindgen_is_function = function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }; + imports.wbg.__wbindgen_is_object = function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; + }; + imports.wbg.__wbindgen_is_string = function(arg0) { + const ret = typeof(getObject(arg0)) === 'string'; + return ret; + }; + imports.wbg.__wbindgen_jsval_eq = function(arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_memory = function() { + const ret = wasm.memory; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_number_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'number' ? obj : undefined; + getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + + return imports; +} + +function __wbg_init_memory(imports, memory) { + +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedDataViewMemory0 = null; + cachedUint8ArrayMemory0 = null; + + + + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined') { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined') { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (typeof module_or_path === 'undefined') { + module_or_path = new URL('lua_fmt_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync }; +export default __wbg_init; diff --git a/frontend/src/common/prettier/plugins/lua/lua_fmt_bg.wasm b/frontend/src/common/prettier/plugins/lua/lua_fmt_bg.wasm new file mode 100644 index 0000000..012de2e Binary files /dev/null and b/frontend/src/common/prettier/plugins/lua/lua_fmt_bg.wasm differ diff --git a/frontend/src/common/prettier/plugins/lua/lua_fmt_bg.wasm.d.ts b/frontend/src/common/prettier/plugins/lua/lua_fmt_bg.wasm.d.ts new file mode 100644 index 0000000..ecdf7c3 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/lua_fmt_bg.wasm.d.ts @@ -0,0 +1,9 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void; +export const __wbindgen_export_0: (a: number, b: number) => number; +export const __wbindgen_export_1: (a: number, b: number, c: number, d: number) => number; +export const __wbindgen_export_2: (a: number) => void; +export const __wbindgen_add_to_stack_pointer: (a: number) => number; +export const __wbindgen_export_3: (a: number, b: number, c: number) => void; diff --git a/frontend/src/common/prettier/plugins/lua/lua_fmt_node.js b/frontend/src/common/prettier/plugins/lua/lua_fmt_node.js new file mode 100644 index 0000000..8828ec5 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/lua_fmt_node.js @@ -0,0 +1,10 @@ +import fs from "node:fs/promises"; +import initAsync from "./lua_fmt.js"; + +const wasm = new URL("./lua_fmt_bg.wasm", import.meta.url); + +export default function __wbg_init(init = { module_or_path: fs.readFile(wasm) }) { + return initAsync(init); +} + +export * from "./lua_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/lua/lua_fmt_vite.js b/frontend/src/common/prettier/plugins/lua/lua_fmt_vite.js new file mode 100644 index 0000000..770a144 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/lua_fmt_vite.js @@ -0,0 +1,8 @@ +import initAsync from "./lua_fmt.js"; +import wasm from "./lua_fmt_bg.wasm?url"; + +export default function __wbg_init(input = { module_or_path: wasm }) { + return initAsync(input); +} + +export * from "./lua_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/lua/scripts/build.sh b/frontend/src/common/prettier/plugins/lua/scripts/build.sh new file mode 100644 index 0000000..de4317b --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/scripts/build.sh @@ -0,0 +1,11 @@ +cd $(dirname $0)/.. +crates_dir=$(pwd) + +cd ../.. +wasm-pack build --target=web --scope=wasm-fmt crates/lua_fmt + +cd $crates_dir + +cp -R ./extra/. ./pkg/ + +./scripts/package.mjs ./pkg/package.json diff --git a/frontend/src/common/prettier/plugins/lua/scripts/package.mjs b/frontend/src/common/prettier/plugins/lua/scripts/package.mjs new file mode 100644 index 0000000..d1452f8 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/scripts/package.mjs @@ -0,0 +1,39 @@ +#!/usr/bin/env node +import process from "node:process"; +import path from "node:path"; +import fs from "node:fs"; + +const pkg_path = path.resolve(process.cwd(), process.argv[2]); +const pkg_text = fs.readFileSync(pkg_path, { encoding: "utf-8" }); +const pkg_json = JSON.parse(pkg_text); + +delete pkg_json.files; + +pkg_json.main = pkg_json.module; +pkg_json.type = "module"; +pkg_json.publishConfig = { + access: "public", +}; +pkg_json.exports = { + ".": { + types: "./lua_fmt.d.ts", + node: "./lua_fmt_node.js", + default: "./lua_fmt.js", + }, + "./vite": { + types: "./lua_fmt.d.ts", + default: "./lua_fmt_vite.js", + }, + "./package.json": "./package.json", + "./*": "./*", +}; + +fs.writeFileSync(pkg_path, JSON.stringify(pkg_json, null, 4)); + +// JSR + +const jsr_path = path.resolve(pkg_path, "..", "jsr.jsonc"); +pkg_json.name = "@fmt/lua-fmt"; +pkg_json.exports = "./lua_fmt.js"; +pkg_json.exclude = ["!**", "*.tgz"]; +fs.writeFileSync(jsr_path, JSON.stringify(pkg_json, null, 4)); diff --git a/frontend/src/common/prettier/plugins/lua/src/lib.rs b/frontend/src/common/prettier/plugins/lua/src/lib.rs new file mode 100644 index 0000000..dab4fc4 --- /dev/null +++ b/frontend/src/common/prettier/plugins/lua/src/lib.rs @@ -0,0 +1,151 @@ +use serde::Deserialize; +use stylua_lib::{ + format_code, CallParenType, CollapseSimpleStatement, Config as StyluaConfig, IndentType, + LineEndings, OutputVerification, QuoteStyle, SortRequiresConfig, +}; +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen] +pub fn format(input: &str, filename: &str, config: Option) -> Result { + let _ = filename; + + let config = config + .map(|x| serde_wasm_bindgen::from_value::(x.clone())) + .transpose() + .map_err(|op| op.to_string())? + .unwrap_or_default(); + + format_code(input, config.into(), None, OutputVerification::None).map_err(|e| e.to_string()) +} + +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#" +interface LayoutConfig { + indent_style?: "tab" | "space"; + indent_width?: number; + line_width?: number; + line_ending?: "lf" | "crlf"; +}"#; + +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#" +export interface Config extends LayoutConfig { + quote_style?: "AutoPreferDouble" | "AutoPreferSingle" | "ForceDouble" | "ForceSingle"; + call_parentheses?: "Always" | "NoSingleString" | "NoSingleTable" | "None" | "Input"; + collapse_simple_statement?: | "Never" | "FunctionOnly" | "ConditionalOnly" | "Always"; + sort_requires?: boolean; +}"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Config")] + pub type Config; +} + +#[derive(Deserialize, Clone, Default)] +struct LayoutConfig { + #[serde(alias = "indentStyle")] + indent_style: Option, + #[serde(alias = "indentWidth")] + indent_width: Option, + #[serde(alias = "lineWidth")] + line_width: Option, + #[serde(alias = "lineEnding")] + line_ending: Option, +} + +#[derive(Deserialize, Clone, Default)] +struct LuaConfig { + #[serde(flatten)] + layout: LayoutConfig, + + #[serde(alias = "quoteStyle")] + quote_style: Option, + + #[serde(alias = "callParentheses")] + call_parentheses: Option, + + #[serde(alias = "collapseSimpleStatement")] + collapse_simple_statement: Option, + + #[serde(alias = "sortRequires")] + sort_requires: Option, +} + +impl From for StyluaConfig { + fn from(val: LuaConfig) -> Self { + let mut config = StyluaConfig::default(); + + if let Some(indent_style) = val.layout.indent_style { + config.indent_type = indent_style.into(); + } + + if let Some(indent_width) = val.layout.indent_width { + config.indent_width = indent_width as usize; + } + + if let Some(line_width) = val.layout.line_width { + config.column_width = line_width as usize; + } + + if let Some(line_ending) = val.layout.line_ending { + config.line_endings = line_ending.into(); + } + + if let Some(quote_style) = val.quote_style { + config.quote_style = quote_style; + } + + if let Some(call_parentheses) = val.call_parentheses { + config.call_parentheses = call_parentheses; + } + + if let Some(collapse_simple_statement) = val.collapse_simple_statement { + config.collapse_simple_statement = collapse_simple_statement; + } + + if let Some(enabled) = val.sort_requires { + let mut sort_requires = SortRequiresConfig::default(); + sort_requires.enabled = enabled; + config.sort_requires = sort_requires; + } + + config + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +#[derive(Clone, Copy, Default)] +enum IndentStyle { + Tab, + #[default] + Space, +} + +impl From for IndentType { + fn from(val: IndentStyle) -> Self { + match val { + IndentStyle::Tab => Self::Tabs, + IndentStyle::Space => Self::Spaces, + } + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +#[derive(Clone, Copy, Default)] +enum LineEnding { + #[default] + Lf, + Crlf, +} + +impl From for LineEndings { + fn from(val: LineEnding) -> Self { + match val { + LineEnding::Lf => Self::Unix, + LineEnding::Crlf => Self::Windows, + } + } +} diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt/Cargo.toml b/frontend/src/common/prettier/plugins/python/ruff_fmt/Cargo.toml new file mode 100644 index 0000000..19835c6 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "ruff_fmt" + +authors.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + + [package.metadata.wasm-pack.profile.release] + wasm-opt = ["-Os"] + + +[dependencies] +ruff_fmt_config = { workspace = true } +ruff_formatter = { workspace = true } +ruff_python_formatter = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde-wasm-bindgen = { workspace = true } +serde_json = { workspace = true, features = ["preserve_order"] } +wasm-bindgen = { workspace = true } + +[dev-dependencies] +testing_macros = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt/extra/.npmignore b/frontend/src/common/prettier/plugins/python/ruff_fmt/extra/.npmignore new file mode 100644 index 0000000..a93ee7a --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt/extra/.npmignore @@ -0,0 +1,2 @@ +*.tgz +jsr.jsonc diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt/extra/ruff_fmt_node.js b/frontend/src/common/prettier/plugins/python/ruff_fmt/extra/ruff_fmt_node.js new file mode 100644 index 0000000..b4ec4fb --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt/extra/ruff_fmt_node.js @@ -0,0 +1,10 @@ +import fs from "node:fs/promises"; +import initAsync from "./ruff_fmt.js"; + +const wasm = new URL("./ruff_fmt_bg.wasm", import.meta.url); + +export default function __wbg_init(init = { module_or_path: fs.readFile(wasm) }) { + return initAsync(init); +} + +export * from "./ruff_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt/extra/ruff_fmt_vite.js b/frontend/src/common/prettier/plugins/python/ruff_fmt/extra/ruff_fmt_vite.js new file mode 100644 index 0000000..0a425c6 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt/extra/ruff_fmt_vite.js @@ -0,0 +1,8 @@ +import initAsync from "./ruff_fmt.js"; +import wasm from "./ruff_fmt_bg.wasm?url"; + +export default function __wbg_init(input = { module_or_path: wasm }) { + return initAsync(input); +} + +export * from "./ruff_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt/scripts/build.sh b/frontend/src/common/prettier/plugins/python/ruff_fmt/scripts/build.sh new file mode 100644 index 0000000..2ecfb7d --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt/scripts/build.sh @@ -0,0 +1,12 @@ +cd $(dirname $0)/.. +crates_dir=$(pwd) + +cd ../.. +wasm-pack build --target=web --scope=wasm-fmt crates/ruff_fmt +cp README.md crates/ruff_fmt/pkg/ + +cd $crates_dir + +cp -R ./extra/. ./pkg/ + +./scripts/package.mjs ./pkg/package.json diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt/scripts/package.mjs b/frontend/src/common/prettier/plugins/python/ruff_fmt/scripts/package.mjs new file mode 100644 index 0000000..bf850bd --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt/scripts/package.mjs @@ -0,0 +1,39 @@ +#!/usr/bin/env node +import process from "node:process"; +import path from "node:path"; +import fs from "node:fs"; + +const pkg_path = path.resolve(process.cwd(), process.argv[2]); +const pkg_text = fs.readFileSync(pkg_path, { encoding: "utf-8" }); +const pkg_json = JSON.parse(pkg_text); + +delete pkg_json.files; + +pkg_json.main = pkg_json.module; +pkg_json.type = "module"; +pkg_json.publishConfig = { + access: "public", +}; +pkg_json.exports = { + ".": { + types: "./ruff_fmt.d.ts", + node: "./ruff_fmt_node.js", + default: "./ruff_fmt.js", + }, + "./vite": { + types: "./ruff_fmt.d.ts", + default: "./ruff_fmt_vite.js", + }, + "./package.json": "./package.json", + "./*": "./*", +}; + +fs.writeFileSync(pkg_path, JSON.stringify(pkg_json, null, 4)); + +// JSR + +const jsr_path = path.resolve(pkg_path, "..", "jsr.jsonc"); +pkg_json.name = "@fmt/ruff-fmt"; +pkg_json.exports = "./ruff_fmt.js"; +pkg_json.exclude = ["!**", "*.tgz"]; +fs.writeFileSync(jsr_path, JSON.stringify(pkg_json, null, 4)); diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt/src/lib.rs b/frontend/src/common/prettier/plugins/python/ruff_fmt/src/lib.rs new file mode 100644 index 0000000..557dfe2 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt/src/lib.rs @@ -0,0 +1,41 @@ +#[cfg(test)] +mod test; + +use ruff_fmt_config::Config as InnerConfig; +use ruff_python_formatter::format_module_source; + +#[wasm_bindgen] +pub fn format(input: &str, path: Option, config: Option) -> Result { + let mut config: InnerConfig = if let Some(config) = config { + serde_wasm_bindgen::from_value(config.clone()).map_err(|e| e.to_string())? + } else { + Default::default() + }; + + if let Some(path) = path { + config = config.with_path(path); + } + + format_module_source(input, config.into()) + .map(|result| result.into_code()) + .map_err(|err| err.to_string()) +} + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#" +export interface Config { + indent_style?: "tab" | "space"; + indent_width?: number; + line_width?: number; + line_ending?: "lf" | "crlf"; + quote_style?: "single" | "double"; + magic_trailing_comma?: "respect" | "ignore"; +}"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Config")] + pub type Config; +} diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt/src/test.rs b/frontend/src/common/prettier/plugins/python/ruff_fmt/src/test.rs new file mode 100644 index 0000000..aa4f058 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt/src/test.rs @@ -0,0 +1,26 @@ +#[cfg(test)] +mod tests { + use std::{fs::File, io::Read, path::PathBuf, str::FromStr}; + use testing_macros::fixture; + + use crate::format; + + #[fixture("test_data/**/*.py")] + #[fixture("test_data/**/*.pyi")] + fn it_works(input: PathBuf) { + // calc the expected file path + let input = input.clone(); + let extect_path = input.to_string_lossy() + ".expect"; + let extect_path = PathBuf::from_str(&extect_path).unwrap(); + + let mut actual = String::new(); + File::open(&input).and_then(|mut file| file.read_to_string(&mut actual)).unwrap(); + + let mut expect = String::new(); + File::open(extect_path).and_then(|mut file| file.read_to_string(&mut expect)).unwrap(); + + let actual = format(&actual, Some(input.to_string_lossy().to_string()), None).unwrap(); + + assert_eq!(actual, expect); + } +} diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt_config/Cargo.toml b/frontend/src/common/prettier/plugins/python/ruff_fmt_config/Cargo.toml new file mode 100644 index 0000000..8991e30 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt_config/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors.workspace = true +description = "Config for ruff_fmt" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "ruff_fmt_config" +repository.workspace = true +version.workspace = true + + +[dependencies] +ruff_formatter = { workspace = true } +ruff_python_formatter = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, features = ["preserve_order"] } diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt_config/src/lib.rs b/frontend/src/common/prettier/plugins/python/ruff_fmt_config/src/lib.rs new file mode 100644 index 0000000..d4a4458 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt_config/src/lib.rs @@ -0,0 +1,163 @@ +use std::{ + num::{NonZeroU16, NonZeroU8}, + path::Path, + str::FromStr, +}; + +use ruff_formatter::{printer::LineEnding as RuffLineEnding, IndentStyle as RuffIndentStyle}; +use ruff_python_formatter::{MagicTrailingComma, PyFormatOptions, QuoteStyle}; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum IndentStyle { + Tab, + Space, +} + +impl FromStr for IndentStyle { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "tab" => Ok(Self::Tab), + "space" => Ok(Self::Space), + _ => Err("Value not supported for IndentStyle"), + } + } +} + +impl From for IndentStyle { + fn from(value: RuffIndentStyle) -> Self { + match value { + RuffIndentStyle::Tab => Self::Tab, + RuffIndentStyle::Space => Self::Space, + } + } +} + +impl From for RuffIndentStyle { + fn from(value: IndentStyle) -> Self { + match value { + IndentStyle::Tab => Self::Tab, + IndentStyle::Space => Self::Space, + } + } +} + +#[derive(Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum LineEnding { + #[default] + Lf, + CrLf, +} + +impl From for RuffLineEnding { + fn from(value: LineEnding) -> Self { + match value { + LineEnding::Lf => Self::LineFeed, + LineEnding::CrLf => Self::CarriageReturnLineFeed, + } + } +} + +impl FromStr for LineEnding { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "lf" => Ok(Self::Lf), + "crlf" => Ok(Self::CrLf), + _ => Err("Value not supported for LineEnding"), + } + } +} + +#[derive(Default, Clone, Deserialize, Serialize)] +pub struct Config { + #[serde(alias = "indentStyle")] + pub indent_style: Option, + #[serde(alias = "indentWidth")] + pub indent_width: Option, + #[serde(alias = "lineWidth")] + pub line_width: Option, + #[serde(alias = "lineEnding")] + pub line_ending: Option, + + pub quote_style: Option, + pub magic_trailing_comma: Option, + + #[serde(skip)] + path: String, +} + +impl Config { + pub fn with_indent_style(mut self, indent_style: IndentStyle) -> Self { + self.indent_style = Some(indent_style); + self + } + + pub fn with_indent_width(mut self, indent_width: NonZeroU8) -> Self { + self.indent_width = Some(indent_width); + self + } + + pub fn with_line_width(mut self, line_width: NonZeroU16) -> Self { + self.line_width = Some(line_width); + self + } + + pub fn with_line_ending(mut self, line_ending: LineEnding) -> Self { + self.line_ending = Some(line_ending); + self + } + + pub fn with_quote_style(mut self, quote_style: QuoteStyle) -> Self { + self.quote_style = Some(quote_style); + self + } + + pub fn with_magic_trailing_comma(mut self, magic_trailing_comma: MagicTrailingComma) -> Self { + self.magic_trailing_comma = Some(magic_trailing_comma); + self + } + + pub fn with_path(mut self, path: String) -> Self { + self.path = path; + self + } +} + +impl From for PyFormatOptions { + fn from(value: Config) -> Self { + let mut config = Self::from_extension(Path::new(&value.path)); + + if let Some(indent_style) = value.indent_style { + config = config.with_indent_style(indent_style.into()); + } + + if let Some(indent_width) = value.indent_width { + config = config.with_indent_width(indent_width.into()); + } + + if let Some(line_width) = value.line_width { + config = config.with_line_width(line_width.into()); + } + + if let Some(line_ending) = value.line_ending { + config = config.with_line_ending(line_ending.into()); + } + + if let Some(quote_style) = value.quote_style { + config = config.with_quote_style(quote_style); + } + + if let Some(magic_trailing_comma) = value.magic_trailing_comma { + config = config.with_magic_trailing_comma(magic_trailing_comma); + } + + config + } +} diff --git a/frontend/src/common/prettier/plugins/python/unicode_names2_patch/Cargo.toml b/frontend/src/common/prettier/plugins/python/unicode_names2_patch/Cargo.toml new file mode 100644 index 0000000..a7241f2 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/unicode_names2_patch/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "unicode_names2" +version = "1.2.2" + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + + + [package.metadata.wasm-pack.profile.release] + wasm-opt = ["-Os"] + + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/frontend/src/common/prettier/plugins/python/unicode_names2_patch/src/lib.rs b/frontend/src/common/prettier/plugins/python/unicode_names2_patch/src/lib.rs new file mode 100644 index 0000000..e01f41a --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/unicode_names2_patch/src/lib.rs @@ -0,0 +1,6 @@ +/// This is not correct, but the formatter does not change the output of Unicode. +/// Additionally, we do not require users to input the correct name. +/// However, this optimization reduces our wasm file size by a substantial 700kb, which is a significant benefit. +pub fn character(_name: &str) -> Option { + Some('a') +} diff --git a/frontend/src/common/prettier/plugins/web/common/Cargo.toml b/frontend/src/common/prettier/plugins/web/common/Cargo.toml new file mode 100644 index 0000000..237ba20 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/common/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "common" + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + + +[dependencies] +biome_formatter = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"], optional = true } +wasm-bindgen = { workspace = true, optional = true } + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/frontend/src/common/prettier/plugins/web/common/src/lib.rs b/frontend/src/common/prettier/plugins/web/common/src/lib.rs new file mode 100644 index 0000000..264e92a --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/common/src/lib.rs @@ -0,0 +1,126 @@ +#[cfg(feature = "serde")] +use serde::Deserialize; + +#[cfg(feature = "wasm-bindgen")] +use wasm_bindgen::prelude::wasm_bindgen; + +#[cfg(feature = "wasm-bindgen")] +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#" +interface LayoutConfig { + indent_style?: "tab" | "space"; + indent_width?: number; + line_width?: number; + line_ending?: "lf" | "crlf"; +}"#; + +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[derive(Clone, Default)] +pub struct LayoutConfig { + #[cfg_attr(feature = "serde", serde(alias = "indentStyle"))] + indent_style: Option, + #[cfg_attr(feature = "serde", serde(alias = "indentWidth"))] + indent_width: Option, + #[cfg_attr(feature = "serde", serde(alias = "lineWidth"))] + line_width: Option, + #[cfg_attr(feature = "serde", serde(alias = "lineEnding"))] + line_ending: Option, +} + +impl LayoutConfig { + pub fn fill_empty_with(mut self, other: &Self) -> Self { + if self.indent_style.is_none() { + self.indent_style = other.indent_style; + } + if self.indent_width.is_none() { + self.indent_width = other.indent_width; + } + if self.line_width.is_none() { + self.line_width = other.line_width; + } + if self.line_ending.is_none() { + self.line_ending = other.line_ending; + } + self + } + + pub fn with_indent_style(mut self, indent_style: IndentStyle) -> Self { + self.indent_style = Some(indent_style); + self + } + + pub fn with_indent_width(mut self, indent_width: u8) -> Self { + self.indent_width = Some(indent_width); + self + } + + pub fn with_line_width(mut self, line_width: u16) -> Self { + self.line_width = Some(line_width); + self + } + + pub fn with_line_ending(mut self, line_ending: LineEnding) -> Self { + self.line_ending = Some(line_ending); + self + } + + pub fn indent_style(&self) -> Option { + self.indent_style + } + + pub fn indent_width(&self) -> Option { + self.indent_width + } + + pub fn line_width(&self) -> Option { + self.line_width + } + + pub fn line_ending(&self) -> Option { + self.line_ending + } +} + +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[derive(Clone, Copy, Default)] +pub enum IndentStyle { + Tab, + #[default] + Space, +} + +impl IndentStyle { + pub fn is_tab(&self) -> bool { + matches!(self, Self::Tab) + } +} + +#[cfg(feature = "biome_formatter")] +impl From for biome_formatter::IndentStyle { + fn from(style: IndentStyle) -> Self { + match style { + IndentStyle::Tab => biome_formatter::IndentStyle::Tab, + IndentStyle::Space => biome_formatter::IndentStyle::Space, + } + } +} + +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +#[derive(Clone, Copy, Default)] +pub enum LineEnding { + #[default] + Lf, + Crlf, +} + +#[cfg(feature = "biome_formatter")] +impl From for biome_formatter::LineEnding { + fn from(ending: LineEnding) -> Self { + match ending { + LineEnding::Lf => biome_formatter::LineEnding::Lf, + LineEnding::Crlf => biome_formatter::LineEnding::Crlf, + } + } +} diff --git a/frontend/src/common/prettier/plugins/web/index.ts b/frontend/src/common/prettier/plugins/web/index.ts new file mode 100644 index 0000000..98d1f25 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/index.ts @@ -0,0 +1,155 @@ +/** + * Prettier Plugin for Web Languages formatting using web_fmt WebAssembly + * + * This plugin provides support for formatting multiple web languages using the web_fmt WASM implementation. + * web_fmt is a comprehensive formatter for web development supporting HTML, CSS, JavaScript, and JSON. + */ +import type { Plugin, Parser, Printer, ParserOptions } from 'prettier'; + +// Import the web_fmt WASM module +import webInit, { format } from './web_fmt_vite.js'; +import { languages } from './languages'; + +const parserName = 'web-fmt'; + +// Parser configuration +const webParser: Parser = { + astFormat: parserName, + parse: (text: string) => text, + locStart: () => 0, + locEnd: (text: string) => text.length, +}; + +// Initialize web_fmt WASM module +let processorInstance: any = null; + +const getProcessorInstance = async () => { + if (!processorInstance) { + try { + await webInit(); + processorInstance = { initialized: true }; + } catch (error) { + console.warn('Failed to initialize web_fmt WASM module:', error); + processorInstance = null; + } + } + return processorInstance; +}; + +// Helper function to convert Prettier options to web_fmt config +function buildWebConfig(options: any): any { + const config: any = {}; + + // Basic layout options + if (options.useTabs !== undefined) { + config.indent_style = options.useTabs ? 'tab' : 'space'; + } + + if (options.tabWidth !== undefined) { + config.indent_width = options.tabWidth; + } + + if (options.printWidth !== undefined) { + config.line_width = options.printWidth; + } + + if (options.endOfLine !== undefined) { + config.line_ending = options.endOfLine === 'crlf' ? 'crlf' : 'lf'; + } + + // Script-specific options (for JS/TS) + if (options.singleQuote !== undefined) { + config.script = config.script || {}; + config.script.quote_style = options.singleQuote ? 'single' : 'double'; + } + + if (options.jsxSingleQuote !== undefined) { + config.script = config.script || {}; + config.script.jsx_quote_style = options.jsxSingleQuote ? 'single' : 'double'; + } + + if (options.trailingComma !== undefined) { + config.script = config.script || {}; + config.script.trailing_comma = options.trailingComma; + } + + if (options.semi !== undefined) { + config.script = config.script || {}; + config.script.semicolons = options.semi ? 'always' : 'as-needed'; + } + + if (options.arrowParens !== undefined) { + config.script = config.script || {}; + config.script.arrow_parentheses = options.arrowParens === 'always' ? 'always' : 'as-needed'; + } + + if (options.bracketSpacing !== undefined) { + config.script = config.script || {}; + config.script.bracket_spacing = options.bracketSpacing; + } + + if (options.bracketSameLine !== undefined) { + config.script = config.script || {}; + config.script.bracket_same_line = options.bracketSameLine; + } + + return config; +} + + +// Printer configuration +const webPrinter: Printer = { + // @ts-expect-error -- Support async printer like shell plugin + async print(path, options) { + const processor = await getProcessorInstance(); + const text = (path as any).getValue ? (path as any).getValue() : path.node; + + if (!processor) { + console.warn('web_fmt WASM not initialized, returning original text'); + return text; + } + + try { + const config = buildWebConfig(options); + + // Format using web_fmt + const formatted = format(text, options.filename, config); + return formatted.trim(); + } catch (error) { + console.warn('web_fmt formatting failed:', error); + // Return original text if formatting fails + return text; + } + }, +}; + +// Parser and printer exports +export const parsers = { + [parserName]: webParser, +}; + +export const printers = { + [parserName]: webPrinter, +}; + +// Configuration options - use standard Prettier options +const options: Plugin['options'] = { + filename: { + // since: '0.1.0', + category: 'Config', + type: 'string', + default: undefined, + description: 'Custom filename to use for web_fmt processing (affects language detection)', + } +}; + +// Plugin object +const webPlugin: Plugin = { + languages, + parsers, + printers, + options, +}; + +export default webPlugin; +export { languages }; diff --git a/frontend/src/common/prettier/plugins/web/json_fmt/Cargo.toml b/frontend/src/common/prettier/plugins/web/json_fmt/Cargo.toml new file mode 100644 index 0000000..6ef4152 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/json_fmt/Cargo.toml @@ -0,0 +1,34 @@ +[package] +description = "JSON formatter powered by WASM ported from Biome" +keywords = ["wasm", "formatter", "json", "biome"] +name = "json_fmt" + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +publish = true +repository.workspace = true +version.workspace = true + + +[features] +default = ["main"] +main = ["wasm-bindgen", "serde-wasm-bindgen", "common/wasm-bindgen"] + +[dependencies] +common = { workspace = true, features = ["biome_formatter", "serde"] } + +biome_formatter = { workspace = true } +biome_json_formatter = { workspace = true } +biome_json_parser = { workspace = true } +biome_json_syntax = { workspace = true } + +serde = { workspace = true, features = ["derive"] } +serde-wasm-bindgen = { workspace = true, optional = true } +serde_json = { workspace = true, features = ["preserve_order"] } +wasm-bindgen = { workspace = true, optional = true } + + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/frontend/src/common/prettier/plugins/web/json_fmt/extra/.npmignore b/frontend/src/common/prettier/plugins/web/json_fmt/extra/.npmignore new file mode 100644 index 0000000..1ee7305 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/json_fmt/extra/.npmignore @@ -0,0 +1,2 @@ +*.tgz +jsr.jsonc \ No newline at end of file diff --git a/frontend/src/common/prettier/plugins/web/json_fmt/extra/json_fmt_node.js b/frontend/src/common/prettier/plugins/web/json_fmt/extra/json_fmt_node.js new file mode 100644 index 0000000..bf3f3df --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/json_fmt/extra/json_fmt_node.js @@ -0,0 +1,10 @@ +import fs from "node:fs/promises"; +import initAsync from "./json_fmt.js"; + +const wasm = new URL("./json_fmt_bg.wasm", import.meta.url); + +export default function __wbg_init(init = { module_or_path: fs.readFile(wasm) }) { + return initAsync(init); +} + +export * from "./json_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/web/json_fmt/extra/json_fmt_vite.js b/frontend/src/common/prettier/plugins/web/json_fmt/extra/json_fmt_vite.js new file mode 100644 index 0000000..bfe6f22 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/json_fmt/extra/json_fmt_vite.js @@ -0,0 +1,8 @@ +import initAsync from "./json_fmt.js"; +import wasm from "./json_fmt_bg.wasm?url"; + +export default function __wbg_init(input = { module_or_path: wasm }) { + return initAsync(input); +} + +export * from "./json_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/web/json_fmt/scripts/build.sh b/frontend/src/common/prettier/plugins/web/json_fmt/scripts/build.sh new file mode 100644 index 0000000..fb3892a --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/json_fmt/scripts/build.sh @@ -0,0 +1,11 @@ +cd $(dirname $0)/.. +crates_dir=$(pwd) + +cd ../.. +wasm-pack build --target=web --scope=wasm-fmt crates/json_fmt + +cd $crates_dir + +cp -R ./extra/. ./pkg/ + +./scripts/package.mjs ./pkg/package.json diff --git a/frontend/src/common/prettier/plugins/web/json_fmt/scripts/package.mjs b/frontend/src/common/prettier/plugins/web/json_fmt/scripts/package.mjs new file mode 100644 index 0000000..1d16430 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/json_fmt/scripts/package.mjs @@ -0,0 +1,39 @@ +#!/usr/bin/env node +import process from "node:process"; +import path from "node:path"; +import fs from "node:fs"; + +const pkg_path = path.resolve(process.cwd(), process.argv[2]); +const pkg_text = fs.readFileSync(pkg_path, { encoding: "utf-8" }); +const pkg_json = JSON.parse(pkg_text); + +delete pkg_json.files; + +pkg_json.main = pkg_json.module; +pkg_json.type = "module"; +pkg_json.publishConfig = { + access: "public", +}; +pkg_json.exports = { + ".": { + types: "./json_fmt.d.ts", + node: "./json_fmt_node.js", + default: "./json_fmt.js", + }, + "./vite": { + types: "./json_fmt.d.ts", + default: "./json_fmt_vite.js", + }, + "./package.json": "./package.json", + "./*": "./*", +}; + +fs.writeFileSync(pkg_path, JSON.stringify(pkg_json, null, 4)); + +// JSR + +const jsr_path = path.resolve(pkg_path, "..", "jsr.jsonc"); +pkg_json.name = "@fmt/json-fmt"; +pkg_json.exports = "./json_fmt.js"; +pkg_json.exclude = ["!**", "*.tgz"]; +fs.writeFileSync(jsr_path, JSON.stringify(pkg_json, null, 4)); diff --git a/frontend/src/common/prettier/plugins/web/json_fmt/src/config.rs b/frontend/src/common/prettier/plugins/web/json_fmt/src/config.rs new file mode 100644 index 0000000..8752f5f --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/json_fmt/src/config.rs @@ -0,0 +1,57 @@ +use biome_formatter::LineWidthFromIntError; +use biome_json_formatter::context::JsonFormatOptions; +use common::LayoutConfig; +use serde::Deserialize; + +#[cfg(feature = "wasm-bindgen")] +use wasm_bindgen::prelude::wasm_bindgen; + +#[cfg(feature = "wasm-bindgen")] +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#"export type Config = LayoutConfig;"#; + +#[derive(Deserialize, Default, Clone)] +#[serde(transparent)] +pub struct JsonConfig(LayoutConfig); + +impl From for JsonConfig { + fn from(config: LayoutConfig) -> Self { + Self(config) + } +} + +impl JsonConfig { + pub fn with_line_width(mut self, line_width: u16) -> Self { + self.0 = self.0.with_line_width(line_width); + self + } +} + +impl TryFrom for JsonFormatOptions { + type Error = String; + + fn try_from(value: JsonConfig) -> Result { + let mut option = JsonFormatOptions::default(); + + if let Some(indent_style) = value.0.indent_style() { + option = option.with_indent_style(indent_style.into()); + }; + + if let Some(indent_width) = value.0.indent_width() { + option = option.with_indent_width(indent_width.into()); + } + + if let Some(line_ending) = value.0.line_ending() { + option = option.with_line_ending(line_ending.into()); + }; + + if let Some(line_width) = value.0.line_width() { + let line_width = + line_width.try_into().map_err(|e: LineWidthFromIntError| e.to_string())?; + + option = option.with_line_width(line_width); + }; + + Ok(option) + } +} diff --git a/frontend/src/common/prettier/plugins/web/json_fmt/src/lib.rs b/frontend/src/common/prettier/plugins/web/json_fmt/src/lib.rs new file mode 100644 index 0000000..a596fc9 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/json_fmt/src/lib.rs @@ -0,0 +1,41 @@ +mod config; + +use biome_formatter::Printed; +use biome_json_formatter::format_node; +use biome_json_parser::{parse_json, JsonParserOptions}; + +use config::JsonConfig; +#[cfg(feature = "wasm-bindgen")] +use wasm_bindgen::prelude::*; + +#[cfg(feature = "wasm-bindgen")] +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Config")] + pub type Config; +} + +#[cfg(feature = "wasm-bindgen")] +#[wasm_bindgen(skip_typescript)] +pub fn format(src: &str, config: Option) -> Result { + let config = config + .map(|x| serde_wasm_bindgen::from_value(x.clone())) + .transpose() + .map_err(|op| op.to_string())? + .unwrap_or_default(); + + format_json_with_config(src, config) +} + +pub fn format_json_with_config(src: &str, config: JsonConfig) -> Result { + let options = JsonParserOptions::default().with_allow_comments().with_allow_trailing_commas(); + let parse = parse_json(src, options); + + let options = config.try_into()?; + + format_node(options, &parse.syntax()) + .map_err(|e| e.to_string())? + .print() + .map(Printed::into_code) + .map_err(|e| e.to_string()) +} diff --git a/frontend/src/common/prettier/plugins/web/languages.ts b/frontend/src/common/prettier/plugins/web/languages.ts new file mode 100644 index 0000000..953c72e --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/languages.ts @@ -0,0 +1,130 @@ +export const languages = [ + // HTML and template languages + { + name: "HTML", + parsers: ["html"], + extensions: [".html", ".htm"], + filenames: ["*.html", "*.htm"], + tmScope: "text.html.basic", + aceMode: "html", + codemirrorMode: "htmlmixed", + linguistLanguageId: 172, + vscodeLanguageIds: ["html"] + }, + { + name: "Vue", + parsers: ["vue"], + extensions: [".vue"], + filenames: ["*.vue"], + tmScope: "text.html.vue", + aceMode: "html", + codemirrorMode: "vue", + linguistLanguageId: 391, + vscodeLanguageIds: ["vue"] + }, + { + name: "Svelte", + parsers: ["svelte"], + extensions: [".svelte"], + filenames: ["*.svelte"], + tmScope: "source.svelte", + aceMode: "html", + codemirrorMode: "htmlmixed", + linguistLanguageId: 377, + vscodeLanguageIds: ["svelte"] + }, + { + name: "Astro", + parsers: ["astro"], + extensions: [".astro"], + filenames: ["*.astro"], + tmScope: "source.astro", + aceMode: "html", + codemirrorMode: "htmlmixed", + linguistLanguageId: 578, + vscodeLanguageIds: ["astro"] + }, + + // Stylesheet languages + { + name: "CSS", + parsers: ["css"], + extensions: [".css"], + filenames: ["*.css"], + tmScope: "source.css", + aceMode: "css", + codemirrorMode: "css", + linguistLanguageId: 50, + vscodeLanguageIds: ["css"] + }, + { + name: "SCSS", + parsers: ["scss"], + extensions: [".scss"], + filenames: ["*.scss"], + tmScope: "source.css.scss", + aceMode: "scss", + codemirrorMode: "css", + linguistLanguageId: 329, + vscodeLanguageIds: ["scss"] + }, + { + name: "Sass", + parsers: ["sass"], + extensions: [".sass"], + filenames: ["*.sass"], + tmScope: "source.sass", + aceMode: "sass", + codemirrorMode: "sass", + linguistLanguageId: 207, + vscodeLanguageIds: ["sass"] + }, + { + name: "Less", + parsers: ["less"], + extensions: [".less"], + filenames: ["*.less"], + tmScope: "source.css.less", + aceMode: "less", + codemirrorMode: "css", + linguistLanguageId: 198, + vscodeLanguageIds: ["less"] + }, + + // Script languages + { + name: "JavaScript", + parsers: ["js"], + extensions: [".js", ".mjs", ".cjs", ".jsx"], + filenames: ["*.js", "*.mjs", "*.cjs", "*.jsx"], + tmScope: "source.js", + aceMode: "javascript", + codemirrorMode: "javascript", + linguistLanguageId: 183, + vscodeLanguageIds: ["javascript"] + }, + { + name: "TypeScript", + parsers: ["ts"], + extensions: [".ts", ".tsx", ".cts", ".mts"], + filenames: ["*.ts", "*.tsx", "*.cts", "*.mts"], + tmScope: "source.ts", + aceMode: "typescript", + codemirrorMode: "javascript", + linguistLanguageId: 378, + vscodeLanguageIds: ["typescript"] + }, + + // Data languages + { + name: "JSON", + parsers: ["json"], + extensions: [".json", ".jsonc"], + filenames: ["*.json", "*.jsonc", ".babelrc", ".eslintrc", "tsconfig.json"], + tmScope: "source.json", + aceMode: "json", + codemirrorMode: "javascript", + linguistLanguageId: 174, + vscodeLanguageIds: ["json", "jsonc"] + } +]; diff --git a/frontend/src/common/prettier/plugins/web/web_fmt.d.ts b/frontend/src/common/prettier/plugins/web/web_fmt.d.ts new file mode 100644 index 0000000..ecfc14a --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt.d.ts @@ -0,0 +1,90 @@ +/* tslint:disable */ +/* eslint-disable */ +export function format_json(src: string, config?: JsonConfig): string; +export function format_markup(src: string, filename: string, config?: MarkupConfig): string; +export function format_script(src: string, filename: string, config?: ScriptConfig): string; +export function format_style(src: string, filename: string, config?: StyleConfig): string; +export function format(src: string, filename: string, config?: Config): string; +export type JsonConfig = LayoutConfig; + + +export interface MarkupConfig extends LayoutConfig { + /** + * See {@link https://github.com/g-plane/markup_fmt/blob/main/docs/config.md} + */ + [other: string]: any; +} + + +export interface ScriptConfig extends LayoutConfig { + quote_style?: "double" | "single"; + jsx_quote_style?: "double" | "single"; + quote_properties?: "preserve" | "as-needed"; + trailing_comma?: "es5" | "all" | "none"; + semicolons?: "always" | "as-needed"; + arrow_parentheses?: "always" | "as-needed"; + bracket_spacing?: boolean; + bracket_same_line?: boolean; +} + + +export interface StyleConfig extends LayoutConfig { + /** + * See {@link https://github.com/g-plane/malva/blob/main/docs/config.md} + */ + [other: string]: any; +} + + +export interface Config extends LayoutConfig { + markup?: MarkupConfig; + script?: ScriptConfig; + style?: StyleConfig; + json?: JsonConfig; +} + + +interface LayoutConfig { + indent_style?: "tab" | "space"; + indent_width?: number; + line_width?: number; + line_ending?: "lf" | "crlf"; +} + + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly format_json: (a: number, b: number, c: number, d: number) => void; + readonly format_markup: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly format_script: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly format_style: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly format: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly __wbindgen_export_0: (a: number, b: number) => number; + readonly __wbindgen_export_1: (a: number, b: number, c: number, d: number) => number; + readonly __wbindgen_export_2: (a: number) => void; + readonly __wbindgen_add_to_stack_pointer: (a: number) => number; + readonly __wbindgen_export_3: (a: number, b: number, c: number) => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; +/** +* Instantiates the given `module`, which can either be bytes or +* a precompiled `WebAssembly.Module`. +* +* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. +* +* @returns {InitOutput} +*/ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. +* +* @returns {Promise} +*/ +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/frontend/src/common/prettier/plugins/web/web_fmt.js b/frontend/src/common/prettier/plugins/web/web_fmt.js new file mode 100644 index 0000000..2655841 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt.js @@ -0,0 +1,673 @@ +let wasm; + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let WASM_VECTOR_LEN = 0; + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_export_2(addHeapObject(e)); + } +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} +/** + * @param {string} src + * @param {JsonConfig | undefined} [config] + * @returns {string} + */ +export function format_json(src, config) { + let deferred3_0; + let deferred3_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(src, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + wasm.format_json(retptr, ptr0, len0, isLikeNone(config) ? 0 : addHeapObject(config)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr2 = r0; + var len2 = r1; + if (r3) { + ptr2 = 0; len2 = 0; + throw takeObject(r2); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred3_0, deferred3_1, 1); + } +} + +/** + * @param {string} src + * @param {string} filename + * @param {MarkupConfig | undefined} [config] + * @returns {string} + */ +export function format_markup(src, filename, config) { + let deferred4_0; + let deferred4_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(src, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(filename, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + wasm.format_markup(retptr, ptr0, len0, ptr1, len1, isLikeNone(config) ? 0 : addHeapObject(config)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr3 = r0; + var len3 = r1; + if (r3) { + ptr3 = 0; len3 = 0; + throw takeObject(r2); + } + deferred4_0 = ptr3; + deferred4_1 = len3; + return getStringFromWasm0(ptr3, len3); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred4_0, deferred4_1, 1); + } +} + +/** + * @param {string} src + * @param {string} filename + * @param {ScriptConfig | undefined} [config] + * @returns {string} + */ +export function format_script(src, filename, config) { + let deferred4_0; + let deferred4_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(src, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(filename, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + wasm.format_script(retptr, ptr0, len0, ptr1, len1, isLikeNone(config) ? 0 : addHeapObject(config)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr3 = r0; + var len3 = r1; + if (r3) { + ptr3 = 0; len3 = 0; + throw takeObject(r2); + } + deferred4_0 = ptr3; + deferred4_1 = len3; + return getStringFromWasm0(ptr3, len3); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred4_0, deferred4_1, 1); + } +} + +/** + * @param {string} src + * @param {string} filename + * @param {StyleConfig | undefined} [config] + * @returns {string} + */ +export function format_style(src, filename, config) { + let deferred4_0; + let deferred4_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(src, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(filename, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + wasm.format_style(retptr, ptr0, len0, ptr1, len1, isLikeNone(config) ? 0 : addHeapObject(config)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr3 = r0; + var len3 = r1; + if (r3) { + ptr3 = 0; len3 = 0; + throw takeObject(r2); + } + deferred4_0 = ptr3; + deferred4_1 = len3; + return getStringFromWasm0(ptr3, len3); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred4_0, deferred4_1, 1); + } +} + +/** + * @param {string} src + * @param {string} filename + * @param {Config | undefined} [config] + * @returns {string} + */ +export function format(src, filename, config) { + let deferred4_0; + let deferred4_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(src, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(filename, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + wasm.format(retptr, ptr0, len0, ptr1, len1, isLikeNone(config) ? 0 : addHeapObject(config)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr3 = r0; + var len3 = r1; + if (r3) { + ptr3 = 0; len3 = 0; + throw takeObject(r2); + } + deferred4_0 = ptr3; + deferred4_1 = len3; + return getStringFromWasm0(ptr3, len3); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export_3(deferred4_0, deferred4_1, 1); + } +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_String_8f0eb39a4a4c2f66 = function(arg0, arg1) { + const ret = String(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_buffer_61b7ce01341d7f88 = function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_call_b0d8e36992d9900d = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_done_f22c1561fa919baa = function(arg0) { + const ret = getObject(arg0).done; + return ret; + }; + imports.wbg.__wbg_entries_4f2bb9b0d701c0f6 = function(arg0) { + const ret = Object.entries(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_9aa3dff3f0266054 = function(arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_bbccf8970793c087 = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_getwithrefkey_1dc361bd10053bfe = function(arg0, arg1) { + const ret = getObject(arg0)[getObject(arg1)]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_ArrayBuffer_670ddde44cdb2602 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_Map_98ecb30afec5acdb = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Map; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_Uint8Array_28af5bc19d6acad8 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_isArray_1ba11a930108ec51 = function(arg0) { + const ret = Array.isArray(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_isSafeInteger_12f5549b2fca23f4 = function(arg0) { + const ret = Number.isSafeInteger(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_iterator_23604bb983791576 = function() { + const ret = Symbol.iterator; + return addHeapObject(ret); + }; + imports.wbg.__wbg_length_65d1cd11729ced11 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_length_d65cf0786bfc5739 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_new_3ff5b33b1ce712df = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_next_01dd9234a5bf6d05 = function() { return handleError(function (arg0) { + const ret = getObject(arg0).next(); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_next_137428deb98342b0 = function(arg0) { + const ret = getObject(arg0).next; + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_23d69db4e5c66a6e = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbg_value_4c32fd138a88eee2 = function(arg0) { + const ret = getObject(arg0).value; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_as_number = function(arg0) { + const ret = +getObject(arg0); + return ret; + }; + imports.wbg.__wbindgen_bigint_from_i64 = function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_bigint_from_u64 = function(arg0) { + const ret = BigInt.asUintN(64, arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_bigint_get_as_i64 = function(arg0, arg1) { + const v = getObject(arg1); + const ret = typeof(v) === 'bigint' ? v : undefined; + getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }; + imports.wbg.__wbindgen_boolean_get = function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_error_new = function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_in = function(arg0, arg1) { + const ret = getObject(arg0) in getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_is_bigint = function(arg0) { + const ret = typeof(getObject(arg0)) === 'bigint'; + return ret; + }; + imports.wbg.__wbindgen_is_function = function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }; + imports.wbg.__wbindgen_is_object = function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; + }; + imports.wbg.__wbindgen_is_string = function(arg0) { + const ret = typeof(getObject(arg0)) === 'string'; + return ret; + }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbindgen_jsval_eq = function(arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1); + return ret; + }; + imports.wbg.__wbindgen_memory = function() { + const ret = wasm.memory; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_number_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'number' ? obj : undefined; + getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + + return imports; +} + +function __wbg_init_memory(imports, memory) { + +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedDataViewMemory0 = null; + cachedUint8ArrayMemory0 = null; + + + + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined') { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined') { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (typeof module_or_path === 'undefined') { + module_or_path = new URL('web_fmt_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync }; +export default __wbg_init; diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/Cargo.toml b/frontend/src/common/prettier/plugins/web/web_fmt/Cargo.toml new file mode 100644 index 0000000..4184c95 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/Cargo.toml @@ -0,0 +1,32 @@ +[package] +description = "a formatter for web development powered by WASM" +keywords = ["wasm", "formatter", "html", "css", "javascript"] +name = "web_fmt" + +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +publish = true +repository.workspace = true +version.workspace = true + +[dependencies] +common = { workspace = true, features = [ + "biome_formatter", + "serde", + "wasm-bindgen", +] } + +biome_fmt = { workspace = true, default-features = false } +json_fmt = { workspace = true, default-features = false } +malva = { workspace = true, features = ["config_serde"] } +markup_fmt = { workspace = true, features = ["config_serde"] } + +serde = { workspace = true, features = ["derive"] } +serde-wasm-bindgen = { workspace = true } +serde_json = { workspace = true, features = ["preserve_order"] } +wasm-bindgen = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/extra/.npmignore b/frontend/src/common/prettier/plugins/web/web_fmt/extra/.npmignore new file mode 100644 index 0000000..1ee7305 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/extra/.npmignore @@ -0,0 +1,2 @@ +*.tgz +jsr.jsonc \ No newline at end of file diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/extra/web_fmt_node.js b/frontend/src/common/prettier/plugins/web/web_fmt/extra/web_fmt_node.js new file mode 100644 index 0000000..c49c6ca --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/extra/web_fmt_node.js @@ -0,0 +1,10 @@ +import fs from "node:fs/promises"; +import initAsync from "./web_fmt.js"; + +const wasm = new URL("./web_fmt_bg.wasm", import.meta.url); + +export default function __wbg_init(init = { module_or_path: fs.readFile(wasm) }) { + return initAsync(init); +} + +export * from "./web_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/extra/web_fmt_vite.js b/frontend/src/common/prettier/plugins/web/web_fmt/extra/web_fmt_vite.js new file mode 100644 index 0000000..b0462b9 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/extra/web_fmt_vite.js @@ -0,0 +1,8 @@ +import initAsync from "./web_fmt.js"; +import wasm from "./web_fmt_bg.wasm?url"; + +export default function __wbg_init(input = { module_or_path: wasm }) { + return initAsync(input); +} + +export * from "./web_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/scripts/build.sh b/frontend/src/common/prettier/plugins/web/web_fmt/scripts/build.sh new file mode 100644 index 0000000..4b2ec50 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/scripts/build.sh @@ -0,0 +1,11 @@ +cd $(dirname $0)/.. +crates_dir=$(pwd) + +cd ../.. +wasm-pack build --target=web --scope=wasm-fmt crates/web_fmt + +cd $crates_dir + +cp -R ./extra/. ./pkg/ + +./scripts/package.mjs ./pkg/package.json diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/scripts/package.mjs b/frontend/src/common/prettier/plugins/web/web_fmt/scripts/package.mjs new file mode 100644 index 0000000..c94c56b --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/scripts/package.mjs @@ -0,0 +1,39 @@ +#!/usr/bin/env node +import process from "node:process"; +import path from "node:path"; +import fs from "node:fs"; + +const pkg_path = path.resolve(process.cwd(), process.argv[2]); +const pkg_text = fs.readFileSync(pkg_path, { encoding: "utf-8" }); +const pkg_json = JSON.parse(pkg_text); + +delete pkg_json.files; + +pkg_json.main = pkg_json.module; +pkg_json.type = "module"; +pkg_json.publishConfig = { + access: "public", +}; +pkg_json.exports = { + ".": { + types: "./web_fmt.d.ts", + node: "./web_fmt_node.js", + default: "./web_fmt.js", + }, + "./vite": { + types: "./web_fmt.d.ts", + default: "./web_fmt_vite.js", + }, + "./package.json": "./package.json", + "./*": "./*", +}; + +fs.writeFileSync(pkg_path, JSON.stringify(pkg_json, null, 4)); + +// JSR + +const jsr_path = path.resolve(pkg_path, "..", "jsr.jsonc"); +pkg_json.name = "@fmt/web-fmt"; +pkg_json.exports = "./web_fmt.js"; +pkg_json.exclude = ["!**", "*.tgz"]; +fs.writeFileSync(jsr_path, JSON.stringify(pkg_json, null, 4)); diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/src/format_json.rs b/frontend/src/common/prettier/plugins/web/web_fmt/src/format_json.rs new file mode 100644 index 0000000..2be026a --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/src/format_json.rs @@ -0,0 +1,40 @@ +use common::LayoutConfig; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "JsonConfig")] + pub type Config; +} + +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#"export type JsonConfig = LayoutConfig;"#; + +#[wasm_bindgen] +pub fn format_json(src: &str, config: Option) -> Result { + let config = config + .map(|x| serde_wasm_bindgen::from_value(x.clone())) + .transpose() + .map_err(|op| op.to_string())? + .unwrap_or_default(); + + json_fmt::format_json_with_config(src, config) +} + +pub(crate) fn produce_json_config( + config: Option, + config_default: &LayoutConfig, + global_fallback: &LayoutConfig, +) -> LayoutConfig { + let default = LayoutConfig::default() + .with_indent_style(common::IndentStyle::Space) + .with_indent_width(2) + .with_line_width(80) + .with_line_ending(common::LineEnding::Lf); + + config + .unwrap_or_default() + .fill_empty_with(config_default) + .fill_empty_with(global_fallback) + .fill_empty_with(&default) +} diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/src/format_markup.rs b/frontend/src/common/prettier/plugins/web/web_fmt/src/format_markup.rs new file mode 100644 index 0000000..e30c4e4 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/src/format_markup.rs @@ -0,0 +1,146 @@ +use std::path::Path; + +use common::LayoutConfig; + +use markup_fmt::Hints; +use wasm_bindgen::prelude::*; + +use crate::format_style; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "MarkupConfig")] + pub type Config; +} + +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#" +export interface MarkupConfig extends LayoutConfig { + /** + * See {@link https://github.com/g-plane/markup_fmt/blob/main/docs/config.md} + */ + [other: string]: any; +}"#; + +#[wasm_bindgen] +pub fn format_markup(src: &str, filename: &str, config: Option) -> Result { + let default_config: LayoutConfig = config + .as_ref() + .map(|x| serde_wasm_bindgen::from_value(x.into())) + .transpose() + .map_err(|e| e.to_string())? + .unwrap_or_default(); + + let config = config + .map(|x| serde_wasm_bindgen::from_value(x.clone())) + .transpose() + .map_err(|e| e.to_string())?; + + let markup_config = produce_markup_config(config, &default_config, &default_config); + let style_config = format_style::produce_style_config(None, &default_config, &default_config); + let script_config = biome_fmt::BiomeConfig::default().fill_empty_layout_with(&default_config); + let json_config = LayoutConfig::default().fill_empty_with(&default_config); + + format_markup_with_config( + src, + filename, + markup_config, + style_config, + script_config, + json_config, + ) +} + +pub fn format_markup_with_config( + src: &str, + filename: &str, + markup_config: markup_fmt::config::FormatOptions, + style_config: malva::config::FormatOptions, + script_config: biome_fmt::BiomeConfig, + json_config: LayoutConfig, +) -> Result { + let language = detect_language(filename).unwrap_or(markup_fmt::Language::Html); + + markup_fmt::format_text( + src, + language, + &markup_config, + |src, Hints { print_width, attr, ext, .. }| match ext.as_bytes() { + b"js" | b"ts" | b"mjs" | b"cjs" | b"jsx" | b"tsx" | b"mjsx" | b"cjsx" | b"mtsx" => { + biome_fmt::format_script_with_config( + src, + filename, + script_config.clone().with_line_width(print_width as u16), + ) + .map(Into::into) + } + b"css" | b"scss" | b"sass" | b"less" => { + let mut style_config = style_config.clone(); + if attr { + if let markup_fmt::config::Quotes::Double = markup_config.language.quotes { + style_config.language.quotes = malva::config::Quotes::AlwaysSingle; + } else { + style_config.language.quotes = malva::config::Quotes::AlwaysDouble; + } + style_config.language.single_line_top_level_declarations = true; + } + format_style::format_style_with_config( + src, + filename, + malva::config::FormatOptions { + layout: malva::config::LayoutOptions { print_width, ..style_config.layout }, + language: style_config.language, + }, + ) + .map(Into::into) + } + b"json" | b"jsonc" => json_fmt::format_json_with_config( + src, + json_config.clone().with_line_width(print_width as u16).into(), + ) + .map(Into::into), + _ => Ok(src.into()), + }, + ) + .map_err(|e| format!("{:?}", e)) +} + +pub(crate) fn detect_language(path: impl AsRef) -> Option { + match path.as_ref().extension().map(|x| x.to_ascii_lowercase())?.as_encoded_bytes() { + b"html" => Some(markup_fmt::Language::Html), + b"vue" => Some(markup_fmt::Language::Vue), + b"svelte" => Some(markup_fmt::Language::Svelte), + b"astro" => Some(markup_fmt::Language::Astro), + b"jinja" | b"jinja2" | b"twig" => Some(markup_fmt::Language::Jinja), + _ => Some(markup_fmt::Language::Html), + } +} + +pub(crate) fn produce_markup_config( + base_config: Option, + config_default: &LayoutConfig, + global_fallback: &LayoutConfig, +) -> markup_fmt::config::FormatOptions { + let mut config = base_config.unwrap_or_default(); + + if let Some(indent_style) = config_default.indent_style().or(global_fallback.indent_style()) { + config.layout.use_tabs = indent_style.is_tab(); + } + + if let Some(indent_width) = config_default.indent_width().or(global_fallback.indent_width()) { + config.layout.indent_width = indent_width as usize; + } + + if let Some(line_width) = config_default.line_width().or(global_fallback.line_width()) { + config.layout.print_width = line_width as usize; + } + + if let Some(line_endings) = config_default.line_ending().or(global_fallback.line_ending()) { + config.layout.line_break = match line_endings { + common::LineEnding::Lf => markup_fmt::config::LineBreak::Lf, + common::LineEnding::Crlf => markup_fmt::config::LineBreak::Crlf, + }; + } + + config +} diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/src/format_script.rs b/frontend/src/common/prettier/plugins/web/web_fmt/src/format_script.rs new file mode 100644 index 0000000..750d047 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/src/format_script.rs @@ -0,0 +1,50 @@ +use common::LayoutConfig; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "ScriptConfig")] + pub type Config; +} + +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#" +export interface ScriptConfig extends LayoutConfig { + quote_style?: "double" | "single"; + jsx_quote_style?: "double" | "single"; + quote_properties?: "preserve" | "as-needed"; + trailing_comma?: "es5" | "all" | "none"; + semicolons?: "always" | "as-needed"; + arrow_parentheses?: "always" | "as-needed"; + bracket_spacing?: boolean; + bracket_same_line?: boolean; +}"#; + +#[wasm_bindgen] +pub fn format_script(src: &str, filename: &str, config: Option) -> Result { + let config = config + .map(|x| serde_wasm_bindgen::from_value(x.clone())) + .transpose() + .map_err(|op| op.to_string())? + .unwrap_or_default(); + + biome_fmt::format_script_with_config(src, filename, config) +} + +pub(crate) fn produce_script_config( + config: Option, + config_default: &LayoutConfig, + global_fallback: &LayoutConfig, +) -> biome_fmt::BiomeConfig { + let default = LayoutConfig::default() + .with_indent_style(common::IndentStyle::Space) + .with_indent_width(2) + .with_line_width(80) + .with_line_ending(common::LineEnding::Lf); + + config + .unwrap_or_default() + .fill_empty_layout_with(config_default) + .fill_empty_layout_with(global_fallback) + .fill_empty_layout_with(&default) +} diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/src/format_style.rs b/frontend/src/common/prettier/plugins/web/web_fmt/src/format_style.rs new file mode 100644 index 0000000..6bd4708 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/src/format_style.rs @@ -0,0 +1,102 @@ +use common::LayoutConfig; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "StyleConfig")] + pub type Config; +} + +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#" +export interface StyleConfig extends LayoutConfig { + /** + * See {@link https://github.com/g-plane/malva/blob/main/docs/config.md} + */ + [other: string]: any; +}"#; + +#[wasm_bindgen] +pub fn format_style(src: &str, filename: &str, config: Option) -> Result { + let default_config: LayoutConfig = config + .as_ref() + .map(|x| serde_wasm_bindgen::from_value(x.into())) + .transpose() + .map_err(|e| e.to_string())? + .unwrap_or_default(); + + let config = config + .map(|x| serde_wasm_bindgen::from_value(x.clone())) + .transpose() + .map_err(|e| e.to_string())?; + + let config = produce_style_config(config, &default_config, &default_config); + + format_style_with_config(src, filename, config) +} + +pub fn format_style_with_config( + src: &str, + filename: &str, + config: malva::config::FormatOptions, +) -> Result { + let syntax = syntax_from_filename(filename).unwrap_or_default(); + + malva::format_text(src, syntax, &config).map_err(|e| e.to_string()) +} + +fn syntax_from_filename(filename: &str) -> Option { + let filename = filename.strip_suffix(['s', 'S'])?; + let filename = filename.strip_suffix(['s', 'S'])?; + + let result = 'result: { + if let Some(filename) = filename.strip_suffix(['a', 'A']) { + filename.strip_suffix(['s', 'S'])?; + break 'result malva::Syntax::Sass; + } + + if let Some(filename) = filename.strip_suffix(['e', 'E']) { + filename.strip_suffix(['l', 'L'])?; + break 'result malva::Syntax::Less; + } + + if filename.strip_suffix(['c', 'C'])?.strip_suffix(['s', 'S']).is_some() { + break 'result malva::Syntax::Scss; + } + + malva::Syntax::Css + }; + + filename.strip_suffix('.')?; + + Some(result) +} + +pub(crate) fn produce_style_config( + base_config: Option, + config_default: &LayoutConfig, + global_fallback: &LayoutConfig, +) -> malva::config::FormatOptions { + let mut config: malva::config::FormatOptions = base_config.unwrap_or_default(); + + if let Some(indent_style) = config_default.indent_style().or(global_fallback.indent_style()) { + config.layout.use_tabs = indent_style.is_tab(); + } + + if let Some(indent_width) = config_default.indent_width().or(global_fallback.indent_width()) { + config.layout.indent_width = indent_width as usize; + } + + if let Some(line_width) = config_default.line_width().or(global_fallback.line_width()) { + config.layout.print_width = line_width as usize; + } + + if let Some(line_endings) = config_default.line_ending().or(global_fallback.line_ending()) { + config.layout.line_break = match line_endings { + common::LineEnding::Lf => malva::config::LineBreak::Lf, + common::LineEnding::Crlf => malva::config::LineBreak::Crlf, + }; + } + + config +} diff --git a/frontend/src/common/prettier/plugins/web/web_fmt/src/lib.rs b/frontend/src/common/prettier/plugins/web/web_fmt/src/lib.rs new file mode 100644 index 0000000..52a70d6 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt/src/lib.rs @@ -0,0 +1,109 @@ +mod format_json; +mod format_markup; +mod format_script; +mod format_style; + +use std::path::Path; + +use common::LayoutConfig; +use serde::Deserialize; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Config")] + pub type JSConfig; +} + +#[derive(Deserialize, Default)] +#[serde(rename_all = "snake_case")] +struct Config { + markup: Option, + script: Option, + style: Option, + json: Option, +} + +#[wasm_bindgen(typescript_custom_section)] +const TS_Config: &'static str = r#" +export interface Config extends LayoutConfig { + markup?: MarkupConfig; + script?: ScriptConfig; + style?: StyleConfig; + json?: JsonConfig; +}"#; + +#[wasm_bindgen] +pub fn format(src: &str, filename: &str, config: Option) -> Result { + let default_config: ConfigDefault = config + .as_ref() + .map(|x| serde_wasm_bindgen::from_value(x.into())) + .transpose() + .map_err(|e| e.to_string())? + .unwrap_or_default(); + + let config: Config = config + .as_ref() + .map(|x| serde_wasm_bindgen::from_value(x.into())) + .transpose() + .map_err(|e| e.to_string())? + .unwrap_or_default(); + + let extension = Path::new(&filename).extension().ok_or("expected extension")?; + + let script_config = format_script::produce_script_config( + config.script, + &default_config.script, + &default_config.default, + ); + let style_config = format_style::produce_style_config( + config.style, + &default_config.style, + &default_config.default, + ); + let markup_config = format_markup::produce_markup_config( + config.markup, + &default_config.markup, + &default_config.default, + ); + let json_config = format_json::produce_json_config( + config.json, + &default_config.json, + &default_config.default, + ); + + match extension.as_encoded_bytes() { + b"js" | b"ts" | b"mjs" | b"cjs" | b"jsx" | b"tsx" | b"mjsx" | b"cjsx" | b"mtsx" + | b"ctsx" => biome_fmt::format_script_with_config(src, filename, script_config), + b"css" | b"scss" | b"sass" | b"less" => { + format_style::format_style_with_config(src, filename, style_config) + } + b"html" | b"vue" | b"svelte" | b"astro" | b"jinja" | b"jinja2" | b"twig" => { + format_markup::format_markup_with_config( + src, + filename, + markup_config, + style_config, + script_config, + json_config, + ) + } + b"json" | b"jsonc" => json_fmt::format_json_with_config(src, json_config.into()), + _ => Err(format!("unsupported file extension: {}", filename)), + } +} + +#[derive(Deserialize, Default)] +#[serde(rename_all = "snake_case")] +struct ConfigDefault { + #[serde(flatten, default)] + default: LayoutConfig, + #[serde(default)] + markup: LayoutConfig, + #[serde(default)] + script: LayoutConfig, + #[serde(default)] + style: LayoutConfig, + #[serde(default)] + json: LayoutConfig, +} diff --git a/frontend/src/common/prettier/plugins/web/web_fmt_bg.wasm b/frontend/src/common/prettier/plugins/web/web_fmt_bg.wasm new file mode 100644 index 0000000..bfec3af Binary files /dev/null and b/frontend/src/common/prettier/plugins/web/web_fmt_bg.wasm differ diff --git a/frontend/src/common/prettier/plugins/web/web_fmt_bg.wasm.d.ts b/frontend/src/common/prettier/plugins/web/web_fmt_bg.wasm.d.ts new file mode 100644 index 0000000..f249126 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt_bg.wasm.d.ts @@ -0,0 +1,13 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const format_json: (a: number, b: number, c: number, d: number) => void; +export const format_markup: (a: number, b: number, c: number, d: number, e: number, f: number) => void; +export const format_script: (a: number, b: number, c: number, d: number, e: number, f: number) => void; +export const format_style: (a: number, b: number, c: number, d: number, e: number, f: number) => void; +export const format: (a: number, b: number, c: number, d: number, e: number, f: number) => void; +export const __wbindgen_export_0: (a: number, b: number) => number; +export const __wbindgen_export_1: (a: number, b: number, c: number, d: number) => number; +export const __wbindgen_export_2: (a: number) => void; +export const __wbindgen_add_to_stack_pointer: (a: number) => number; +export const __wbindgen_export_3: (a: number, b: number, c: number) => void; diff --git a/frontend/src/common/prettier/plugins/web/web_fmt_node.js b/frontend/src/common/prettier/plugins/web/web_fmt_node.js new file mode 100644 index 0000000..c49c6ca --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt_node.js @@ -0,0 +1,10 @@ +import fs from "node:fs/promises"; +import initAsync from "./web_fmt.js"; + +const wasm = new URL("./web_fmt_bg.wasm", import.meta.url); + +export default function __wbg_init(init = { module_or_path: fs.readFile(wasm) }) { + return initAsync(init); +} + +export * from "./web_fmt.js"; diff --git a/frontend/src/common/prettier/plugins/web/web_fmt_vite.js b/frontend/src/common/prettier/plugins/web/web_fmt_vite.js new file mode 100644 index 0000000..b0462b9 --- /dev/null +++ b/frontend/src/common/prettier/plugins/web/web_fmt_vite.js @@ -0,0 +1,8 @@ +import initAsync from "./web_fmt.js"; +import wasm from "./web_fmt_bg.wasm?url"; + +export default function __wbg_init(input = { module_or_path: wasm }) { + return initAsync(input); +} + +export * from "./web_fmt.js"; diff --git a/frontend/src/components/toolbar/BlockLanguageSelector.vue b/frontend/src/components/toolbar/BlockLanguageSelector.vue index 3f6ca13..6242fd1 100644 --- a/frontend/src/components/toolbar/BlockLanguageSelector.vue +++ b/frontend/src/components/toolbar/BlockLanguageSelector.vue @@ -2,7 +2,8 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'; import { useI18n } from 'vue-i18n'; import { useEditorStore } from '@/stores/editorStore'; -import { SUPPORTED_LANGUAGES, type SupportedLanguage } from '@/views/editor/extensions/codeblock/types'; +import { type SupportedLanguage } from '@/views/editor/extensions/codeblock/types'; +import { LANGUAGES, getAllSupportedLanguages } from '@/views/editor/extensions/codeblock/lang-parser/languages'; import { getActiveNoteBlock } from '@/views/editor/extensions/codeblock/state'; import { changeCurrentBlockLanguage } from '@/views/editor/extensions/codeblock/commands'; @@ -14,73 +15,37 @@ const showLanguageMenu = ref(false); const searchQuery = ref(''); const searchInputRef = ref(); -// 语言别名映射 -const LANGUAGE_ALIASES: Record = { - auto: 'auto', - text: 'txt', - json: 'JSON', - py: 'python', - html: 'HTML', - sql: 'SQL', - md: 'markdown', - java: 'Java', - php: 'PHP', - css: 'CSS', - xml: 'XML', - cpp: 'c++', - rs: 'rust', - cs: 'c#', - rb: 'ruby', - sh: 'shell', - yaml: 'yml', - toml: 'TOML', - go: 'Go', - clj: 'clojure', - ex: 'elixir', - erl: 'erlang', - js: 'javascript', - ts: 'typescript', - swift: 'Swift', - kt: 'kotlin', - groovy: 'Groovy', - ps1: 'powershell', - dart: 'Dart', - scala: 'Scala' -}; +// 支持的语言列表 +const supportedLanguages = getAllSupportedLanguages(); -// 语言显示名称映射 -const LANGUAGE_NAMES: Record = { - auto: 'Auto', - text: 'Plain Text', - json: 'JSON', - py: 'Python', - html: 'HTML', - sql: 'SQL', - md: 'Markdown', - java: 'Java', - php: 'PHP', - css: 'CSS', - xml: 'XML', - cpp: 'C++', - rs: 'Rust', - cs: 'C#', - rb: 'Ruby', - sh: 'Shell', - yaml: 'YAML', - toml: 'TOML', - go: 'Go', - clj: 'Clojure', - ex: 'Elixir', - erl: 'Erlang', - js: 'JavaScript', - ts: 'TypeScript', - swift: 'Swift', - kt: 'Kotlin', - groovy: 'Groovy', - ps1: 'PowerShell', - dart: 'Dart', - scala: 'Scala' -}; +// 动态生成语言显示名称映射 +const languageNames = computed(() => { + const names: Record = { + auto: 'Auto', + text: 'Plain Text' + }; + + LANGUAGES.forEach(lang => { + names[lang.token] = lang.name; + }); + + return names; +}); + +// 动态生成语言别名映射 +const languageAliases = computed(() => { + const aliases: Record = { + auto: 'auto', + text: 'txt' + }; + + LANGUAGES.forEach(lang => { + // 使用语言名称的小写作为别名 + aliases[lang.token] = lang.name.toLowerCase(); + }); + + return aliases; +}); // 当前活动块的语言信息 const currentBlockLanguage = ref<{ name: SupportedLanguage; auto: boolean }>({ @@ -197,13 +162,13 @@ watch( // 过滤后的语言列表 const filteredLanguages = computed(() => { if (!searchQuery.value) { - return SUPPORTED_LANGUAGES; + return supportedLanguages; } const query = searchQuery.value.toLowerCase(); - return SUPPORTED_LANGUAGES.filter(langId => { - const name = LANGUAGE_NAMES[langId]; - const alias = LANGUAGE_ALIASES[langId]; + return supportedLanguages.filter(langId => { + const name = languageNames.value[langId]; + const alias = languageAliases.value[langId]; return langId.toLowerCase().includes(query) || (name && name.toLowerCase().includes(query)) || (alias && alias.toLowerCase().includes(query)); @@ -380,7 +345,7 @@ const scrollToCurrentLanguage = () => { :data-language="language" @click="selectLanguage(language)" > - {{ LANGUAGE_NAMES[language] || language }} + {{ languageNames[language] || language }} {{ language }} diff --git a/frontend/src/views/editor/extensions/codeblock/copyPaste.ts b/frontend/src/views/editor/extensions/codeblock/copyPaste.ts index 7263d28..7032bcd 100644 --- a/frontend/src/views/editor/extensions/codeblock/copyPaste.ts +++ b/frontend/src/views/editor/extensions/codeblock/copyPaste.ts @@ -6,36 +6,14 @@ import { EditorState, EditorSelection } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { Command } from "@codemirror/view"; -import { SUPPORTED_LANGUAGES } from "./types"; +import { LANGUAGES } from "./lang-parser/languages"; /** * 构建块分隔符正则表达式 */ -const languageTokensMatcher = SUPPORTED_LANGUAGES.join("|"); +const languageTokensMatcher = LANGUAGES.map(lang => lang.token).join("|"); const blockSeparatorRegex = new RegExp(`\\n∞∞∞(${languageTokensMatcher})(-a)?\\n`, "g"); -/** - * 降级复制方法 - 使用传统的 document.execCommand - */ -function fallbackCopyToClipboard(text: string): boolean { - try { - const textArea = document.createElement('textarea'); - textArea.value = text; - textArea.style.position = 'fixed'; - textArea.style.left = '-999999px'; - textArea.style.top = '-999999px'; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - const result = document.execCommand('copy'); - document.body.removeChild(textArea); - return result; - } catch (err) { - console.error('The downgrade replication method also failed:', err); - return false; - } -} - /** * 获取被复制的范围和内容 */ @@ -118,15 +96,9 @@ const copyCut = (view: EditorView, cut: boolean): boolean => { let { text, ranges } = copiedRange(view.state); // 将块分隔符替换为双换行符 text = text.replaceAll(blockSeparatorRegex, "\n\n"); - - // 使用现代剪贴板 API + if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(text).catch(err => { - fallbackCopyToClipboard(text); - }); - } else { - // 降级到传统方法 - fallbackCopyToClipboard(text); + navigator.clipboard.writeText(text); } if (cut && !view.state.readOnly) { diff --git a/frontend/src/views/editor/extensions/codeblock/decorations.ts b/frontend/src/views/editor/extensions/codeblock/decorations.ts index e809135..c122e4a 100644 --- a/frontend/src/views/editor/extensions/codeblock/decorations.ts +++ b/frontend/src/views/editor/extensions/codeblock/decorations.ts @@ -158,7 +158,6 @@ const blockLayer = layer({ markers.push(new RectangleMarker( idx++ % 2 == 0 ? "block-even" : "block-odd", 0, - // 参考 Heynote 的精确计算方式 fromCoordsTop - (view.documentTop - view.documentPadding.top) - 1 - 6, null, // 宽度在 CSS 中设置为 100% (toCoordsBottom - fromCoordsTop) + 15, diff --git a/frontend/src/views/editor/extensions/codeblock/formatCode.ts b/frontend/src/views/editor/extensions/codeblock/formatCode.ts index a923471..cc1f452 100644 --- a/frontend/src/views/editor/extensions/codeblock/formatCode.ts +++ b/frontend/src/views/editor/extensions/codeblock/formatCode.ts @@ -38,12 +38,16 @@ export const formatBlockContent = (view) => { const performFormat = async () => { let formattedContent try { - // 格式化代码 - const formatted = await prettier.format(content, { + // 构建格式化配置 + const formatOptions = { parser: language.prettier!.parser, plugins: language.prettier!.plugins, tabWidth: tabSize, - }) + ...language.prettier!.options, + } + + // 格式化代码 + const formatted = await prettier.format(content, formatOptions) // 计算新光标位置 const cursorOffset = cursorAtEdge diff --git a/frontend/src/views/editor/extensions/codeblock/index.ts b/frontend/src/views/editor/extensions/codeblock/index.ts index 767060e..2fe3546 100644 --- a/frontend/src/views/editor/extensions/codeblock/index.ts +++ b/frontend/src/views/editor/extensions/codeblock/index.ts @@ -22,8 +22,8 @@ import {getBlockSelectExtensions} from './selectAll'; import {getCopyPasteExtensions} from './copyPaste'; import {moveLineDown, moveLineUp} from './moveLines'; import {getCodeBlockLanguageExtension} from './lang-parser'; -import {createLanguageDetection} from './language-detection'; -import {EditorOptions, SupportedLanguage} from './types'; +import {createLanguageDetection} from './lang-detect'; +import {SupportedLanguage} from './types'; /** * 代码块扩展配置选项 @@ -127,7 +127,6 @@ export { type Block, type SupportedLanguage, type CreateBlockOptions, - SUPPORTED_LANGUAGES } from './types'; // 状态管理 @@ -187,10 +186,8 @@ export { export { getCodeBlockLanguageExtension, getLanguage, - getLanguageTokens, languageMapping, LanguageInfo, - LANGUAGES as PARSER_LANGUAGES } from './lang-parser'; // 语言检测 @@ -200,7 +197,7 @@ export { detectLanguages, levenshteinDistance, type LanguageDetectionResult -} from './language-detection'; +} from './lang-detect'; // 行号相关 export {getBlockLineFromPos, blockLineNumbers}; diff --git a/frontend/src/views/editor/extensions/codeblock/language-detection/autodetect.ts b/frontend/src/views/editor/extensions/codeblock/lang-detect/autodetect.ts similarity index 91% rename from frontend/src/views/editor/extensions/codeblock/language-detection/autodetect.ts rename to frontend/src/views/editor/extensions/codeblock/lang-detect/autodetect.ts index de44026..5f0bc0e 100644 --- a/frontend/src/views/editor/extensions/codeblock/language-detection/autodetect.ts +++ b/frontend/src/views/editor/extensions/codeblock/lang-detect/autodetect.ts @@ -61,18 +61,24 @@ const DEFAULT_CONFIG = { }; /** - * 支持的语言列表 + * 创建检测ID到语言token的映射 */ -const SUPPORTED_LANGUAGES = new Set([ - "json", "py", "html", "sql", "md", "java", "php", "css", "xml", - "cpp", "rs", "cs", "rb", "sh", "yaml", "toml", "go", "clj", - "ex", "erl", "js", "ts", "swift", "kt", "groovy", "ps1", "dart", "scala" -]); +function createDetectionMap(): Map { + const map = new Map(); + LANGUAGES.forEach(lang => { + if (lang.detectIds) { + lang.detectIds.forEach(detectId => { + map.set(detectId, lang.token); + }); + } + }); + return map; +} /** - * 语言标记映射表 + * 检测ID到语言token的映射表 */ -const LANGUAGE_MAP = new Map(LANGUAGES.map(lang => [lang.token, lang.token])); +const DETECTION_MAP = createDetectionMap(); // ===== 工具函数 ===== @@ -253,14 +259,16 @@ export function createLanguageDetection(config: LanguageDetectionConfig = {}): V try { const result = await worker.detectLanguage(content); - if (result.confidence >= finalConfig.confidenceThreshold && - result.language !== block.language.name && - SUPPORTED_LANGUAGES.has(result.language) && - LANGUAGE_MAP.has(result.language)) { + // 使用检测映射表将检测结果转换为我们支持的语言 + const mappedLanguage = DETECTION_MAP.get(result.language); + + if (mappedLanguage && + result.confidence >= finalConfig.confidenceThreshold && + mappedLanguage !== block.language.name) { // 只有在用户没有撤销操作时才更改语言 if (redoDepth(state) === 0) { - changeLanguageTo(state, this.view.dispatch, block, result.language, true); + changeLanguageTo(state, this.view.dispatch, block, mappedLanguage, true); } } diff --git a/frontend/src/views/editor/extensions/codeblock/language-detection/index.ts b/frontend/src/views/editor/extensions/codeblock/lang-detect/index.ts similarity index 100% rename from frontend/src/views/editor/extensions/codeblock/language-detection/index.ts rename to frontend/src/views/editor/extensions/codeblock/lang-detect/index.ts diff --git a/frontend/src/views/editor/extensions/codeblock/language-detection/levenshtein.ts b/frontend/src/views/editor/extensions/codeblock/lang-detect/levenshtein.ts similarity index 100% rename from frontend/src/views/editor/extensions/codeblock/language-detection/levenshtein.ts rename to frontend/src/views/editor/extensions/codeblock/lang-detect/levenshtein.ts diff --git a/frontend/src/views/editor/extensions/codeblock/lang-parser/index.ts b/frontend/src/views/editor/extensions/codeblock/lang-parser/index.ts index 40a9ba9..1fedf96 100644 --- a/frontend/src/views/editor/extensions/codeblock/lang-parser/index.ts +++ b/frontend/src/views/editor/extensions/codeblock/lang-parser/index.ts @@ -16,7 +16,6 @@ export { LANGUAGES, languageMapping, getLanguage, - getLanguageTokens } from './languages'; // 嵌套解析器 diff --git a/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts index 53335f4..a94d9d0 100644 --- a/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts +++ b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts @@ -28,6 +28,8 @@ import {groovy} from "@codemirror/legacy-modes/mode/groovy"; import {powerShell} from "@codemirror/legacy-modes/mode/powershell"; import {toml} from "@codemirror/legacy-modes/mode/toml"; import {elixir} from "codemirror-lang-elixir"; +import {dockerFile} from "@codemirror/legacy-modes/mode/dockerfile"; +import {lua} from "@codemirror/legacy-modes/mode/lua"; import {SupportedLanguage} from '../types'; import typescriptPlugin from "prettier/plugins/typescript" @@ -43,6 +45,7 @@ import javaPrettierPlugin from "@/common/prettier/plugins/java" import xmlPrettierPlugin from "@prettier/plugin-xml" import * as rustPrettierPlugin from "@/common/prettier/plugins/rust"; import * as shellPrettierPlugin from "@/common/prettier/plugins/shell"; +import * as dockerfilePrettierPlugin from "@/common/prettier/plugins/shell"; import tomlPrettierPlugin from "@/common/prettier/plugins/toml"; import clojurePrettierPlugin from "@cospaia/prettier-plugin-clojure"; import groovyPrettierPlugin from "@/common/prettier/plugins/groovy"; @@ -50,6 +53,7 @@ import scalaPrettierPlugin from "@/common/prettier/plugins/scala"; import clangPrettierPlugin from "@/common/prettier/plugins/clang"; import pythonPrettierPlugin from "@/common/prettier/plugins/python"; import dartPrettierPlugin from "@/common/prettier/plugins/dart"; +import luaPrettierPlugin from "@/common/prettier/plugins/lua"; import * as prettierPluginEstree from "prettier/plugins/estree"; /** @@ -60,9 +64,11 @@ export class LanguageInfo { public token: SupportedLanguage, public name: string, public parser: any, + public detectIds?: string[], public prettier?: { parser: string; plugins: any[]; + options?: Record; // 添加自定义配置选项 }) { } } @@ -72,97 +78,105 @@ export class LanguageInfo { */ export const LANGUAGES: LanguageInfo[] = [ new LanguageInfo("text", "Plain Text", null), - new LanguageInfo("json", "JSON", jsonLanguage.parser, { + new LanguageInfo("json", "JSON", jsonLanguage.parser, ["json"], { parser: "json", plugins: [babelPrettierPlugin, prettierPluginEstree] }), - new LanguageInfo("py", "Python", pythonLanguage.parser,{ + new LanguageInfo("py", "Python", pythonLanguage.parser, ["py"], { parser: "python", plugins: [pythonPrettierPlugin] }), - new LanguageInfo("html", "HTML", htmlLanguage.parser, { + new LanguageInfo("html", "HTML", htmlLanguage.parser, ["html"], { parser: "html", plugins: [htmlPrettierPlugin] }), - new LanguageInfo("sql", "SQL", StandardSQL.language.parser, { + new LanguageInfo("sql", "SQL", StandardSQL.language.parser, ["sql"], { parser: "sql", plugins: [sqlPrettierPlugin] }), - new LanguageInfo("md", "Markdown", markdownLanguage.parser, { + new LanguageInfo("md", "Markdown", markdownLanguage.parser, ["md"], { parser: "markdown", plugins: [markdownPrettierPlugin] }), - new LanguageInfo("java", "Java", javaLanguage.parser,{ + new LanguageInfo("java", "Java", javaLanguage.parser, ["java"], { parser: "java", plugins: [javaPrettierPlugin] }), - new LanguageInfo("php", "PHP", phpLanguage.configure({top: "Program"}).parser, { + new LanguageInfo("php", "PHP", phpLanguage.configure({top: "Program"}).parser, ["php"], { parser: "php", plugins: [phpPrettierPlugin] }), - new LanguageInfo("css", "CSS", cssLanguage.parser, { + new LanguageInfo("css", "CSS", cssLanguage.parser, ["css"], { parser: "css", plugins: [cssPrettierPlugin] }), - new LanguageInfo("xml", "XML", xmlLanguage.parser,{ + new LanguageInfo("xml", "XML", xmlLanguage.parser, ["xml"], { parser: "xml", plugins: [xmlPrettierPlugin] }), - new LanguageInfo("cpp", "C++", cppLanguage.parser,{ + new LanguageInfo("cpp", "C++", cppLanguage.parser, ["cpp", "c"], { parser: "clang", plugins: [clangPrettierPlugin] }), - new LanguageInfo("rs", "Rust", rustLanguage.parser,{ + new LanguageInfo("rs", "Rust", rustLanguage.parser, ["rs"], { parser: "jinx-rust", plugins: [rustPrettierPlugin] }), - new LanguageInfo("cs", "C#", StreamLanguage.define(csharp).parser), - new LanguageInfo("rb", "Ruby", StreamLanguage.define(ruby).parser), - new LanguageInfo("sh", "Shell", StreamLanguage.define(shell).parser,{ + new LanguageInfo("cs", "C#", StreamLanguage.define(csharp).parser, ["cs"]), + new LanguageInfo("rb", "Ruby", StreamLanguage.define(ruby).parser, ["rb"]), + new LanguageInfo("sh", "Shell", StreamLanguage.define(shell).parser, ["sh", "bat"], { parser: "sh", plugins: [shellPrettierPlugin] }), - new LanguageInfo("yaml", "YAML", yamlLanguage.parser, { + new LanguageInfo("yaml", "YAML", yamlLanguage.parser, ["yaml"], { parser: "yaml", plugins: [yamlPrettierPlugin] }), - new LanguageInfo("toml", "TOML", StreamLanguage.define(toml).parser,{ + new LanguageInfo("toml", "TOML", StreamLanguage.define(toml).parser, ["toml"], { parser: "toml", plugins: [tomlPrettierPlugin] }), - new LanguageInfo("go", "Go", StreamLanguage.define(go).parser, { + new LanguageInfo("go", "Go", StreamLanguage.define(go).parser, ["go"], { parser: "go-format", plugins: [goPrettierPlugin] }), - new LanguageInfo("clj", "Clojure", StreamLanguage.define(clojure).parser,{ + new LanguageInfo("clj", "Clojure", StreamLanguage.define(clojure).parser, ["clj"], { parser: "clojure", plugins: [clojurePrettierPlugin] }), - new LanguageInfo("ex", "Elixir", elixir().language.parser), - new LanguageInfo("erl", "Erlang", StreamLanguage.define(erlang).parser), - new LanguageInfo("js", "JavaScript", javascriptLanguage.parser, { + new LanguageInfo("ex", "Elixir", elixir().language.parser, ["ex"]), + new LanguageInfo("erl", "Erlang", StreamLanguage.define(erlang).parser, ["erl"]), + new LanguageInfo("js", "JavaScript", javascriptLanguage.parser, ["js"], { parser: "babel", plugins: [babelPrettierPlugin, prettierPluginEstree] }), - new LanguageInfo("ts", "TypeScript", typescriptLanguage.parser, { + new LanguageInfo("ts", "TypeScript", typescriptLanguage.parser, ["ts", "js"], { parser: "typescript", plugins: [typescriptPlugin, prettierPluginEstree] }), - new LanguageInfo("swift", "Swift", StreamLanguage.define(swift).parser), - new LanguageInfo("kt", "Kotlin", StreamLanguage.define(kotlin).parser), - new LanguageInfo("groovy", "Groovy", StreamLanguage.define(groovy).parser,{ + new LanguageInfo("swift", "Swift", StreamLanguage.define(swift).parser, ["swift"]), + new LanguageInfo("kt", "Kotlin", StreamLanguage.define(kotlin).parser, ["kt"]), + new LanguageInfo("groovy", "Groovy", StreamLanguage.define(groovy).parser, ["groovy"], { parser: "groovy", plugins: [groovyPrettierPlugin] }), - new LanguageInfo("ps1", "PowerShell", StreamLanguage.define(powerShell).parser), - new LanguageInfo("dart", "Dart", null,{ + new LanguageInfo("ps1", "PowerShell", StreamLanguage.define(powerShell).parser, ["ps1"]), + new LanguageInfo("dart", "Dart", null, ["dart"], { parser: "dart", plugins: [dartPrettierPlugin] }), - new LanguageInfo("scala", "Scala", StreamLanguage.define(scala).parser,{ + new LanguageInfo("scala", "Scala", StreamLanguage.define(scala).parser, ["scala"], { parser: "scala", plugins: [scalaPrettierPlugin] }), + new LanguageInfo("dockerfile", "Dockerfile", StreamLanguage.define(dockerFile).parser, ["dockerfile"], { + parser: "dockerfile", + plugins: [dockerfilePrettierPlugin] + }), + new LanguageInfo("lua", "Lua", StreamLanguage.define(lua).parser, ["lua"], { + parser: "lua", + plugins: [luaPrettierPlugin] + }), ]; /** @@ -180,8 +194,8 @@ export function getLanguage(token: SupportedLanguage): LanguageInfo | undefined } /** - * 获取所有语言的 token 列表 + * 获取完整的支持语言列表(包括 'auto') */ -export function getLanguageTokens(): SupportedLanguage[] { - return LANGUAGES.map(lang => lang.token); +export function getAllSupportedLanguages(): SupportedLanguage[] { + return ['auto', ...LANGUAGES.map(lang => lang.token)]; } \ No newline at end of file diff --git a/frontend/src/views/editor/extensions/codeblock/moveLines.ts b/frontend/src/views/editor/extensions/codeblock/moveLines.ts index 59faafa..66636c8 100644 --- a/frontend/src/views/editor/extensions/codeblock/moveLines.ts +++ b/frontend/src/views/editor/extensions/codeblock/moveLines.ts @@ -5,7 +5,7 @@ import { EditorSelection, SelectionRange } from "@codemirror/state"; import { blockState } from "./state"; -import { SUPPORTED_LANGUAGES } from "./types"; +import { LANGUAGES } from "./lang-parser/languages"; interface LineBlock { from: number; @@ -14,7 +14,7 @@ interface LineBlock { } // 创建语言标记的正则表达式 -const languageTokensMatcher = SUPPORTED_LANGUAGES.join("|"); +const languageTokensMatcher = LANGUAGES.map(lang => lang.token).join("|"); const tokenRegEx = new RegExp(`^∞∞∞(${languageTokensMatcher})(-a)?$`, "g"); /** diff --git a/frontend/src/views/editor/extensions/codeblock/parser.ts b/frontend/src/views/editor/extensions/codeblock/parser.ts index 0f7f059..7952386 100644 --- a/frontend/src/views/editor/extensions/codeblock/parser.ts +++ b/frontend/src/views/editor/extensions/codeblock/parser.ts @@ -6,17 +6,14 @@ import { EditorState } from '@codemirror/state'; import { syntaxTree, syntaxTreeAvailable } from '@codemirror/language'; import { Block as BlockNode, BlockDelimiter, BlockContent, BlockLanguage, Document } from './lang-parser/parser.terms.js'; import { - CodeBlock, SupportedLanguage, - SUPPORTED_LANGUAGES, DELIMITER_REGEX, DELIMITER_PREFIX, DELIMITER_SUFFIX, AUTO_DETECT_SUFFIX, - ParseOptions, - LanguageDetectionResult, Block } from './types'; +import { LANGUAGES } from './lang-parser/languages'; /** * 从语法树解析代码块 @@ -397,7 +394,7 @@ export function parseDelimiter(delimiterText: string): { language: SupportedLang const languageName = match[1]; const isAuto = match[2] === AUTO_DETECT_SUFFIX; - const validLanguage = SUPPORTED_LANGUAGES.includes(languageName as SupportedLanguage) + const validLanguage = LANGUAGES.some(lang => lang.token === languageName) ? languageName as SupportedLanguage : 'text'; diff --git a/frontend/src/views/editor/extensions/codeblock/types.ts b/frontend/src/views/editor/extensions/codeblock/types.ts index 22a2fb8..d25fb70 100644 --- a/frontend/src/views/editor/extensions/codeblock/types.ts +++ b/frontend/src/views/editor/extensions/codeblock/types.ts @@ -2,136 +2,102 @@ * Block 结构 */ export interface Block { - language: { - name: string; - auto: boolean; - }; - content: { - from: number; - to: number; - }; - delimiter: { - from: number; - to: number; - }; - range: { - from: number; - to: number; - }; + language: { + name: string; + auto: boolean; + }; + content: { + from: number; + to: number; + }; + delimiter: { + from: number; + to: number; + }; + range: { + from: number; + to: number; + }; } /** * 支持的语言类型 */ -export type SupportedLanguage = - | 'auto' // 自动检测 - | 'text' - | 'json' - | 'py' // Python - | 'html' - | 'sql' - | 'md' // Markdown - | 'java' - | 'php' - | 'css' - | 'xml' - | 'cpp' // C++ - | 'rs' // Rust - | 'cs' // C# - | 'rb' // Ruby - | 'sh' // Shell - | 'yaml' - | 'toml' - | 'go' - | 'clj' // Clojure - | 'ex' // Elixir - | 'erl' // Erlang - | 'js' // JavaScript - | 'ts' // TypeScript - | 'swift' - | 'kt' // Kotlin - | 'groovy' - | 'ps1' // PowerShell - | 'dart' - | 'scala'; - -/** - * 支持的语言列表 - */ -export const SUPPORTED_LANGUAGES: SupportedLanguage[] = [ - 'auto', - 'text', - 'json', - 'py', - 'html', - 'sql', - 'md', - 'java', - 'php', - 'css', - 'xml', - 'cpp', - 'rs', - 'cs', - 'rb', - 'sh', - 'yaml', - 'toml', - 'go', - 'clj', - 'ex', - 'erl', - 'js', - 'ts', - 'swift', - 'kt', - 'groovy', - 'ps1', - 'dart', - 'scala' -]; +export type SupportedLanguage = + | 'auto' // 自动检测 + | 'text' + | 'json' + | 'py' // Python + | 'html' + | 'sql' + | 'md' // Markdown + | 'java' + | 'php' + | 'css' + | 'xml' + | 'cpp' // C++ + | 'rs' // Rust + | 'cs' // C# + | 'rb' // Ruby + | 'sh' // Shell + | 'yaml' + | 'toml' + | 'go' + | 'clj' // Clojure + | 'ex' // Elixir + | 'erl' // Erlang + | 'js' // JavaScript + | 'ts' // TypeScript + | 'swift' + | 'kt' // Kotlin + | 'groovy' + | 'ps1' // PowerShell + | 'dart' + | 'scala' + | 'dockerfile' + | 'lua' /** * 创建块的选项 */ export interface CreateBlockOptions { - language?: SupportedLanguage; - auto?: boolean; - content?: string; + language?: SupportedLanguage; + auto?: boolean; + content?: string; } /** * 编辑器配置选项 */ export interface EditorOptions { - defaultBlockToken: string; - defaultBlockAutoDetect: boolean; + defaultBlockToken: string; + defaultBlockAutoDetect: boolean; } // 语言信息接口 export interface LanguageInfo { - name: SupportedLanguage; - auto: boolean; // 是否自动检测语言 + name: SupportedLanguage; + auto: boolean; // 是否自动检测语言 } // 位置范围接口 export interface Range { - from: number; - to: number; + from: number; + to: number; } // 代码块核心接口 export interface CodeBlock { - language: LanguageInfo; - content: Range; // 内容区域 - delimiter: Range; // 分隔符区域 - range: Range; // 整个块区域(包括分隔符和内容) + language: LanguageInfo; + content: Range; // 内容区域 + delimiter: Range; // 分隔符区域 + range: Range; // 整个块区域(包括分隔符和内容) } // 代码块解析选项 export interface ParseOptions { - fallbackLanguage?: SupportedLanguage; - enableAutoDetection?: boolean; + fallbackLanguage?: SupportedLanguage; + enableAutoDetection?: boolean; } // 分隔符格式常量 @@ -141,23 +107,23 @@ export const DELIMITER_SUFFIX = '\n'; export const AUTO_DETECT_SUFFIX = '-a'; // 代码块操作类型 -export type BlockOperation = - | 'insert-after' - | 'insert-before' - | 'delete' - | 'move-up' - | 'move-down' - | 'change-language'; +export type BlockOperation = + | 'insert-after' + | 'insert-before' + | 'delete' + | 'move-up' + | 'move-down' + | 'change-language'; // 代码块状态更新事件 export interface BlockStateUpdate { - blocks: CodeBlock[]; - activeBlockIndex: number; - operation?: BlockOperation; + blocks: CodeBlock[]; + activeBlockIndex: number; + operation?: BlockOperation; } // 语言检测结果 export interface LanguageDetectionResult { - language: SupportedLanguage; - confidence: number; + language: SupportedLanguage; + confidence: number; } \ No newline at end of file