diff --git a/backend/api.js b/backend/api.js index f0d78a4..af90c2c 100644 --- a/backend/api.js +++ b/backend/api.js @@ -304,7 +304,7 @@ export class Session { cmdline = `echo '${name} has no REPL, press Run to see it in action'`; } if (code === undefined) { - code = createEmpty !== undefined ? createEmpty : template; + code = createEmpty !== undefined ? createEmpty : template + "\n"; } if (code && suffix) { code += suffix; diff --git a/backend/langs.js b/backend/langs.js index 510d42f..8e923e1 100644 --- a/backend/langs.js +++ b/backend/langs.js @@ -9,6 +9,22 @@ import { log } from "./util.js"; // populated at runtime and updated asynchronously. export let langs = {}; +// Correct whitespace problems in a language configuration, +// destructively. Return the fixed configuration. +// +// This basically removes leading and trailing whitespace from all +// values in the configuration recursively. +function fixupLangConfig(langConfig) { + if (typeof langConfig === "string") { + return langConfig.trim(); + } else if (typeof langConfig === "object") { + for (const key in langConfig) { + langConfig[key] = fixupLangConfig(langConfig[key]); + } + } + return langConfig; +} + // Read languages from JSON files in /opt/riju/langs, and update the // global langs variable in this module. Never throw an error. If // there is a problem then just leave the languages as they previously @@ -21,8 +37,8 @@ async function readLangsFromDisk() { continue; } const id = path.parse(filename).name; - const langConfig = JSON.parse( - await fs.readFile(`/opt/riju/langs/${filename}`, "utf-8") + const langConfig = fixupLangConfig( + JSON.parse(await fs.readFile(`/opt/riju/langs/${filename}`, "utf-8")) ); if (langConfig.id !== id) { log.error( diff --git a/backend/test-runner.js b/backend/test-runner.js index 9e8882b..cd570a9 100644 --- a/backend/test-runner.js +++ b/backend/test-runner.js @@ -214,7 +214,7 @@ class Test { }; testRun = async () => { const pattern = this.config.hello || "Hello, world!"; - this.send({ event: "runCode", code: this.config.template }); + this.send({ event: "runCode", code: this.config.template + "\n" }); if (this.config.helloInput !== undefined) { sendInput(this.send, this.config.helloInput); } @@ -229,7 +229,7 @@ class Test { testRunRepl = async () => { const input = this.config.runReplInput || this.config.input || "123 * 234"; const output = this.config.runReplOutput || this.config.output || "28782"; - this.send({ event: "runCode", code: this.config.template }); + this.send({ event: "runCode", code: this.config.template + "\n" }); sendInput(this.send, input); await this.waitForOutput(output); }; @@ -238,10 +238,7 @@ class Test { const after = this.config.scope.after; const input = this.config.scope.input || "x"; const output = this.config.scope.output || "28782"; - let allCode = this.config.template; - if (!allCode.endsWith("\n")) { - allCode += "\n"; - } + let allCode = this.config.template + "\n"; if (after) { allCode = allCode.replace(after + "\n", after + "\n" + code + "\n"); } else { @@ -253,7 +250,7 @@ class Test { }; testFormat = async () => { const input = this.config.format.input; - const output = this.config.format.output || this.config.template; + const output = (this.config.format.output || this.config.template) + "\n"; this.send({ event: "formatCode", code: input }); const result = await this.wait("formatter response", (msg) => { if (msg.event === "formattedCode") { @@ -265,16 +262,14 @@ class Test { } }; testLsp = async () => { + const template = this.config.template + "\n"; const insertedCode = this.config.lsp.code; const after = this.config.lsp.after; const item = this.config.lsp.item; const idx = after - ? this.config.template.indexOf(after) + after.length - : this.config.template.length; - const code = - this.config.template.slice(0, idx) + - insertedCode + - this.config.template.slice(idx); + ? template.indexOf(after) + after.length + : template.length; + const code = template.slice(0, idx) + insertedCode + template.slice(idx); const root = await this.wait("lspStarted message", (msg) => { if (msg.event === "lspStarted") { return msg.root; @@ -538,7 +533,7 @@ class Test { jsonrpc: "2.0", id: msg.output.id, result: Array(msg.output.params.items.length).fill( - this.config.lsp.config == undefined + this.config.lsp.config !== undefined ? this.config.lsp.config : {} ), @@ -560,11 +555,6 @@ class Test { function lint(lang) { const config = langs[lang]; - if (!config.template.endsWith("\n")) { - throw new Error("template is missing a trailing newline"); - } - // These can be removed when the types are adjusted to make these - // situations impossible. if ( config.format && !config.format.input && diff --git a/frontend/src/app.js b/frontend/src/app.js index aa70f60..c899bb7 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -289,7 +289,7 @@ async function main() { scrollbar: { verticalScrollbarSize: 0 }, }); window.addEventListener("resize", () => editor.layout()); - editor.getModel().setValue(config.template); + editor.getModel().setValue(config.template + "\n"); monaco.editor.setModelLanguage( editor.getModel(), config.monacoLang || "plaintext" diff --git a/langs/python.yaml b/langs/python.yaml index 15e6f67..92784d3 100644 --- a/langs/python.yaml +++ b/langs/python.yaml @@ -20,7 +20,7 @@ install: chmod +x "${pkg}/opt/mspyls/Microsoft.Python.LanguageServer" ln -s "/opt/mspyls/Microsoft.Python.LanguageServer" "${pkg}/usr/local/bin/Microsoft.Python.LanguageServer" -repl: >- +repl: | python3 -u main: "main.py" template: | @@ -31,7 +31,7 @@ scope: x = 123 * 234 format: - run: >- + run: | black - input: | print('Hello, world!') @@ -39,7 +39,7 @@ format: pkg: install: "pip3 install --user NAME" uninstall: "pip3 uninstall NAME" - search: >- + search: | python3 -c 'import json; from xmlrpc import client; print(json.dumps(client.ServerProxy("https://pypi.org/pypi").search({"name": "NAME"})))' | jq -r 'map(.name) | .[]' lsp: diff --git a/tools/generate-build-script.js b/tools/generate-build-script.js index e1cac0d..7d4479f 100644 --- a/tools/generate-build-script.js +++ b/tools/generate-build-script.js @@ -39,7 +39,7 @@ cat < "\${pkg}/DEBIAN/control" ${debianControlData} EOF`); for (const part of manual || []) { - parts.push(part.trim()); + parts.push(part); } return parts.join("\n\n"); }