diff --git a/frontend/src/common/prettier/plugins/python/index.ts b/frontend/src/common/prettier/plugins/python/index.ts new file mode 100644 index 0000000..8513db2 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/index.ts @@ -0,0 +1,146 @@ +/** + * Prettier Plugin for Python formatting using Ruff WebAssembly + * + * This plugin provides support for formatting Python files using the Ruff WASM implementation. + * Ruff is a fast Python linter and code formatter written in Rust. + */ +import type { Plugin, Parser, Printer } from 'prettier'; + +// Import the Ruff WASM module +import ruffInit, { format, type Config } from './ruff_fmt_vite.js'; + +const parserName = 'python'; + +// Language configuration +const languages = [ + { + name: 'Python', + aliases: ['python', 'py'], + parsers: [parserName], + extensions: ['.py', '.pyi', '.pyw'], + aceMode: 'python', + tmScope: 'source.python', + linguistLanguageId: 303, + vscodeLanguageIds: ['python'] + } +]; + +// Parser configuration +const pythonParser: Parser = { + astFormat: parserName, + parse: (text: string) => text, + locStart: () => 0, + locEnd: (node: string) => node.length, +}; + +// Initialize Ruff WASM module +let initPromise: Promise | null = null; +let isInitialized = false; + +function initRuff(): Promise { + if (initPromise) { + return initPromise; + } + + initPromise = (async () => { + if (!isInitialized) { + await ruffInit(); + isInitialized = true; + } + })(); + + return initPromise; +} + +// Printer configuration +const pythonPrinter: Printer = { + print: (path, options) => { + try { + if (!isInitialized) { + console.warn('Ruff 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 = getRuffConfig(options); + + // Format using Ruff (synchronous call) + const formatted = format(text, undefined, config); + + return formatted.trim(); + } catch (error) { + console.warn('Ruff formatting failed:', error); + // Return original text if formatting fails + return (path as any).getValue ? (path as any).getValue() : path.node; + } + }, +}; + +// Helper function to create Ruff config from Prettier options +function getRuffConfig(options: any): Config { + const config: Config = {}; + + // Map Prettier options to Ruff 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 ? 'single' : 'double'; + } + + if (options.ruffMagicTrailingComma !== undefined) { + config.magic_trailing_comma = options.ruffMagicTrailingComma; + } + + return config; +} + +// Plugin options +const options = { + ruffMagicTrailingComma: { + since: '0.0.1', + category: 'Format' as const, + type: 'choice' as const, + default: 'respect', + description: 'How to handle trailing commas in collections', + choices: [ + { value: 'respect', description: 'Respect existing trailing commas' }, + { value: 'ignore', description: 'Remove trailing commas' } + ] + } +}; + +// Plugin object +const pythonPlugin: Plugin = { + languages, + parsers: { + [parserName]: pythonParser, + }, + printers: { + [parserName]: pythonPrinter, + }, + options, +}; + +// Initialize WASM module when plugin loads +initRuff().catch(error => { + console.warn('Failed to initialize Ruff WASM module:', error); +}); + +export default pythonPlugin; +export { languages }; +export const parsers = pythonPlugin.parsers; +export const printers = pythonPlugin.printers; diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt.d.ts b/frontend/src/common/prettier/plugins/python/ruff_fmt.d.ts new file mode 100644 index 0000000..f04cb57 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt.d.ts @@ -0,0 +1,45 @@ +/* tslint:disable */ +/* eslint-disable */ +export function format(input: string, path?: string, config?: Config): string; + +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"; +} + + +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_add_to_stack_pointer: (a: number) => number; + readonly __wbindgen_export_2: (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/python/ruff_fmt.js b/frontend/src/common/prettier/plugins/python/ruff_fmt.js new file mode 100644 index 0000000..1b8772d --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt.js @@ -0,0 +1,460 @@ +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 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 isLikeNone(x) { + return x === undefined || x === null; +} + +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 | undefined} [path] + * @param {Config | undefined} [config] + * @returns {string} + */ +export function format(input, path, 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; + var ptr1 = isLikeNone(path) ? 0 : passStringToWasm0(path, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + var 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_2(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_6e1d53ff183194fc = function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_entries_ce82e236f8300a53 = function(arg0) { + const ret = Object.entries(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_68aa371864aa301a = function(arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_getwithrefkey_1dc361bd10053bfe = function(arg0, arg1) { + const ret = getObject(arg0)[getObject(arg1)]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_ArrayBuffer_435fcead703e2827 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_Uint8Array_9b67296cab48238f = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_isSafeInteger_4de146aa53f6e470 = function(arg0) { + const ret = Number.isSafeInteger(getObject(arg0)); + return ret; + }; + imports.wbg.__wbg_length_2e63ba34c4121df5 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_length_e74df4881604f1d9 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_new_23362fa370a0a372 = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_7b70226104a82921 = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbindgen_as_number = function(arg0) { + const ret = +getObject(arg0); + return ret; + }; + 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_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_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('ruff_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/python/ruff_fmt_bg.wasm b/frontend/src/common/prettier/plugins/python/ruff_fmt_bg.wasm new file mode 100644 index 0000000..bcd0a5c Binary files /dev/null and b/frontend/src/common/prettier/plugins/python/ruff_fmt_bg.wasm differ diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt_bg.wasm.d.ts b/frontend/src/common/prettier/plugins/python/ruff_fmt_bg.wasm.d.ts new file mode 100644 index 0000000..ef4a4d1 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/ruff_fmt_bg.wasm.d.ts @@ -0,0 +1,8 @@ +/* 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_add_to_stack_pointer: (a: number) => number; +export const __wbindgen_export_2: (a: number, b: number, c: number) => void; diff --git a/frontend/src/common/prettier/plugins/python/ruff_fmt_node.js b/frontend/src/common/prettier/plugins/python/ruff_fmt_node.js new file mode 100644 index 0000000..b4ec4fb --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/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_vite.js b/frontend/src/common/prettier/plugins/python/ruff_fmt_vite.js new file mode 100644 index 0000000..0a425c6 --- /dev/null +++ b/frontend/src/common/prettier/plugins/python/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/views/editor/extensions/codeblock/lang-parser/languages.ts b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts index d8fa95f..d2e49a4 100644 --- a/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts +++ b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts @@ -48,6 +48,7 @@ import clojurePrettierPlugin from "@cospaia/prettier-plugin-clojure"; import groovyPrettierPlugin from "@/common/prettier/plugins/groovy"; import scalaPrettierPlugin from "@/common/prettier/plugins/scala"; import clangPrettierPlugin from "@/common/prettier/plugins/clang"; +import pythonPrettierPlugin from "@/common/prettier/plugins/python"; import * as prettierPluginEstree from "prettier/plugins/estree"; /** @@ -74,7 +75,10 @@ export const LANGUAGES: LanguageInfo[] = [ parser: "json", plugins: [babelPrettierPlugin, prettierPluginEstree] }), - new LanguageInfo("py", "Python", pythonLanguage.parser), + new LanguageInfo("py", "Python", pythonLanguage.parser,{ + parser: "python", + plugins: [pythonPrettierPlugin] + }), new LanguageInfo("html", "HTML", htmlLanguage.parser, { parser: "html", plugins: [htmlPrettierPlugin]