Add code formatting button, Python/Haskell support

This commit is contained in:
Radon Rosborough 2020-07-19 17:40:54 -06:00
parent 3fd5bd32d8
commit 68bb853fa0
7 changed files with 119 additions and 21 deletions

View File

@ -35,6 +35,12 @@ export class Session {
writer: rpc.StreamMessageWriter; writer: rpc.StreamMessageWriter;
} | null = null; } | null = null;
daemon: { proc: ChildProcess } | null = null; daemon: { proc: ChildProcess } | null = null;
formatter: {
proc: ChildProcess;
live: boolean;
input: string;
output: string;
} | null = null;
get homedir() { get homedir() {
return `/tmp/riju/${this.uuid}`; return `/tmp/riju/${this.uuid}`;
@ -224,6 +230,13 @@ export class Session {
} }
await this.runCode(msg.code); await this.runCode(msg.code);
break; break;
case "formatCode":
if (typeof msg.code !== "string") {
this.logBadMessage(msg);
break;
}
await this.formatCode(msg.code);
break;
case "lspInput": case "lspInput":
if (typeof msg.input !== "object" || !msg) { if (typeof msg.input !== "object" || !msg) {
this.logBadMessage(msg); this.logBadMessage(msg);
@ -257,12 +270,11 @@ export class Session {
compile, compile,
run, run,
template, template,
hacks,
} = this.config; } = this.config;
if (this.term) { if (this.term) {
const pid = this.term.pty.pid; const pid = this.term.pty.pid;
const args = this.privilegedSpawn( const args = this.privilegedSpawn(
bash(`kill -SIGTERM ${pid}; sleep 3; kill -SIGKILL ${pid}`) bash(`kill -SIGTERM ${pid}; sleep 1; kill -SIGKILL ${pid}`)
); );
spawn(args[0], args.slice(1)); spawn(args[0], args.slice(1));
// Signal to terminalOutput message generator using closure. // Signal to terminalOutput message generator using closure.
@ -304,18 +316,6 @@ export class Session {
]), ]),
{ input: code } { input: code }
); );
if (hacks && hacks.includes("ghci-config") && run) {
if (code) {
await this.run(
this.privilegedSpawn(["sh", "-c", `cat > ${this.homedir}/.ghci`]),
{ input: ":load Main\nmain\n" }
);
} else {
await this.run(
this.privilegedSpawn(["rm", "-f", `${this.homedir}/.ghci`])
);
}
}
const termArgs = this.privilegedSpawn(bash(cmdline)); const termArgs = this.privilegedSpawn(bash(cmdline));
const term = { const term = {
pty: pty.spawn(termArgs[0], termArgs.slice(1), { pty: pty.spawn(termArgs[0], termArgs.slice(1), {
@ -338,6 +338,66 @@ export class Session {
} }
}; };
formatCode = async (code: string) => {
if (!this.config.format) {
this.log("formatCode ignored because format is null");
return;
}
if (this.formatter) {
const pid = this.formatter.proc.pid;
const args = this.privilegedSpawn(
bash(`kill -SIGTERM ${pid}; sleep 1; kill -SIGKILL ${pid}`)
);
spawn(args[0], args.slice(1));
this.formatter.live = false;
this.formatter = null;
}
const args = this.privilegedSpawn(bash(this.config.format));
const formatter = {
proc: spawn(args[0], args.slice(1)),
live: true,
input: code,
output: "",
};
formatter.proc.stdout!.on("data", (data) => {
if (formatter.live) {
formatter.output += data.toString("utf8");
}
});
formatter.proc.stderr!.on("data", (data) => {
if (formatter.live) {
this.send({
event: "serviceLog",
service: "formatter",
output: data.toString("utf8"),
});
}
});
formatter.proc.on("exit", (code, signal) => {
if (code === 0) {
this.send({
event: "formattedCode",
code: formatter.output,
originalCode: formatter.input,
});
} else {
this.send({
event: "serviceFailed",
service: "formatter",
error: `Exited with status ${signal || code}`,
});
}
});
formatter.proc.on("error", (err) =>
this.send({
event: "serviceFailed",
service: "formatter",
error: `${err}`,
})
);
this.formatter = formatter;
};
teardown = async () => { teardown = async () => {
try { try {
if (this.tearingDown) { if (this.tearingDown) {

View File

@ -11,6 +11,7 @@ export interface LangConfig {
createEmpty?: string; createEmpty?: string;
compile?: string; compile?: string;
run: string; run: string;
format?: string;
pkg?: { pkg?: {
install: string; install: string;
uninstall?: string; uninstall?: string;
@ -24,7 +25,6 @@ export interface LangConfig {
lspConfig?: any; lspConfig?: any;
lspLang?: string; lspLang?: string;
template: string; template: string;
hacks?: "ghci-config"[];
test?: { test?: {
ensure?: string; ensure?: string;
}; };
@ -764,9 +764,10 @@ function main(): void {
haskell: { haskell: {
aliases: ["ghc", "ghci", "hs"], aliases: ["ghc", "ghci", "hs"],
name: "Haskell", name: "Haskell",
repl: "ghci", repl: "rm -f .ghci && ghci",
main: "Main.hs", main: "Main.hs",
run: "ghci", run: "(echo ':load Main' && echo 'main') > .ghci && ghci",
format: "brittany Main.hs",
lspSetup: "cp /opt/haskell/hie.yaml hie.yaml", lspSetup: "cp /opt/haskell/hie.yaml hie.yaml",
lsp: "HIE_HOOGLE_DATABASE=/opt/haskell/hoogle.hoo hie --lsp", lsp: "HIE_HOOGLE_DATABASE=/opt/haskell/hoogle.hoo hie --lsp",
lspInit: { lspInit: {
@ -1337,6 +1338,7 @@ main = do
repl: "python3 -u", repl: "python3 -u",
main: "main.py", main: "main.py",
run: "python3 -u -i main.py", run: "python3 -u -i main.py",
format: "cat main.py | black -",
pkg: { pkg: {
install: "pip3 install --user NAME", install: "pip3 install --user NAME",
uninstall: "pip3 uninstall NAME", uninstall: "pip3 uninstall NAME",
@ -1350,7 +1352,7 @@ main = do
}, },
}, },
}, },
template: `print("Hello, world!") template: `print('Hello, world!')
`, `,
}, },
قلب: { قلب: {

View File

@ -16,6 +16,7 @@
<div id="editor" class="column"></div> <div id="editor" class="column"></div>
<div id="terminal" class="column"></div> <div id="terminal" class="column"></div>
<button type="button" class="btn btn-success" id="runButton">Run</button> <button type="button" class="btn btn-success" id="runButton">Run</button>
<button type="button" class="btn btn-info" id="formatButton">Prettify</button>
<a href="/" class="btn btn-secondary" id="backButton">Switch to a different language</a> <a href="/" class="btn btn-secondary" id="backButton">Switch to a different language</a>
</div> </div>
<script> <script>

View File

@ -25,6 +25,7 @@ interface RijuConfig {
id: string; id: string;
monacoLang?: string; monacoLang?: string;
main: string; main: string;
format?: string;
lspDisableDynamicRegistration?: boolean; lspDisableDynamicRegistration?: boolean;
lspInit?: any; lspInit?: any;
lspConfig?: any; lspConfig?: any;
@ -133,7 +134,7 @@ async function main() {
function sendMessage(message: any) { function sendMessage(message: any) {
if (DEBUG) { if (DEBUG) {
console.log("SEND", message); console.log("SEND:", message);
} }
if (socket) { if (socket) {
socket.send(JSON.stringify(message)); socket.send(JSON.stringify(message));
@ -165,8 +166,7 @@ async function main() {
DEBUG && DEBUG &&
message && message &&
message.event !== "lspOutput" && message.event !== "lspOutput" &&
message.event !== "lspLog" && message.event !== "serviceLog"
message.event !== "daemonLog"
) { ) {
console.log("RECEIVE:", message); console.log("RECEIVE:", message);
} }
@ -184,6 +184,18 @@ async function main() {
} }
term.write(message.output); term.write(message.output);
return; return;
case "formattedCode":
if (
typeof message.code !== "string" ||
typeof message.originalCode !== "string"
) {
console.error("Unexpected message from server:", message);
return;
}
if (editor.getValue() === message.originalCode) {
editor.setValue(message.code);
}
return;
case "lspStarted": case "lspStarted":
if (typeof message.root !== "string") { if (typeof message.root !== "string") {
console.error("Unexpected message from server:", message); console.error("Unexpected message from server:", message);
@ -308,6 +320,12 @@ async function main() {
document.getElementById("runButton")!.addEventListener("click", () => { document.getElementById("runButton")!.addEventListener("click", () => {
sendMessage({ event: "runCode", code: editor.getValue() }); sendMessage({ event: "runCode", code: editor.getValue() });
}); });
if (config.format) {
document.getElementById("formatButton")!.classList.add("visible");
document.getElementById("formatButton")!.addEventListener("click", () => {
sendMessage({ event: "formatCode", code: editor.getValue() });
});
}
} }
main().catch(console.error); main().catch(console.error);

View File

@ -27,6 +27,17 @@ body {
right: calc(50% + 25px); right: calc(50% + 25px);
} }
#formatButton {
position: absolute;
bottom: 25px;
right: calc(50% + 25px);
visibility: hidden;
}
#formatButton.visible {
visibility: visible;
}
#backButton { #backButton {
position: absolute; position: absolute;
left: 25px; left: 25px;

View File

@ -82,6 +82,9 @@ npm install -g pug-cli
# PureScript # PureScript
npm install -g purescript spago npm install -g purescript spago
# Python
pip3 install black
# ReasonML # ReasonML
npm install -g bs-platform npm install -g bs-platform

View File

@ -121,6 +121,9 @@ tar -xf linux-x86_64-static.tar.gz
mv stack-*-linux-x86_64-static/stack /usr/bin/stack mv stack-*-linux-x86_64-static/stack /usr/bin/stack
rm -rf stack-*-linux-x86_64-static linux-x86_64-static.tar.gz rm -rf stack-*-linux-x86_64-static linux-x86_64-static.tar.gz
wget "https://drive.google.com/uc?export=download&id=1MpozlNLmWeUaQuT-5t6gyE3Yv56gUbea" -O /usr/local/bin/brittany
chmod +x /usr/local/bin/brittany
mkdir -p /opt/haskell mkdir -p /opt/haskell
gdown "https://drive.google.com/uc?export=download&id=1GPoR_ja4ns16KCamRgwB-JVag4HK0igz" /usr/bin/hie gdown "https://drive.google.com/uc?export=download&id=1GPoR_ja4ns16KCamRgwB-JVag4HK0igz" /usr/bin/hie
gdown "https://drive.google.com/uc?export=download&id=1qSxj8JjAeetAmNjUGayX0RBARgr5R4Ij" /opt/haskell/hoogle.hoo gdown "https://drive.google.com/uc?export=download&id=1qSxj8JjAeetAmNjUGayX0RBARgr5R4Ij" /opt/haskell/hoogle.hoo