github.com/coyove/nj@v0.0.0-20221110084952-c7f8db1065c3/playground.html (about) 1 <!doctype html> 2 3 <title>Playground</title> 4 <meta charset="utf-8"/> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 7 <style> 8 * { 9 box-sizing: border-box; 10 font-size: 14px; 11 } 12 html, body { 13 height: 100%; 14 margin: 0; 15 } 16 #code { 17 height: 100%; 18 width: 100%; 19 border: none; 20 background-color: transparent; 21 resize: none; 22 font-family: Lucida Console, Monaco, Monospace; 23 } 24 .CodeMirror { 25 height: 100% !important; 26 background-color: #ffd !important; 27 font-family: Lucida Console, Monaco, Monospace !important; 28 } 29 #input { 30 position:absolute; 31 left:0; 32 top:32px; 33 width:100%; 34 bottom:33%; 35 } 36 #output { 37 position: absolute; 38 width: 100%; 39 top: 67%; 40 bottom: 0; 41 overflow-y: scroll; 42 } 43 #output div { 44 white-space: pre-wrap; 45 word-break: break-all; 46 padding: 0.2em 0.5em; 47 } 48 #output div.title { 49 font-size: 90%; 50 background: #eee; 51 } 52 #output div.content { 53 font-family: Lucida Console, Monaco, Monospace; 54 padding: 0.5em; 55 } 56 </style> 57 58 <body> 59 <div style="position:relative;height:100%"> 60 <div style="line-height: 32px;height:32px;padding:0 0.5em"> 61 <button onclick="run()">Run (F9)</button> 62 <select id='output_fields'> 63 <option value="all">Return all</option> 64 <option value="stdout">Stdout only</option> 65 <option value="result">Results only</option> 66 </select> 67 <span style="float:right"> 68 <select id='cm_cdn' onchange="localStorage.setItem('cdn',this.value);location.reload()"> 69 <option value="-">Plain editor</option> 70 <option value="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.0">CodeMirror (cloudflare)</option> 71 <option value="https://cdn.bootcdn.net/ajax/libs/codemirror/5.65.0">CodeMirror (bootcdn)</option> 72 </select> 73 </span> 74 </div> 75 <div id="input"> 76 <div style="border:solid 1px #aaa;border-width:1px 0 1px 0;height:100%;background: #ffd;"> 77 <textarea id="code">__CODE__</textarea> 78 </div> 79 </div> 80 <div id="output"></div> 81 </div> 82 </body> 83 84 <script> 85 var output = document.getElementById("output"), editor = document.getElementById('code'); 86 async function loadEditor(textarea, prefix) { 87 prefix = prefix || localStorage.getItem('cdn') || "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.0" 88 document.getElementById('cm_cdn').value = prefix; 89 if (prefix == "-") { 90 loadPlainEditor(editor); 91 return; 92 } 93 94 if (window.EDITOR) return; 95 var createScript = function(src, f) { 96 return new Promise(function(resolve) { 97 var s = document.createElement("script") 98 s.onload = resolve; s.src = src; 99 document.body.appendChild(s); 100 }) 101 }, createCSS = function(src, f) { 102 return new Promise(function(resolve) { 103 var s = document.createElement("link") 104 s.onload = resolve; s.href = src; s.rel = 'stylesheet'; s.type = 'text/css'; s.media = 'all'; 105 document.body.appendChild(s); 106 }) 107 }; 108 109 await createScript(prefix + "/codemirror.min.js"); 110 await createScript(prefix + "/mode/lua/lua.min.js"); 111 await createScript(prefix + "/addon/hint/show-hint.min.js"); 112 await createCSS(prefix + "/codemirror.min.css"); 113 await createCSS(prefix + "/addon/hint/show-hint.min.css"); 114 115 const controlKeys = [ 116 8, 9, 13, 16, 17, 18, 19, 20, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46, 91, 92, 93, 107, 109, 110, 111, 112, 117 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 144, 145, 186, 187, 188, 189, 190, 191, 192, 220, 222, 118 ]; 119 const keywords = [ 120 "and", "or", "local", "break", "continue", "else", "function", "lambda", "if", "elseif", 121 "then", "end", "not", "return", "for", "while", "repeat", "until", "do", "in", "goto", "is", 122 ] 123 const names = [__NAMES__]; 124 CodeMirror.hint.lua = function(cm) { 125 const cursor = cm.getCursor(); 126 const token = cm.getTokenAt(cursor); 127 const word = (token.string.match(/\w+$/) || [token.string])[0]; 128 const candidates = names.concat(word == token.string ? keywords : []); 129 const res = {from: cursor, to: cursor, list: []}; 130 for (const i in candidates) { 131 const raw = candidates[i]; 132 const data = raw.replace(/\(.+\)\./, ''); 133 if (data.startsWith(word)) 134 res.list.push({text: data, displayText: raw}) 135 } 136 if (token.string.length <= 1 || !word || keywords.includes(token.string)) { 137 res.list = []; 138 } else { 139 CodeMirror.on(res, "pick", function(obj) { 140 const doc = window.EDITOR.getDoc(), w = obj.text; 141 window.EDITOR.execCommand("delWordBefore"); 142 doc.replaceRange(keywords.includes(w) ? (w + ' ') : w, doc.getCursor()); 143 }); 144 } 145 return res; 146 }; 147 window.EDITOR = CodeMirror.fromTextArea(textarea, { 148 mode: 'lua', 149 lineNumbers: true, 150 smartIndent: true, 151 indentUnit: 4, 152 extraKeys: { "F9": run }, 153 hintOptions: { completeSingle: false }, 154 }); 155 window.EDITOR.on("keyup", function(editor, event) { 156 var keyCode = parseInt(event.keyCode || event.which); 157 var cursor = editor.getDoc().getCursor(); 158 var token = editor.getTokenAt(cursor); 159 var word = editor.findWordAt(cursor); 160 var currentWord = editor.getRange(word.anchor, word.head); 161 162 if (!editor.state.completionActive && !controlKeys.includes(keyCode) && token.type != 'number' && token.type != 'string') { 163 editor.showHint(); 164 return; 165 } 166 }); 167 } 168 loadEditor(code).then(function() {}); 169 170 function loadPlainEditor(editor) { 171 editor.addEventListener('keydown', function(e) { 172 var start = this.selectionStart, end = this.selectionEnd; 173 if (e.key == 'Tab') { 174 e.preventDefault(); 175 this.value = this.value.substring(0, start) + "\t" + this.value.substring(end); 176 this.selectionStart = this.selectionEnd = start + 1; 177 } 178 if (e.key == 'F9') { 179 e.preventDefault(); 180 run(); 181 } 182 if (e.key == 'Enter') { 183 var v = this.value.substring(0, start); 184 var idx = v.lastIndexOf('\n'); 185 if (idx) { 186 v = v.substring(idx + 1) 187 var spaces = ''; 188 for (var i = 0; i < v.length; i++) { 189 var c = v.charAt(i); 190 if (c == ' ' || c == '\t') 191 spaces += c; 192 else 193 break; 194 } 195 if (spaces) { 196 e.preventDefault() 197 this.value = this.value.substring(0, start) + "\n" + spaces + this.value.substring(end); 198 this.selectionStart = this.selectionEnd = start + 1 + spaces.length; 199 } 200 } 201 } 202 }); 203 } 204 205 function div(html, title) { 206 var el = document.createElement("div"); 207 el.className = title ? 'title' : 'content'; 208 el.innerText = html; 209 return el; 210 } 211 212 function run() { 213 const outf = document.getElementById('output_fields').value; 214 const code = encodeURIComponent(window.EDITOR ? window.EDITOR.getValue() : editor.value); 215 localStorage.setItem('output', outf); 216 fetch('?output=' + outf + '&code=' + code) 217 .then(response => response.json()) 218 .then(function(data) { 219 var el = output; 220 el.innerHTML = ''; 221 222 if (data.error) { 223 el.appendChild(div("Error", true)) 224 el.appendChild(div(data.error)) 225 } else if (data.result) { 226 el.appendChild(div("Results", true)) 227 v = data.result; 228 el.appendChild(div(typeof v === 'object' ? JSON.stringify(v) : v)); 229 } 230 if (data.stdout) { 231 el.appendChild(div("Stdout", true)); 232 el.appendChild(div(data.stdout)); 233 el.appendChild(div("Elapsed", true)) 234 el.appendChild(div(data.elapsed + 's')); 235 } 236 if (data.opcode) { 237 el.appendChild(div("Compiled", true)); 238 el.appendChild(div(data.opcode)); 239 } 240 if (data.survey) { 241 el.appendChild(div("Survey", true)); 242 el.appendChild(div(JSON.stringify(data.survey))); 243 } 244 }); 245 } 246 247 document.getElementById('output_fields').value = localStorage.getItem('output') || 'all'; 248 </script>