<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>preval</title> <link href="/favicon.png" rel="icon" type="image/png" /> <style> html, body, main { font-family: "Courier New"; height: 100%; margin: 0; padding: 0; width: 100%; } main { display: flex; } textarea { background-color: black; box-sizing: border-box; color: white; font-family: inherit; resize: none; } textarea, code { flex-basis: 50%; font-size: 18px; padding: .5em; } </style> </head> <body> <main> <textarea placeholder="Enter Ruby code..."></textarea> <code></code> </main> <script> (() => { const textarea = document.querySelector("textarea"); const code = document.querySelector("code"); const fetchCode = () => { var xhr = new XMLHttpRequest(); xhr.open("POST", "/", true); xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { code.innerText = xhr.status === 200 ? xhr.responseText : ""; } }; xhr.send(textarea.value); }; let timeout = 0; textarea.addEventListener("input", () => { clearTimeout(timeout); timeout = setTimeout(fetchCode, 300); }); const handleTab = event => { event.preventDefault(); const { selectionStart, selectionEnd, value } = textarea; const preSpace = value.substring(0, selectionStart); const postSpace = value.substring(selectionEnd); textarea.value = `${preSpace} ${postSpace}`; textarea.selectionStart = textarea.selectionEnd = selectionStart + 2; }; const indentLine = line => ` ${line}`; const dedentLine = line => { if (line.startsWith(" ")) { return line.slice(2); } if (line.startsWith(" ")) { return line.slice(1); } return line; }; const handleDent = event => { event.preventDefault(); const { selectionStart, selectionEnd, value } = textarea; const lines = value.split("\n"); let currentStart = selectionStart; let currentEnd = selectionEnd; let startLine = null; let endLine = null; for (let lineIdx = 0; lineIdx < lines.length; lineIdx += 1) { currentStart -= (lines[lineIdx].length + 1); if (startLine === null && currentStart < 0) { startLine = lineIdx; } currentEnd -= (lines[lineIdx].length + 1); if (endLine === null && currentEnd < 0) { endLine = lineIdx; } if (currentStart < 0 && currentEnd < 0) { break; } } const nextLines = ( lines.slice(0, startLine) .concat(lines.slice(startLine, endLine + 1).map( event.key === "]" ? indentLine : dedentLine )) .concat(lines.slice(endLine + 1)) ); textarea.value = nextLines.join("\n"); const buffer = nextLines.slice(0, startLine + 1).join("\n").length; textarea.selectionStart = textarea.selectionEnd = ( buffer + currentStart + 1 ); }; textarea.addEventListener("keydown", event => { if (event.key === "Tab") { handleTab(event); } else if (["[", "]"].includes(event.key) && event.metaKey) { handleDent(event); } }); })(); </script> </body> </html>