├── JavaScript ├── main.js ├── main.min.js ├── vim.js ├── vim.min.js ├── vimwasm.js └── vimwasm.min.js ├── LICENSE ├── README.md ├── fs.txt ├── img └── btn_donate_SM.gif ├── index.php ├── index ├── About.html ├── ChangeLog.html └── TODOs.html ├── robots.txt ├── styles ├── style.css └── water.css └── vim.wasm /JavaScript/main.js: -------------------------------------------------------------------------------- 1 | var _a; 2 | import { 3 | VimWasm, 4 | checkBrowserCompatibility, 5 | VIM_VERSION 6 | } from "./vimwasm.js"; 7 | const queryParams = new URLSearchParams(window.location.search); 8 | const debugging = queryParams.has("debug"); 9 | const perf = queryParams.has("perf"); 10 | const feature = (_a = queryParams.get("feature"), _a !== null && _a !== void 0 ? _a : "normal"); 11 | const clipboardAvailable = navigator.clipboard !== undefined; 12 | const dirs = queryParams.getAll("dir"); 13 | const cmdArgs = queryParams.getAll("arg"); 14 | if (cmdArgs.length === 0 && feature === "normal") { 15 | cmdArgs.push("/home/web_user/tryit.js") 16 | } 17 | const fetchFiles = function() { 18 | const ret = {}; 19 | for (const mapping of queryParams.getAll("file")) { 20 | const i = mapping.indexOf("="); 21 | if (i <= 0) { 22 | continue 23 | } 24 | const path = mapping.slice(0, i); 25 | const remote = mapping.slice(i + 1); 26 | ret[path] = remote 27 | } 28 | return ret 29 | }(); 30 | let vimIsRunning = false; 31 | 32 | function fatal(err) { 33 | if (typeof err === "string") { 34 | err = new Error(err) 35 | } 36 | alert("FATAL: " + err.message); 37 | throw err 38 | } { 39 | const compatMessage = checkBrowserCompatibility(); 40 | if (compatMessage !== undefined) { 41 | fatal(compatMessage) 42 | } 43 | } 44 | const screenCanvasElement = document.getElementById("vim-screen"); 45 | const workerScriptPath = feature === "normal" ? "./vim.js" : `./${feature}/vim.js`; 46 | const vim = new VimWasm({ 47 | canvas: screenCanvasElement, 48 | input: document.getElementById("vim-input"), 49 | workerScriptPath: workerScriptPath 50 | }); 51 | screenCanvasElement.addEventListener("dragover", e => { 52 | e.stopPropagation(); 53 | e.preventDefault(); 54 | if (e.dataTransfer) { 55 | e.dataTransfer.dropEffect = "copy" 56 | } 57 | }, false); 58 | screenCanvasElement.addEventListener("drop", e => { 59 | e.stopPropagation(); 60 | e.preventDefault(); 61 | if (e.dataTransfer === null) { 62 | return 63 | } 64 | vim.dropFiles(e.dataTransfer.files).catch(fatal) 65 | }, false); 66 | vim.onVimInit = (() => { 67 | vimIsRunning = true 68 | }); 69 | if (!perf) { 70 | vim.onVimExit = (status => { 71 | vimIsRunning = false; 72 | alert(`Vim exited with status ${status}`) 73 | }) 74 | } 75 | if (!perf && !debugging) { 76 | window.addEventListener("beforeunload", e => { 77 | if (vimIsRunning) { 78 | e.preventDefault(); 79 | e.returnValue = "" 80 | } 81 | }) 82 | } 83 | vim.onFileExport = ((fullpath, contents) => { 84 | const slashIdx = fullpath.lastIndexOf("/"); 85 | const filename = slashIdx !== -1 ? fullpath.slice(slashIdx + 1) : fullpath; 86 | const blob = new Blob([contents], { 87 | type: "application/octet-stream" 88 | }); 89 | const url = URL.createObjectURL(blob); 90 | const a = document.createElement("a"); 91 | a.style.display = "none"; 92 | a.href = url; 93 | a.rel = "noopener"; 94 | a.download = filename; 95 | document.body.appendChild(a); 96 | a.click(); 97 | document.body.removeChild(a); 98 | URL.revokeObjectURL(url) 99 | }); 100 | 101 | function clipboardSupported() { 102 | if (clipboardAvailable) { 103 | return undefined 104 | } 105 | alert("Clipboard API is not supported by this browser. Clipboard register is not available"); 106 | return Promise.reject() 107 | } 108 | vim.readClipboard = (() => { 109 | var _a; 110 | return _a = clipboardSupported(), _a !== null && _a !== void 0 ? _a : navigator.clipboard.readText() 111 | }); 112 | vim.onWriteClipboard = (text => { 113 | var _a; 114 | return _a = clipboardSupported(), _a !== null && _a !== void 0 ? _a : navigator.clipboard.writeText(text) 115 | }); 116 | vim.onTitleUpdate = (title => { 117 | document.title = title 118 | }); 119 | vim.onError = fatal; 120 | vim.start({ 121 | debug: debugging, 122 | perf: perf, 123 | clipboard: clipboardAvailable, 124 | persistentDirs: ["/home/web_user/.vim"], 125 | dirs: dirs, 126 | fetchFiles: fetchFiles, 127 | cmdArgs: cmdArgs 128 | }); 129 | if (debugging) { 130 | window.vim = vim; 131 | console.log("main: Vim version:", VIM_VERSION) 132 | } 133 | -------------------------------------------------------------------------------- /JavaScript/main.min.js: -------------------------------------------------------------------------------- 1 | var _a;import{VimWasm,checkBrowserCompatibility,VIM_VERSION}from"./vimwasm.js";const queryParams=new URLSearchParams(window.location.search);const debugging=queryParams.has("debug");const perf=queryParams.has("perf");const feature=(_a=queryParams.get("feature"),_a!==null&&_a!==void 0?_a:"normal");const clipboardAvailable=navigator.clipboard!==undefined;const dirs=queryParams.getAll("dir");const cmdArgs=queryParams.getAll("arg");if(cmdArgs.length===0&&feature==="normal"){cmdArgs.push("/home/web_user/tryit.js")}const fetchFiles=function(){const ret={};for(const mapping of queryParams.getAll("file")){const i=mapping.indexOf("=");if(i<=0){continue}const path=mapping.slice(0,i);const remote=mapping.slice(i+1);ret[path]=remote}return ret}();let vimIsRunning=false;function fatal(err){if(typeof err==="string"){err=new Error(err)}alert("FATAL: "+err.message);throw err}{const compatMessage=checkBrowserCompatibility();if(compatMessage!==undefined){fatal(compatMessage)}}const screenCanvasElement=document.getElementById("vim-screen");const workerScriptPath=feature==="normal"?"./vim.js":`./${feature}/vim.js`;const vim=new VimWasm({canvas:screenCanvasElement,input:document.getElementById("vim-input"),workerScriptPath:workerScriptPath});screenCanvasElement.addEventListener("dragover",e=>{e.stopPropagation();e.preventDefault();if(e.dataTransfer){e.dataTransfer.dropEffect="copy"}},false);screenCanvasElement.addEventListener("drop",e=>{e.stopPropagation();e.preventDefault();if(e.dataTransfer===null){return}vim.dropFiles(e.dataTransfer.files).catch(fatal)},false);vim.onVimInit=(()=>{vimIsRunning=true});if(!perf){vim.onVimExit=(status=>{vimIsRunning=false;alert(`Vim exited with status ${status}`)})}if(!perf&&!debugging){window.addEventListener("beforeunload",e=>{if(vimIsRunning){e.preventDefault();e.returnValue=""}})}vim.onFileExport=((fullpath,contents)=>{const slashIdx=fullpath.lastIndexOf("/");const filename=slashIdx!==-1?fullpath.slice(slashIdx+1):fullpath;const blob=new Blob([contents],{type:"application/octet-stream"});const url=URL.createObjectURL(blob);const a=document.createElement("a");a.style.display="none";a.href=url;a.rel="noopener";a.download=filename;document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url)});function clipboardSupported(){if(clipboardAvailable){return undefined}alert("Clipboard API is not supported by this browser. Clipboard register is not available");return Promise.reject()}vim.readClipboard=(()=>{var _a;return _a=clipboardSupported(),_a!==null&&_a!==void 0?_a:navigator.clipboard.readText()});vim.onWriteClipboard=(text=>{var _a;return _a=clipboardSupported(),_a!==null&&_a!==void 0?_a:navigator.clipboard.writeText(text)});vim.onTitleUpdate=(title=>{document.title=title});vim.onError=fatal;vim.start({debug:debugging,perf:perf,clipboard:clipboardAvailable,persistentDirs:["/home/web_user/.vim"],dirs:dirs,fetchFiles:fetchFiles,cmdArgs:cmdArgs});if(debugging){window.vim=vim;console.log("main: Vim version:",VIM_VERSION)} 2 | -------------------------------------------------------------------------------- /JavaScript/vimwasm.js: -------------------------------------------------------------------------------- 1 | /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 2 | export const VIM_VERSION = "8.2.0055"; 3 | const AsyncFunction = Object.getPrototypeOf(async function() {}).constructor; 4 | 5 | function noop() {} 6 | let debug = noop; 7 | const STATUS_NOT_SET = 0; 8 | const STATUS_NOTIFY_KEY = 1; 9 | const STATUS_NOTIFY_RESIZE = 2; 10 | const STATUS_NOTIFY_OPEN_FILE_BUF_COMPLETE = 3; 11 | const STATUS_NOTIFY_CLIPBOARD_WRITE_COMPLETE = 4; 12 | const STATUS_REQUEST_CMDLINE = 5; 13 | const STATUS_REQUEST_SHARED_BUF = 6; 14 | const STATUS_NOTIFY_ERROR_OUTPUT = 7; 15 | const STATUS_NOTIFY_EVAL_FUNC_RET = 8; 16 | 17 | function statusName(s) { 18 | switch (s) { 19 | case STATUS_NOT_SET: 20 | return "NOT_SET"; 21 | case STATUS_NOTIFY_KEY: 22 | return "NOTIFY_KEY"; 23 | case STATUS_NOTIFY_RESIZE: 24 | return "NOTIFY_RESIZE"; 25 | case STATUS_NOTIFY_OPEN_FILE_BUF_COMPLETE: 26 | return "NOTIFY_OPEN_FILE_BUF_COMPLETE"; 27 | case STATUS_NOTIFY_CLIPBOARD_WRITE_COMPLETE: 28 | return "NOTIFY_CLIPBOARD_WRITE_COMPLETE"; 29 | case STATUS_REQUEST_CMDLINE: 30 | return "REQUEST_CMDLINE"; 31 | case STATUS_REQUEST_SHARED_BUF: 32 | return "REQUEST_SHARED_BUF"; 33 | case STATUS_NOTIFY_ERROR_OUTPUT: 34 | return "NOTIFY_ERROR_OUTPUT"; 35 | case STATUS_NOTIFY_EVAL_FUNC_RET: 36 | return "STATUS_NOTIFY_EVAL_FUNC_RET"; 37 | default: 38 | return `Unknown command: ${s}` 39 | } 40 | } 41 | export function checkBrowserCompatibility() { 42 | function notSupported(feat) { 43 | return `${feat} is not supported by this browser. If you're using Firefox or Safari, please enable feature flag.` 44 | } 45 | if (typeof SharedArrayBuffer === "undefined") { 46 | return notSupported("SharedArrayBuffer") 47 | } 48 | if (typeof Atomics === "undefined") { 49 | return notSupported("Atomics API") 50 | } 51 | return undefined 52 | }; 53 | export class VimWorker { 54 | constructor(scriptPath, onMessage, onError) { 55 | // XXX We load all these fnames from Indexed when the Web Worker starts. 56 | var fnames = JSON.stringify(Object.keys(getfnames())); 57 | 58 | this.worker = new Worker(scriptPath, {name: fnames}); 59 | this.worker.onmessage = this.recvMessage.bind(this); 60 | this.worker.onerror = this.recvError.bind(this); 61 | this.sharedBuffer = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT*128)); 62 | this.onMessage = onMessage; 63 | this.onError = onError; 64 | this.onOneshotMessage = new Map; 65 | this.debug = false; 66 | this.pendingEvents = [] 67 | } 68 | terminate() { 69 | this.worker.terminate(); 70 | this.worker.onmessage = null; 71 | debug("Terminated worker thread. Thank you for working hard!") 72 | } 73 | sendStartMessage(msg) { 74 | this.worker.postMessage(msg); 75 | debug("Sent start message", msg) 76 | } 77 | notifyOpenFileBufComplete(filename, bufId) { 78 | this.enqueueEvent(STATUS_NOTIFY_OPEN_FILE_BUF_COMPLETE, bufId, filename) 79 | } 80 | notifyClipboardWriteComplete(cannotSend, bufId) { 81 | this.enqueueEvent(STATUS_NOTIFY_CLIPBOARD_WRITE_COMPLETE, cannotSend, bufId) 82 | } 83 | notifyKeyEvent(key, keyCode, ctrl, shift, alt, meta) { 84 | this.enqueueEvent(STATUS_NOTIFY_KEY, keyCode, ctrl, shift, alt, meta, key) 85 | } 86 | notifyResizeEvent(width, height) { 87 | this.enqueueEvent(STATUS_NOTIFY_RESIZE, width, height) 88 | } 89 | async requestSharedBuffer(byteLength) { 90 | this.enqueueEvent(STATUS_REQUEST_SHARED_BUF, byteLength); 91 | const msg = await this.waitForOneshotMessage("shared-buf:response"); 92 | if (msg.buffer.byteLength !== byteLength) { 93 | throw new Error(`Size of shared buffer from worker ${msg.buffer.byteLength} bytes mismatches to requested size ${byteLength} bytes`) 94 | } 95 | return [msg.bufId, msg.buffer] 96 | } 97 | notifyClipboardError() { 98 | this.notifyClipboardWriteComplete(true, 0); 99 | debug("Reading clipboard failed. Notify it to worker") 100 | } 101 | async responseClipboardText(text) { 102 | const encoded = (new TextEncoder).encode(text); 103 | const [bufId, buffer] = await this.requestSharedBuffer(encoded.byteLength + 1); 104 | new Uint8Array(buffer).set(encoded); 105 | this.notifyClipboardWriteComplete(false, bufId); 106 | debug("Wrote clipboard", encoded.byteLength, "bytes text and notified to worker") 107 | } 108 | async requestCmdline(cmdline) { 109 | if (cmdline.length === 0) { 110 | throw new Error("Specified command line is empty") 111 | } 112 | this.enqueueEvent(STATUS_REQUEST_CMDLINE, cmdline); 113 | const msg = await this.waitForOneshotMessage("cmdline:response"); 114 | debug("Result of command", cmdline, ":", msg.success); 115 | if (!msg.success) { 116 | throw Error(`Command '${cmdline}' was invalid and not accepted by Vim`) 117 | } 118 | } 119 | 120 | // // Added by yours truly. Programmerhat. 121 | // async requestFile() { 122 | // const msg = await this.waitForOneshotMessage("cmdline:response"); 123 | // } 124 | 125 | async notifyErrorOutput(message) { 126 | const encoded = (new TextEncoder).encode(message); 127 | const [bufId, buffer] = await this.requestSharedBuffer(encoded.byteLength); 128 | new Uint8Array(buffer).set(encoded); 129 | this.enqueueEvent(STATUS_NOTIFY_ERROR_OUTPUT, bufId); 130 | debug("Sent error message output:", message) 131 | } 132 | async notifyEvalFuncRet(ret) { 133 | const encoded = (new TextEncoder).encode(ret); 134 | const [bufId, buffer] = await this.requestSharedBuffer(encoded.byteLength); 135 | new Uint8Array(buffer).set(encoded); 136 | this.enqueueEvent(STATUS_NOTIFY_EVAL_FUNC_RET, false, bufId); 137 | debug("Sent return value of evaluated JS function:", ret) 138 | } 139 | async notifyEvalFuncError(msg, err, dontReply) { 140 | const errmsg = `${msg} for jsevalfunc(): ${err.message}: ${err.stack}`; 141 | if (dontReply) { 142 | debug("Will send error output from jsevalfunc() though the invocation was notify-only:", errmsg); 143 | return this.notifyErrorOutput(errmsg) 144 | } 145 | const encoded = (new TextEncoder).encode("E9999: " + errmsg); 146 | const [bufId, buffer] = await this.requestSharedBuffer(encoded.byteLength); 147 | new Uint8Array(buffer).set(encoded); 148 | this.enqueueEvent(STATUS_NOTIFY_EVAL_FUNC_RET, true, bufId); 149 | debug("Sent exception thrown by evaluated JS function:", msg, err) 150 | } 151 | onEventDone(doneStatus) { 152 | const done = statusName(doneStatus); 153 | const finished = this.pendingEvents.shift(); 154 | if (finished === undefined) { 155 | throw new Error(`FATAL: Received ${done} event but event queue is empty`) 156 | } 157 | if (finished[0] !== doneStatus) { 158 | throw new Error(`FATAL: Received ${done} event but queue says previous event was ${statusName(finished[0])} with args ${finished[1]}`) 159 | } 160 | if (this.pendingEvents.length === 0) { 161 | debug("No pending event remains after event", done); 162 | return 163 | } 164 | debug("After", done, "event, still", this.pendingEvents.length, "events are pending"); 165 | const [status, values] = this.pendingEvents[0]; 166 | this.sendEvent(status, values) 167 | } 168 | enqueueEvent(status, ...values) { 169 | this.pendingEvents.push([status, values]); 170 | if (this.pendingEvents.length > 1) { 171 | debug("Other event is being handled by worker. Pending:", statusName(status), values); 172 | return 173 | } 174 | this.sendEvent(status, values) 175 | } 176 | sendEvent(status, values) { 177 | const event = statusName(status); 178 | if (this.debug) { 179 | const status = Atomics.load(this.sharedBuffer, 0); 180 | if (status !== STATUS_NOT_SET) { 181 | console.error("INVARIANT ERROR! Status byte must be zero cleared:", event) 182 | } 183 | } 184 | debug("Write event", event, "payload to buffer:", values); 185 | let idx = 0; 186 | this.sharedBuffer[idx++] = status; 187 | for (const value of values) { 188 | switch (typeof value) { 189 | case "string": 190 | idx = this.encodeStringToBuffer(value, idx); 191 | break; 192 | case "number": 193 | this.sharedBuffer[idx++] = value; 194 | break; 195 | case "boolean": 196 | this.sharedBuffer[idx++] = +value; 197 | break; 198 | default: 199 | throw new Error(`FATAL: Invalid value for payload to worker: ${value}`) 200 | } 201 | } 202 | debug("Wrote", idx * 4, "bytes to buffer for event", event); 203 | Atomics.notify(this.sharedBuffer, 0, 1); 204 | debug("Notified event", event, "to worker") 205 | } 206 | async waitForOneshotMessage(kind) { 207 | return new Promise(resolve => { 208 | this.onOneshotMessage.set(kind, resolve) 209 | }) 210 | } 211 | encodeStringToBuffer(s, startIdx) { 212 | let idx = startIdx; 213 | const len = s.length; 214 | this.sharedBuffer[idx++] = len; 215 | for (let i = 0; i < len; ++i) { 216 | this.sharedBuffer[idx++] = s.charCodeAt(i) 217 | } 218 | return idx 219 | } 220 | recvMessage(e, asdf) { 221 | const msg = e.data; 222 | const handler = this.onOneshotMessage.get(msg.kind); 223 | if (handler !== undefined) { 224 | this.onOneshotMessage.delete(msg.kind); 225 | handler(msg); 226 | return 227 | } 228 | this.onMessage(msg) 229 | } 230 | recvError(e) { 231 | debug("Received an error from worker:", e); 232 | const msg = `${e.message} (${e.filename}:${e.lineno}:${e.colno})`; 233 | this.onError(new Error(msg)) 234 | } 235 | }; 236 | export class ResizeHandler { 237 | constructor(domWidth, domHeight, canvas, worker) { 238 | this.canvas = canvas; 239 | this.worker = worker; 240 | this.elemHeight = domHeight; 241 | this.elemWidth = domWidth; 242 | const dpr = window.devicePixelRatio || 1; 243 | this.canvas.width = domWidth * dpr; 244 | this.canvas.height = domHeight * dpr; 245 | this.bounceTimerToken = null; 246 | this.onResize = this.onResize.bind(this) 247 | } 248 | onVimInit() { 249 | window.addEventListener("resize", this.onResize, { 250 | passive: true 251 | }) 252 | } 253 | onVimExit() { 254 | window.removeEventListener("resize", this.onResize) 255 | } 256 | doResize() { 257 | const rect = this.canvas.getBoundingClientRect(); 258 | debug("Resize Vim:", rect); 259 | this.elemWidth = rect.width; 260 | this.elemHeight = rect.height; 261 | const res = window.devicePixelRatio || 1; 262 | this.canvas.width = rect.width * res; 263 | this.canvas.height = rect.height * res; 264 | this.worker.notifyResizeEvent(rect.width, rect.height) 265 | } 266 | onResize() { 267 | if (this.bounceTimerToken !== null) { 268 | window.clearTimeout(this.bounceTimerToken) 269 | } 270 | this.bounceTimerToken = window.setTimeout(() => { 271 | this.bounceTimerToken = null; 272 | this.doResize() 273 | }, 500) 274 | } 275 | }; 276 | export class InputHandler { 277 | constructor(worker, input) { 278 | this.worker = worker; 279 | this.elem = input; 280 | this.onKeydown = this.onKeydown.bind(this); 281 | this.onBlur = this.onBlur.bind(this); 282 | this.onFocus = this.onFocus.bind(this); 283 | this.focus() 284 | } 285 | setFont(name, size) { 286 | this.elem.style.fontFamily = name; 287 | this.elem.style.fontSize = `${size}px` 288 | } 289 | focus() { 290 | this.elem.focus() 291 | } 292 | onVimInit() { 293 | this.elem.addEventListener("keydown", this.onKeydown, { 294 | capture: true 295 | }); 296 | this.elem.addEventListener("blur", this.onBlur); 297 | this.elem.addEventListener("focus", this.onFocus) 298 | } 299 | onVimExit() { 300 | this.elem.removeEventListener("keydown", this.onKeydown); 301 | this.elem.removeEventListener("blur", this.onBlur); 302 | this.elem.removeEventListener("focus", this.onFocus) 303 | } 304 | onKeydown(event) { 305 | event.preventDefault(); 306 | event.stopPropagation(); 307 | debug("onKeydown():", event, event.key, event.keyCode); 308 | let key = event.key; 309 | const ctrl = event.ctrlKey; 310 | const shift = event.shiftKey; 311 | const alt = event.altKey; 312 | const meta = event.metaKey; 313 | if (key.length > 1) { 314 | if (key === "Unidentified" || ctrl && key === "Control" || shift && key === "Shift" || alt && key === "Alt" || meta && key === "Meta") { 315 | debug("Ignore key input", key); 316 | return 317 | } 318 | } 319 | if (key === "Â¥" || !shift && key === "|" && event.code === "IntlYen") { 320 | key = "\\" 321 | } 322 | this.worker.notifyKeyEvent(key, event.keyCode, ctrl, shift, alt, meta) 323 | } 324 | onFocus() { 325 | debug("onFocus()") 326 | } 327 | onBlur(event) { 328 | debug("onBlur():", event); 329 | event.preventDefault() 330 | } 331 | }; 332 | export class ScreenCanvas { 333 | constructor(worker, canvas, input) { 334 | this.worker = worker; 335 | this.canvas = canvas; 336 | const ctx = this.canvas.getContext("2d", { 337 | alpha: false 338 | }); 339 | if (ctx === null) { 340 | throw new Error("Cannot get 2D context for ") 341 | } 342 | this.ctx = ctx; 343 | const rect = this.canvas.getBoundingClientRect(); 344 | const res = window.devicePixelRatio || 1; 345 | this.canvas.width = rect.width * res; 346 | this.canvas.height = rect.height * res; 347 | this.canvas.addEventListener("click", this.onClick.bind(this), { 348 | capture: true, 349 | passive: true 350 | }); 351 | this.input = new InputHandler(this.worker, input); 352 | this.resizer = new ResizeHandler(rect.width, rect.height, canvas, worker); 353 | this.onAnimationFrame = this.onAnimationFrame.bind(this); 354 | this.queue = []; 355 | this.rafScheduled = false; 356 | this.perf = false; 357 | this.fgColor = ""; 358 | this.spColor = ""; 359 | this.fontName = "" 360 | } 361 | onVimInit() { 362 | this.input.onVimInit(); 363 | this.resizer.onVimInit() 364 | } 365 | onVimExit() { 366 | this.input.onVimExit(); 367 | this.resizer.onVimExit() 368 | } 369 | draw(msg) { 370 | if (!this.rafScheduled) { 371 | window.requestAnimationFrame(this.onAnimationFrame); 372 | this.rafScheduled = true 373 | } 374 | this.queue.push(msg) 375 | } 376 | focus() { 377 | this.input.focus() 378 | } 379 | getDomSize() { 380 | return { 381 | width: this.resizer.elemWidth, 382 | height: this.resizer.elemHeight 383 | } 384 | } 385 | setPerf(enabled) { 386 | this.perf = enabled 387 | } 388 | setColorFG(name) { 389 | this.fgColor = name 390 | } 391 | setColorBG(_name) {} 392 | setColorSP(name) { 393 | this.spColor = name 394 | } 395 | setFont(name, size) { 396 | this.fontName = name; 397 | this.input.setFont(name, size) 398 | } 399 | drawRect(x, y, w, h, color, filled) { 400 | const dpr = window.devicePixelRatio || 1; 401 | x = Math.floor(x * dpr); 402 | y = Math.floor(y * dpr); 403 | w = Math.floor(w * dpr); 404 | h = Math.floor(h * dpr); 405 | this.ctx.fillStyle = color; 406 | if (filled) { 407 | this.ctx.fillRect(x, y, w, h) 408 | } else { 409 | this.ctx.rect(x, y, w, h) 410 | } 411 | } 412 | drawText(text, ch, lh, cw, x, y, bold, underline, undercurl, strike) { 413 | const dpr = window.devicePixelRatio || 1; 414 | ch = ch * dpr; 415 | lh = lh * dpr; 416 | cw = cw * dpr; 417 | x = x * dpr; 418 | y = y * dpr; 419 | let font = `${Math.floor(ch)}px ${this.fontName}`; 420 | if (bold) { 421 | font = "bold " + font 422 | } 423 | this.ctx.font = font; 424 | this.ctx.textBaseline = "bottom"; 425 | this.ctx.fillStyle = this.fgColor; 426 | const descent = (lh - ch) / 2; 427 | const yi = Math.floor(y + lh - descent); 428 | for (let i = 0; i < text.length; ++i) { 429 | const c = text[i]; 430 | if (c === " ") { 431 | continue 432 | } 433 | this.ctx.fillText(c, Math.floor(x + cw * i), yi) 434 | } 435 | if (underline) { 436 | this.ctx.strokeStyle = this.fgColor; 437 | this.ctx.lineWidth = 1 * dpr; 438 | this.ctx.setLineDash([]); 439 | this.ctx.beginPath(); 440 | const underlineY = Math.floor(y + lh - descent - 1 * dpr); 441 | this.ctx.moveTo(Math.floor(x), underlineY); 442 | this.ctx.lineTo(Math.floor(x + cw * text.length), underlineY); 443 | this.ctx.stroke() 444 | } else if (undercurl) { 445 | this.ctx.strokeStyle = this.spColor; 446 | this.ctx.lineWidth = 1 * dpr; 447 | const curlWidth = Math.floor(cw / 3); 448 | this.ctx.setLineDash([curlWidth, curlWidth]); 449 | this.ctx.beginPath(); 450 | const undercurlY = Math.floor(y + lh - descent - 1 * dpr); 451 | this.ctx.moveTo(Math.floor(x), undercurlY); 452 | this.ctx.lineTo(Math.floor(x + cw * text.length), undercurlY); 453 | this.ctx.stroke() 454 | } else if (strike) { 455 | this.ctx.strokeStyle = this.fgColor; 456 | this.ctx.lineWidth = 1 * dpr; 457 | this.ctx.beginPath(); 458 | const strikeY = Math.floor(y + lh / 2); 459 | this.ctx.moveTo(Math.floor(x), strikeY); 460 | this.ctx.lineTo(Math.floor(x + cw * text.length), strikeY); 461 | this.ctx.stroke() 462 | } 463 | } 464 | invertRect(x, y, w, h) { 465 | const dpr = window.devicePixelRatio || 1; 466 | x = Math.floor(x * dpr); 467 | y = Math.floor(y * dpr); 468 | w = Math.floor(w * dpr); 469 | h = Math.floor(h * dpr); 470 | const img = this.ctx.getImageData(x, y, w, h); 471 | const data = img.data; 472 | const len = data.length; 473 | for (let i = 0; i < len; ++i) { 474 | data[i] = 255 - data[i]; 475 | ++i; 476 | data[i] = 255 - data[i]; 477 | ++i; 478 | data[i] = 255 - data[i]; 479 | ++i 480 | } 481 | this.ctx.putImageData(img, x, y) 482 | } 483 | imageScroll(x, sy, dy, w, h) { 484 | const dpr = window.devicePixelRatio || 1; 485 | x = Math.floor(x * dpr); 486 | sy = Math.floor(sy * dpr); 487 | dy = Math.floor(dy * dpr); 488 | w = Math.floor(w * dpr); 489 | h = Math.floor(h * dpr); 490 | this.ctx.drawImage(this.canvas, x, sy, w, h, x, dy, w, h) 491 | } 492 | onClick() { 493 | this.input.focus() 494 | } 495 | onAnimationFrame() { 496 | debug("Rendering", this.queue.length, "events on animation frame"); 497 | this.perfMark("raf"); 498 | for (const [method, args] of this.queue) { 499 | this.perfMark("draw"); 500 | this[method].apply(this, args); 501 | this.perfMeasure("draw", `draw:${method}`) 502 | } 503 | this.queue.length = 0; 504 | this.rafScheduled = false; 505 | this.perfMeasure("raf") 506 | } 507 | perfMark(m) { 508 | if (this.perf) { 509 | performance.mark(m) 510 | } 511 | } 512 | perfMeasure(m, n) { 513 | if (this.perf) { 514 | performance.measure(n !== null && n !== void 0 ? n : m, m); 515 | performance.clearMarks(m) 516 | } 517 | } 518 | }; 519 | export class VimWasm { 520 | constructor(opts) { 521 | const script = opts.workerScriptPath; 522 | if (!script) { 523 | throw new Error("'workerScriptPath' option is required") 524 | } 525 | this.handleError = this.handleError.bind(this); 526 | this.worker = new VimWorker(script, this.onMessage.bind(this), this.handleError); 527 | if ("canvas" in opts && "input" in opts) { 528 | this.screen = new ScreenCanvas(this.worker, opts.canvas, opts.input) 529 | } else if ("screen" in opts) { 530 | this.screen = opts.screen 531 | } else { 532 | throw new Error("Invalid options for VimWasm construction: " + JSON.stringify(opts)) 533 | } 534 | this.perf = false; 535 | this.debug = false; 536 | this.perfMessages = {}; 537 | this.running = false; 538 | this.end = false 539 | this.fs = new MyIndexedDB(); // XXX 540 | } 541 | start(opts) { 542 | var _a, _b, _c, _d, _e; 543 | if (this.running || this.end) { 544 | throw new Error("Cannot start Vim twice") 545 | } 546 | const o = opts !== null && opts !== void 0 ? opts : { 547 | clipboard: navigator.clipboard !== undefined 548 | }; 549 | if (o.debug) { 550 | debug = console.log.bind(console, "main:"); 551 | this.worker.debug = true 552 | } 553 | this.perf = !!o.perf; 554 | this.debug = !!o.debug; 555 | this.screen.setPerf(this.perf); 556 | this.running = true; 557 | this.perfMark("init"); 558 | const { 559 | width: width, 560 | height: height 561 | } = this.screen.getDomSize(); 562 | const msg = { 563 | kind: "start", 564 | buffer: this.worker.sharedBuffer, 565 | canvasDomWidth: width, 566 | canvasDomHeight: height, 567 | debug: this.debug, 568 | perf: this.perf, 569 | clipboard: !!o.clipboard, 570 | files: (_a = o.files, _a !== null && _a !== void 0 ? _a : {}), 571 | dirs: (_b = o.dirs, _b !== null && _b !== void 0 ? _b : []), 572 | fetchFiles: (_c = o.fetchFiles, _c !== null && _c !== void 0 ? _c : {}), 573 | persistent: (_d = o.persistentDirs, _d !== null && _d !== void 0 ? _d : []), 574 | cmdArgs: (_e = o.cmdArgs, _e !== null && _e !== void 0 ? _e : []) 575 | }; 576 | this.worker.sendStartMessage(msg); 577 | debug("Started with drawer", this.screen) 578 | } 579 | async dropFile(name, contents) { 580 | if (!this.running) { 581 | throw new Error("Cannot open file since Vim is not running") 582 | } 583 | debug("Handling to open file", name, contents); 584 | const [bufId, buffer] = await this.worker.requestSharedBuffer(contents.byteLength); 585 | new Uint8Array(buffer).set(new Uint8Array(contents)); 586 | this.worker.notifyOpenFileBufComplete(name, bufId); 587 | debug("Wrote file", name, "to", contents.byteLength, "bytes buffer and notified it to worker") 588 | } 589 | async dropFiles(files) { 590 | const reader = new FileReader; 591 | for (const file of files) { 592 | const [name, contents] = await this.readFile(reader, file); 593 | await this.dropFile(name, contents) 594 | } 595 | } 596 | resize(pixelWidth, pixelHeight) { 597 | this.worker.notifyResizeEvent(pixelWidth, pixelHeight) 598 | } 599 | sendKeydown(key, keyCode, modifiers) { 600 | const { 601 | ctrl: ctrl = false, 602 | shift: shift = false, 603 | alt: alt = false, 604 | meta: meta = false 605 | } = modifiers !== null && modifiers !== void 0 ? modifiers : {}; 606 | if (key.length > 1) { 607 | if (key === "Unidentified" || ctrl && key === "Control" || shift && key === "Shift" || alt && key === "Alt" || meta && key === "Meta") { 608 | debug("Ignore key input", key); 609 | return 610 | } 611 | } 612 | this.worker.notifyKeyEvent(key, keyCode, ctrl, shift, alt, meta) 613 | } 614 | cmdline(cmdline) { 615 | return this.worker.requestCmdline(cmdline) 616 | } 617 | isRunning() { 618 | return this.running 619 | } 620 | focus() { 621 | this.screen.focus() 622 | } 623 | showError(message) { 624 | return this.worker.notifyErrorOutput(message) 625 | } 626 | async readFile(reader, file) { 627 | return new Promise((resolve, reject) => { 628 | reader.onload = (f => { 629 | debug("Read file", file.name, "from D&D:", f); 630 | resolve([file.name, reader.result]) 631 | }); 632 | reader.onerror = (() => { 633 | reader.abort(); 634 | reject(new Error(`Error on loading file ${file}`)) 635 | }); 636 | reader.readAsArrayBuffer(file) 637 | }) 638 | } 639 | async evalJS(path, contents) { 640 | debug("Evaluating JavaScript file", path, "with size", contents.byteLength, "bytes"); 641 | const dec = new TextDecoder; 642 | const src = '"use strict";' + dec.decode(contents); 643 | try { 644 | Function(src)() 645 | } catch (err) { 646 | debug("Failed to evaluate", path, "with error:", err); 647 | await this.showError(`${err.message}\n\n${err.stack}`) 648 | } 649 | } 650 | async evalFunc(body, args, notifyOnly) { 651 | debug("Evaluating JavaScript function:", body, args); 652 | let f; 653 | try { 654 | f = new AsyncFunction(body) 655 | } catch (err) { 656 | return this.worker.notifyEvalFuncError("Could not construct function", err, notifyOnly) 657 | } 658 | let ret; 659 | try { 660 | ret = await f(...args) 661 | } catch (err) { 662 | return this.worker.notifyEvalFuncError("Exception was thrown while evaluating function", err, notifyOnly) 663 | } 664 | if (notifyOnly) { 665 | debug("Evaluated JavaScript result was discarded since the message was notify-only:", ret, body); 666 | return Promise.resolve() 667 | } 668 | let retJson; 669 | try { 670 | retJson = JSON.stringify(ret) 671 | } catch (err) { 672 | return this.worker.notifyEvalFuncError("Could not serialize return value as JSON from function", err, false) 673 | } 674 | return this.worker.notifyEvalFuncRet(retJson) 675 | } 676 | onMessage(msg) { 677 | if (this.perf && msg.timestamp !== undefined) { 678 | const duration = Date.now() - msg.timestamp; 679 | const name = msg.kind === "draw" ? `draw:${msg.event[0]}` : msg.kind; 680 | const timestamps = this.perfMessages[name]; 681 | if (timestamps === undefined) { 682 | this.perfMessages[name] = [duration] 683 | } else { 684 | this.perfMessages[name].push(duration) 685 | } 686 | } 687 | switch (msg.kind) { 688 | case "draw": 689 | this.screen.draw(msg.event); 690 | debug("draw event", msg.event); 691 | break; 692 | case "done": 693 | this.worker.onEventDone(msg.status); 694 | break; 695 | case "evalfunc": { 696 | const args = msg.argsJson === undefined ? [] : JSON.parse(msg.argsJson); 697 | this.evalFunc(msg.body, args, msg.notifyOnly).catch(this.handleError); 698 | break 699 | } 700 | case "title": 701 | if (this.onTitleUpdate) { 702 | return; // Don't update title. screws up SEO. TODO: look at whether to re-enable. 703 | debug("title was updated:", msg.title); 704 | this.onTitleUpdate(msg.title) 705 | } 706 | break; 707 | case "read-clipboard:request": 708 | if (this.readClipboard) { 709 | this.readClipboard().then(text => this.worker.responseClipboardText(text)).catch(err => { 710 | debug("Cannot read clipboard:", err); 711 | this.worker.notifyClipboardError() 712 | }) 713 | } else { 714 | debug("Cannot read clipboard because VimWasm.readClipboard is not set"); 715 | this.worker.notifyClipboardError() 716 | } 717 | break; 718 | case "write-clipboard": 719 | debug("Handle writing text", msg.text, "to clipboard with", this.onWriteClipboard); 720 | if (this.onWriteClipboard) { 721 | this.onWriteClipboard(msg.text) 722 | } 723 | break; 724 | case "export": 725 | if (this.onFileExport !== undefined) { 726 | debug("Exporting file", msg.path, "with size in bytes", msg.contents.byteLength); 727 | this.onFileExport(msg.path, msg.contents) 728 | } 729 | break; 730 | case "eval": 731 | this.evalJS(msg.path, msg.contents).catch(this.handleError); 732 | break; 733 | case "started": 734 | this.screen.onVimInit(); 735 | if (this.onVimInit) { 736 | this.onVimInit() 737 | } 738 | this.perfMeasure("init"); 739 | debug("Vim started"); 740 | break; 741 | case "exit": 742 | this.screen.onVimExit(); 743 | this.printPerfs(); 744 | this.worker.terminate(); 745 | if (this.onVimExit) { 746 | this.onVimExit(msg.status) 747 | } 748 | debug("Vim exited with status", msg.status); 749 | this.perf = false; 750 | this.debug = false; 751 | this.screen.setPerf(false); 752 | this.running = false; 753 | this.end = true; 754 | break; 755 | case "error": 756 | debug("Vim threw an error:", msg.message); 757 | this.handleError(new Error(msg.message)); 758 | this.worker.terminate(); 759 | break; 760 | case "writeFile": 761 | if (this.fs) { this.fs.writeFile(msg.fname, msg.file); } 762 | break; 763 | default: 764 | throw new Error(`Unexpected message from worker: ${JSON.stringify(msg)}`) 765 | } 766 | } 767 | handleError(err) { 768 | if (this.onError) { 769 | this.onError(err) 770 | } 771 | } 772 | printPerfs() { 773 | if (!this.perf) { 774 | return 775 | } { 776 | const measurements = new Map; 777 | for (const e of performance.getEntries()) { 778 | const ms = measurements.get(e.name); 779 | if (ms === undefined) { 780 | measurements.set(e.name, [e]) 781 | } else { 782 | ms.push(e) 783 | } 784 | } 785 | const averages = {}; 786 | const amounts = {}; 787 | const timings = []; 788 | for (const [name, ms] of measurements) { 789 | if (ms.length === 1 && ms[0].entryType !== "measure") { 790 | timings.push(ms[0]); 791 | continue 792 | } 793 | console.log(`%c${name}`, "color: green; font-size: large"); 794 | console.table(ms, ["duration", "startTime"]); 795 | const total = ms.reduce((a, m) => a + m.duration, 0); 796 | averages[name] = total / ms.length; 797 | amounts[name] = total 798 | } 799 | console.log("%cTimings (ms)", "color: green; font-size: large"); 800 | console.table(timings, ["name", "entryType", "startTime", "duration"]); 801 | console.log("%cAmount: Perf Mark Durations (ms)", "color: green; font-size: large"); 802 | console.table(amounts); 803 | console.log("%cAverage: Perf Mark Durations (ms)", "color: green; font-size: large"); 804 | console.table(averages); 805 | performance.clearMarks(); 806 | performance.clearMeasures() 807 | } { 808 | const averages = {}; 809 | for (const name of Object.keys(this.perfMessages)) { 810 | const durations = this.perfMessages[name]; 811 | const total = durations.reduce((a, d) => a + d, 0); 812 | averages[name] = total / durations.length 813 | } 814 | console.log("%cAverage: Inter-thread Messages Duration (ms)", "color: green; font-size: large"); 815 | console.table(averages); 816 | this.perfMessages = {} 817 | } 818 | } 819 | perfMark(m) { 820 | if (this.perf) { 821 | performance.mark(m) 822 | } 823 | } 824 | perfMeasure(m) { 825 | if (this.perf) { 826 | performance.measure(m, m); 827 | performance.clearMarks(m) 828 | } 829 | } 830 | }; 831 | 832 | //----------------------------------------------------------------------------- 833 | // Interface with IndexedDB. XXX 834 | //----------------------------------------------------------------------------- 835 | 836 | const DB_NAME = "EM_FS_/vim.js"; // Same as emscripten FS. 837 | const DB_STORE_NAME = "FILE_DATA"; // Same as emscripten FS. 838 | export class MyIndexedDB { 839 | constructor() { 840 | var _this = this; 841 | this.request = indexedDB.open(DB_NAME, 1.0); 842 | var request = this.request; 843 | 844 | request.onblocked = function(event) { 845 | console.error("IndexedDB request blocked", event); 846 | }; 847 | 848 | request.onerror = function (event) { 849 | console.error("Error creating/accessing IndexedDB database", event); 850 | }; 851 | 852 | request.onupgradeneeded = function (event) { 853 | var db = event.target.result; 854 | db.createObjectStore(DB_STORE_NAME); 855 | }; 856 | 857 | request.onsuccess = function (event) { 858 | _this.db = _this.request.result; 859 | _this.db.onerror = function (event) { 860 | console.error("Error creating/accessing IndexedDB database", event); 861 | }; 862 | } 863 | 864 | return this; 865 | } 866 | 867 | writeFile(fname /*string*/, file /*ArrayBuffer*/) { 868 | var buf = new Uint8Array(file); 869 | var transaction = this.db.transaction([DB_STORE_NAME], "readwrite"); 870 | var put = transaction.objectStore(DB_STORE_NAME).put(buf, fname); 871 | 872 | // Keep track of the files we've written to the filesystem. 873 | var fnames = getfnames(); 874 | fnames[fname] = true; 875 | localStorage.setItem('fnames', JSON.stringify(fnames)); 876 | } 877 | 878 | // This is basically debug only as well. 879 | // returns ArrayBuffer in `callback`. 880 | getFile(fname /*string*/, callback) { 881 | var transaction = this.db.transaction([DB_STORE_NAME], "readwrite"); 882 | var get = transaction.objectStore(DB_STORE_NAME).get(fname); 883 | get.onsuccess = function(event) { 884 | var contents = event.target.result; 885 | if (callback) { callback(contents); } 886 | }; 887 | get.onerror = function(event) { 888 | console.error('mainthread getFile', event); 889 | }; 890 | } 891 | } 892 | 893 | // Debug only. 894 | function stringify(file) { 895 | // Taken from https://stackoverflow.com/a/36949791 896 | var string = new TextDecoder().decode(file); 897 | console.log('filecontents are:', string); 898 | } 899 | 900 | function getfnames() { 901 | var fnames = JSON.parse(localStorage.getItem('fnames') || '{}'); 902 | return fnames; 903 | } 904 | -------------------------------------------------------------------------------- /JavaScript/vimwasm.min.js: -------------------------------------------------------------------------------- 1 | export const VIM_VERSION="8.2.0055";const AsyncFunction=Object.getPrototypeOf(async function(){}).constructor;function noop(){}let debug=noop;const STATUS_NOT_SET=0;const STATUS_NOTIFY_KEY=1;const STATUS_NOTIFY_RESIZE=2;const STATUS_NOTIFY_OPEN_FILE_BUF_COMPLETE=3;const STATUS_NOTIFY_CLIPBOARD_WRITE_COMPLETE=4;const STATUS_REQUEST_CMDLINE=5;const STATUS_REQUEST_SHARED_BUF=6;const STATUS_NOTIFY_ERROR_OUTPUT=7;const STATUS_NOTIFY_EVAL_FUNC_RET=8;function statusName(s){switch(s){case STATUS_NOT_SET:return"NOT_SET";case STATUS_NOTIFY_KEY:return"NOTIFY_KEY";case STATUS_NOTIFY_RESIZE:return"NOTIFY_RESIZE";case STATUS_NOTIFY_OPEN_FILE_BUF_COMPLETE:return"NOTIFY_OPEN_FILE_BUF_COMPLETE";case STATUS_NOTIFY_CLIPBOARD_WRITE_COMPLETE:return"NOTIFY_CLIPBOARD_WRITE_COMPLETE";case STATUS_REQUEST_CMDLINE:return"REQUEST_CMDLINE";case STATUS_REQUEST_SHARED_BUF:return"REQUEST_SHARED_BUF";case STATUS_NOTIFY_ERROR_OUTPUT:return"NOTIFY_ERROR_OUTPUT";case STATUS_NOTIFY_EVAL_FUNC_RET:return"STATUS_NOTIFY_EVAL_FUNC_RET";default:return`Unknown command: ${s}`}}export function checkBrowserCompatibility(){function notSupported(feat){return`${feat} is not supported by this browser. If you're using Firefox or Safari, please enable feature flag.`}if(typeof SharedArrayBuffer==="undefined"){return notSupported("SharedArrayBuffer")}if(typeof Atomics==="undefined"){return notSupported("Atomics API")}return undefined};export class VimWorker{constructor(scriptPath,onMessage,onError){this.worker=new Worker(scriptPath);this.worker.onmessage=this.recvMessage.bind(this);this.worker.onerror=this.recvError.bind(this);this.sharedBuffer=new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT*128));this.onMessage=onMessage;this.onError=onError;this.onOneshotMessage=new Map;this.debug=false;this.pendingEvents=[]}terminate(){this.worker.terminate();this.worker.onmessage=null;debug("Terminated worker thread. Thank you for working hard!")}sendStartMessage(msg){this.worker.postMessage(msg);debug("Sent start message",msg)}notifyOpenFileBufComplete(filename,bufId){this.enqueueEvent(STATUS_NOTIFY_OPEN_FILE_BUF_COMPLETE,bufId,filename)}notifyClipboardWriteComplete(cannotSend,bufId){this.enqueueEvent(STATUS_NOTIFY_CLIPBOARD_WRITE_COMPLETE,cannotSend,bufId)}notifyKeyEvent(key,keyCode,ctrl,shift,alt,meta){this.enqueueEvent(STATUS_NOTIFY_KEY,keyCode,ctrl,shift,alt,meta,key)}notifyResizeEvent(width,height){this.enqueueEvent(STATUS_NOTIFY_RESIZE,width,height)}async requestSharedBuffer(byteLength){this.enqueueEvent(STATUS_REQUEST_SHARED_BUF,byteLength);const msg=await this.waitForOneshotMessage("shared-buf:response");if(msg.buffer.byteLength!==byteLength){throw new Error(`Size of shared buffer from worker ${msg.buffer.byteLength} bytes mismatches to requested size ${byteLength} bytes`)}return[msg.bufId,msg.buffer]}notifyClipboardError(){this.notifyClipboardWriteComplete(true,0);debug("Reading clipboard failed. Notify it to worker")}async responseClipboardText(text){const encoded=(new TextEncoder).encode(text);const[bufId,buffer]=await this.requestSharedBuffer(encoded.byteLength+1);new Uint8Array(buffer).set(encoded);this.notifyClipboardWriteComplete(false,bufId);debug("Wrote clipboard",encoded.byteLength,"bytes text and notified to worker")}async requestCmdline(cmdline){if(cmdline.length===0){throw new Error("Specified command line is empty")}this.enqueueEvent(STATUS_REQUEST_CMDLINE,cmdline);const msg=await this.waitForOneshotMessage("cmdline:response");debug("Result of command",cmdline,":",msg.success);if(!msg.success){throw Error(`Command '${cmdline}' was invalid and not accepted by Vim`)}}async notifyErrorOutput(message){const encoded=(new TextEncoder).encode(message);const[bufId,buffer]=await this.requestSharedBuffer(encoded.byteLength);new Uint8Array(buffer).set(encoded);this.enqueueEvent(STATUS_NOTIFY_ERROR_OUTPUT,bufId);debug("Sent error message output:",message)}async notifyEvalFuncRet(ret){const encoded=(new TextEncoder).encode(ret);const[bufId,buffer]=await this.requestSharedBuffer(encoded.byteLength);new Uint8Array(buffer).set(encoded);this.enqueueEvent(STATUS_NOTIFY_EVAL_FUNC_RET,false,bufId);debug("Sent return value of evaluated JS function:",ret)}async notifyEvalFuncError(msg,err,dontReply){const errmsg=`${msg} for jsevalfunc(): ${err.message}: ${err.stack}`;if(dontReply){debug("Will send error output from jsevalfunc() though the invocation was notify-only:",errmsg);return this.notifyErrorOutput(errmsg)}const encoded=(new TextEncoder).encode("E9999: "+errmsg);const[bufId,buffer]=await this.requestSharedBuffer(encoded.byteLength);new Uint8Array(buffer).set(encoded);this.enqueueEvent(STATUS_NOTIFY_EVAL_FUNC_RET,true,bufId);debug("Sent exception thrown by evaluated JS function:",msg,err)}onEventDone(doneStatus){const done=statusName(doneStatus);const finished=this.pendingEvents.shift();if(finished===undefined){throw new Error(`FATAL: Received ${done} event but event queue is empty`)}if(finished[0]!==doneStatus){throw new Error(`FATAL: Received ${done} event but queue says previous event was ${statusName(finished[0])} with args ${finished[1]}`)}if(this.pendingEvents.length===0){debug("No pending event remains after event",done);return}debug("After",done,"event, still",this.pendingEvents.length,"events are pending");const[status,values]=this.pendingEvents[0];this.sendEvent(status,values)}enqueueEvent(status,...values){this.pendingEvents.push([status,values]);if(this.pendingEvents.length>1){debug("Other event is being handled by worker. Pending:",statusName(status),values);return}this.sendEvent(status,values)}sendEvent(status,values){const event=statusName(status);if(this.debug){const status=Atomics.load(this.sharedBuffer,0);if(status!==STATUS_NOT_SET){console.error("INVARIANT ERROR! Status byte must be zero cleared:",event)}}debug("Write event",event,"payload to buffer:",values);let idx=0;this.sharedBuffer[idx++]=status;for(const value of values){switch(typeof value){case"string":idx=this.encodeStringToBuffer(value,idx);break;case"number":this.sharedBuffer[idx++]=value;break;case"boolean":this.sharedBuffer[idx++]=+value;break;default:throw new Error(`FATAL: Invalid value for payload to worker: ${value}`)}}debug("Wrote",idx*4,"bytes to buffer for event",event);Atomics.notify(this.sharedBuffer,0,1);debug("Notified event",event,"to worker")}async waitForOneshotMessage(kind){return new Promise(resolve=>{this.onOneshotMessage.set(kind,resolve)})}encodeStringToBuffer(s,startIdx){let idx=startIdx;const len=s.length;this.sharedBuffer[idx++]=len;for(let i=0;i{this.bounceTimerToken=null;this.doResize()},500)}};export class InputHandler{constructor(worker,input){this.worker=worker;this.elem=input;this.onKeydown=this.onKeydown.bind(this);this.onBlur=this.onBlur.bind(this);this.onFocus=this.onFocus.bind(this);this.focus()}setFont(name,size){this.elem.style.fontFamily=name;this.elem.style.fontSize=`${size}px`}focus(){this.elem.focus()}onVimInit(){this.elem.addEventListener("keydown",this.onKeydown,{capture:true});this.elem.addEventListener("blur",this.onBlur);this.elem.addEventListener("focus",this.onFocus)}onVimExit(){this.elem.removeEventListener("keydown",this.onKeydown);this.elem.removeEventListener("blur",this.onBlur);this.elem.removeEventListener("focus",this.onFocus)}onKeydown(event){event.preventDefault();event.stopPropagation();debug("onKeydown():",event,event.key,event.keyCode);let key=event.key;const ctrl=event.ctrlKey;const shift=event.shiftKey;const alt=event.altKey;const meta=event.metaKey;if(key.length>1){if(key==="Unidentified"||ctrl&&key==="Control"||shift&&key==="Shift"||alt&&key==="Alt"||meta&&key==="Meta"){debug("Ignore key input",key);return}}if(key==="¥"||!shift&&key==="|"&&event.code==="IntlYen"){key="\\"}this.worker.notifyKeyEvent(key,event.keyCode,ctrl,shift,alt,meta)}onFocus(){debug("onFocus()")}onBlur(event){debug("onBlur():",event);event.preventDefault()}};export class ScreenCanvas{constructor(worker,canvas,input){this.worker=worker;this.canvas=canvas;const ctx=this.canvas.getContext("2d",{alpha:false});if(ctx===null){throw new Error("Cannot get 2D context for ")}this.ctx=ctx;const rect=this.canvas.getBoundingClientRect();const res=window.devicePixelRatio||1;this.canvas.width=rect.width*res;this.canvas.height=rect.height*res;this.canvas.addEventListener("click",this.onClick.bind(this),{capture:true,passive:true});this.input=new InputHandler(this.worker,input);this.resizer=new ResizeHandler(rect.width,rect.height,canvas,worker);this.onAnimationFrame=this.onAnimationFrame.bind(this);this.queue=[];this.rafScheduled=false;this.perf=false;this.fgColor="";this.spColor="";this.fontName=""}onVimInit(){this.input.onVimInit();this.resizer.onVimInit()}onVimExit(){this.input.onVimExit();this.resizer.onVimExit()}draw(msg){if(!this.rafScheduled){window.requestAnimationFrame(this.onAnimationFrame);this.rafScheduled=true}this.queue.push(msg)}focus(){this.input.focus()}getDomSize(){return{width:this.resizer.elemWidth,height:this.resizer.elemHeight}}setPerf(enabled){this.perf=enabled}setColorFG(name){this.fgColor=name}setColorBG(_name){}setColorSP(name){this.spColor=name}setFont(name,size){this.fontName=name;this.input.setFont(name,size)}drawRect(x,y,w,h,color,filled){const dpr=window.devicePixelRatio||1;x=Math.floor(x*dpr);y=Math.floor(y*dpr);w=Math.floor(w*dpr);h=Math.floor(h*dpr);this.ctx.fillStyle=color;if(filled){this.ctx.fillRect(x,y,w,h)}else{this.ctx.rect(x,y,w,h)}}drawText(text,ch,lh,cw,x,y,bold,underline,undercurl,strike){const dpr=window.devicePixelRatio||1;ch=ch*dpr;lh=lh*dpr;cw=cw*dpr;x=x*dpr;y=y*dpr;let font=`${Math.floor(ch)}px ${this.fontName}`;if(bold){font="bold "+font}this.ctx.font=font;this.ctx.textBaseline="bottom";this.ctx.fillStyle=this.fgColor;const descent=(lh-ch)/2;const yi=Math.floor(y+lh-descent);for(let i=0;i1){if(key==="Unidentified"||ctrl&&key==="Control"||shift&&key==="Shift"||alt&&key==="Alt"||meta&&key==="Meta"){debug("Ignore key input",key);return}}this.worker.notifyKeyEvent(key,keyCode,ctrl,shift,alt,meta)}cmdline(cmdline){return this.worker.requestCmdline(cmdline)}isRunning(){return this.running}focus(){this.screen.focus()}showError(message){return this.worker.notifyErrorOutput(message)}async readFile(reader,file){return new Promise((resolve,reject)=>{reader.onload=(f=>{debug("Read file",file.name,"from D&D:",f);resolve([file.name,reader.result])});reader.onerror=(()=>{reader.abort();reject(new Error(`Error on loading file ${file}`))});reader.readAsArrayBuffer(file)})}async evalJS(path,contents){debug("Evaluating JavaScript file",path,"with size",contents.byteLength,"bytes");const dec=new TextDecoder;const src='"use strict";'+dec.decode(contents);try{Function(src)()}catch(err){debug("Failed to evaluate",path,"with error:",err);await this.showError(`${err.message}\n\n${err.stack}`)}}async evalFunc(body,args,notifyOnly){debug("Evaluating JavaScript function:",body,args);let f;try{f=new AsyncFunction(body)}catch(err){return this.worker.notifyEvalFuncError("Could not construct function",err,notifyOnly)}let ret;try{ret=await f(...args)}catch(err){return this.worker.notifyEvalFuncError("Exception was thrown while evaluating function",err,notifyOnly)}if(notifyOnly){debug("Evaluated JavaScript result was discarded since the message was notify-only:",ret,body);return Promise.resolve()}let retJson;try{retJson=JSON.stringify(ret)}catch(err){return this.worker.notifyEvalFuncError("Could not serialize return value as JSON from function",err,false)}return this.worker.notifyEvalFuncRet(retJson)}onMessage(msg){if(this.perf&&msg.timestamp!==undefined){const duration=Date.now()-msg.timestamp;const name=msg.kind==="draw"?`draw:${msg.event[0]}`:msg.kind;const timestamps=this.perfMessages[name];if(timestamps===undefined){this.perfMessages[name]=[duration]}else{this.perfMessages[name].push(duration)}}switch(msg.kind){case"draw":this.screen.draw(msg.event);debug("draw event",msg.event);break;case"done":this.worker.onEventDone(msg.status);break;case"evalfunc":{const args=msg.argsJson===undefined?[]:JSON.parse(msg.argsJson);this.evalFunc(msg.body,args,msg.notifyOnly).catch(this.handleError);break}case"title":if(this.onTitleUpdate){debug("title was updated:",msg.title);this.onTitleUpdate(msg.title)}break;case"read-clipboard:request":if(this.readClipboard){this.readClipboard().then(text=>this.worker.responseClipboardText(text)).catch(err=>{debug("Cannot read clipboard:",err);this.worker.notifyClipboardError()})}else{debug("Cannot read clipboard because VimWasm.readClipboard is not set");this.worker.notifyClipboardError()}break;case"write-clipboard":debug("Handle writing text",msg.text,"to clipboard with",this.onWriteClipboard);if(this.onWriteClipboard){this.onWriteClipboard(msg.text)}break;case"export":if(this.onFileExport!==undefined){debug("Exporting file",msg.path,"with size in bytes",msg.contents.byteLength);this.onFileExport(msg.path,msg.contents)}break;case"eval":this.evalJS(msg.path,msg.contents).catch(this.handleError);break;case"started":this.screen.onVimInit();if(this.onVimInit){this.onVimInit()}this.perfMeasure("init");debug("Vim started");break;case"exit":this.screen.onVimExit();this.printPerfs();this.worker.terminate();if(this.onVimExit){this.onVimExit(msg.status)}debug("Vim exited with status",msg.status);this.perf=false;this.debug=false;this.screen.setPerf(false);this.running=false;this.end=true;break;case"error":debug("Vim threw an error:",msg.message);this.handleError(new Error(msg.message));this.worker.terminate();break;default:throw new Error(`Unexpected message from worker: ${JSON.stringify(msg)}`)}}handleError(err){if(this.onError){this.onError(err)}}printPerfs(){if(!this.perf){return}{const measurements=new Map;for(const e of performance.getEntries()){const ms=measurements.get(e.name);if(ms===undefined){measurements.set(e.name,[e])}else{ms.push(e)}}const averages={};const amounts={};const timings=[];for(const[name,ms]of measurements){if(ms.length===1&&ms[0].entryType!=="measure"){timings.push(ms[0]);continue}console.log(`%c${name}`,"color: green; font-size: large");console.table(ms,["duration","startTime"]);const total=ms.reduce((a,m)=>a+m.duration,0);averages[name]=total/ms.length;amounts[name]=total}console.log("%cTimings (ms)","color: green; font-size: large");console.table(timings,["name","entryType","startTime","duration"]);console.log("%cAmount: Perf Mark Durations (ms)","color: green; font-size: large");console.table(amounts);console.log("%cAverage: Perf Mark Durations (ms)","color: green; font-size: large");console.table(averages);performance.clearMarks();performance.clearMeasures()}{const averages={};for(const name of Object.keys(this.perfMessages)){const durations=this.perfMessages[name];const total=durations.reduce((a,d)=>a+d,0);averages[name]=total/durations.length}console.log("%cAverage: Inter-thread Messages Duration (ms)","color: green; font-size: large");console.table(averages);this.perfMessages={}}}perfMark(m){if(this.perf){performance.mark(m)}}perfMeasure(m){if(this.perf){performance.measure(m,m);performance.clearMarks(m)}}}; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the open source code repository for the web app at [Online Vim Editor](https://vimonlineeditor.com/) 2 | -------------------------------------------------------------------------------- /fs.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programmerhat/vim-online-editor/5bb574bca71e4076829a5ec455dd9bdd88967f3e/fs.txt -------------------------------------------------------------------------------- /img/btn_donate_SM.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programmerhat/vim-online-editor/5bb574bca71e4076829a5ec455dd9bdd88967f3e/img/btn_donate_SM.gif -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | Vim Online Editor - Vim Editor In Browser 9 | 10 | 11 | 12 | 13 | 14 |

Vim Online Editor (beta)

15 | 16 |

17 | Hey! This is still in beta, which means LOTS of exciting new features are being developed! And yes, it's OPEN SOURCED! You can check it out on Github: programmerhat/vim-online-editor 18 |

19 | 20 |

21 | Got a feature request? I'd love to hear it! I'm on Twitter @programmerhat. I will occasionally do product decisions via polling on Twitter! You can also send an email to hello@programmerhat.com. 22 |

23 | 24 |

NOTE that this is downloading 2 MB, so give it a second for it to fully download.

25 |
26 | 27 |
31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 |
40 | 41 |
42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 132 | 133 | 134 | 143 | 144 | 145 | 146 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /index/About.html: -------------------------------------------------------------------------------- 1 |

What is the Vim Online Editor?

2 | 3 |

4 | This is "Vim Online", a vim editor in browser. It's a online vim editor that allows you can install your vimrc, and this app will remember your vimrc between visits to a vim editor online. 5 |

6 | 7 |

8 | The Online Vim Editor is building off groundbreaking efforts by @rhysd and @coolwanglu to bring vim to the browser. 9 |

10 | 11 |

12 | While those projects did a great job getting started on an online vim editor, there are still many missing pieces. The most important missing feature in my opinion is being able to install a vimrc to your vim editor online get back all the keybindings you're used to. 13 |

14 | 15 |

16 | Another really important missing feature of a vim editor online is being able to save files easily and navigate between files easily. 17 |

18 | 19 |

20 | Another really important feature of a vim editor online is being able to git clone a repo into the browser. 21 |

22 | 23 |

24 | What would really be cool is being able to edit files from the filesystem, using the WASI API. 25 |

26 | 27 |

28 | Even if direct access to the filesystem isn't possible, an autosync with the source code so that you could easily test the code would be super cool. 29 |

30 | 31 |

What is the vision for the Vim Editor Online?

32 | 33 |

I'm thinking this is going to take inspiration from these projects:

34 | 35 |
    36 |
  • gvim
  • 37 |
  • online notepad
  • 38 |
39 | 40 |

41 | This project is going to use vim.wasm as a starting point because that project actually supports clipboard. 42 |

43 | 44 |

45 | Unfortunately, the vim.wasm project by rhysd appears not to have had any serious progress for several years. Not since Sep 18, 2021. I last checked Dec 16, 2022. 46 |

47 | 48 |

Who made the Vim Online Editor?

49 | 50 |

51 | "Vim Online" was built by the lovely folks at programmerhat.com. 52 |

53 | 54 |

55 | We happen to be huge Vim enthusiasts ourselves. Using Vim for many years. It's the best editor in the world. 56 |

57 | 58 |

59 | And of course we're software engineers! We like building software. 60 |

61 | 62 |

63 | What we'd LOVE to hear from you are FEATURE REQUESTS! We know there's a lot of work needed to make this as good as the Vim you're used to in the terminal. So interact with me on Twitter @programmerhat or send an email to hello@programmerhat.com 64 |

65 | 66 |

How to use Vim Online Editor?

67 | 68 |

69 | If you've got your own vimrc, you'll probably want to install that straight away. Click on "Load vimrc", then copy your vimrc, and click "Paste" to install your vimrc. Do ":write" and reload the tab. The vimrc will be installed. 70 |

71 | 72 |

73 | Caveat is that this app currently does not support plugins. 74 |

75 | 76 |

77 | Then just click the vim box and you're good to go! 78 |

79 | 80 |

81 | It's free. Doesn't cost anything. There will be some ads to help fund feature development. There are a lot of features I want to build. 82 |

83 | 84 |

Why use Vim Online Editor?

85 | 86 |
    87 |
  1. Because you love vim.
  2. 88 |
  3. Because you don't have access to vim somehow (maybe you're on a Chromebook that doesn't allow access to the system)
  4. 89 |
  5. Especially if you're on Windows and you still want to use vim.
  6. 90 |
  7. Because you want a notepad of some sort in the browser, and you want to use vim bindings instead of normal notepad.
  8. 91 |
      92 | -------------------------------------------------------------------------------- /index/ChangeLog.html: -------------------------------------------------------------------------------- 1 |

      Changelog

      2 | 3 | We used github.com/rhysd/vim.wasm as a starting point. However there were a lot of missing stuff. This is a changelog of stuff we added. 4 | 5 |
        6 | 7 |
      • Rename vim.data.bmp to fs.txt, because bmp files were not getting compressed by the web server for some reason.
      • 8 |
      • different starting screen
      • 9 |
      • make vim's starting directory $HOME instead of `/`
      • 10 |
      • Make NEW files persist between edit sessions
      • 11 |
      • Make "tryit.js" persist between edit sessions
      • 12 |
      • Make vimrc persist on WRITE and not on vim quit
      • 13 |
      • Focus the editor properly to top of canvas instead of top of webpage when the canvas is clicked on.
      • 14 |
      • Make vim canvas fill up as much of the screen as possible
      • 15 |
      • One button click "Load vimrc" to configure vimrc. This was a dealbreaker for me to personally use vim online
      • 16 |
      • One button click to paste into vim. Make it super easy to paste a custom vimrc in
      • 17 |
      18 | -------------------------------------------------------------------------------- /index/TODOs.html: -------------------------------------------------------------------------------- 1 |

      Vim Editor Online TODO

      2 | 3 |

      4 | "github.com/rhysd/vim.wasm" is a great starting point. However we plan on adding 5 | a lot more features to make this as good as the vim you're used to. This is our TODO list. 6 |

      7 | 8 |
        9 | 10 |
      • TODO: "Upload file" button
      • 11 |
      • TODO: Able to directly edit the filesystem.
      • 12 |
      • TODO: Implement loading of vim sessions from a persistent file.
      • 13 |
      • TODO: Implement support for a plugin such as Vundle.
      • 14 |
      • TODO: mouse support
      • 15 |
      • TODO: vim command history (`:e ~/.vim` and uparrow should go up history)
      • 16 |
      • TODO: vim account management. Be able to open source code from anywhere in the world. Use vimrc across machines/browsers
      • 17 |
      • TODO: allow creation of new directories.
      • 18 |
      • TODO: When user loads a file not in filesystem, then load NEW files from IndexedDB. This is VERY technically challenging. Requires a sleep in the Web Worker to make IndexedDB call look synchronous.
      • 19 |
      • TODO: Make paste work in Vim's command line mode `:`
      • 20 |
      • TODO: check what is the string limit on Web Worker name, which impacts how large the filesytem can be.
      • 21 |
      22 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /styles/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | height: 100vh; 6 | width: 100vw; 7 | margin: 0px; 8 | padding: 0px; 9 | /* overflow: hidden; */ 10 | } 11 | 12 | #vim-editor { 13 | display: flex; 14 | padding: 2px; 15 | margin: 0px; 16 | width: calc(100vw - 50px); 17 | height: calc(100vh - 50px); 18 | background-color: #282c33; 19 | } 20 | 21 | #vim-screen { 22 | padding: 0px; 23 | width: calc(100vw - 50px); 24 | height: calc(100vh - 50px); 25 | } 26 | 27 | #vim-cursor { 28 | padding: 0px; 29 | margin: 0px; 30 | position: absolute; 31 | display: none; /* TODO */ 32 | } 33 | 34 | #vim-input { 35 | width: 1px; 36 | color: transparent; 37 | background-color: transparent; 38 | padding: 0px; 39 | border: 0px; 40 | outline: none; 41 | vertical-align: middle; 42 | display: inline-block; 43 | } 44 | 45 | #vim-preedit { 46 | visibility: hidden; 47 | position: fixed; 48 | display: none; /* TODO */ 49 | } 50 | -------------------------------------------------------------------------------- /styles/water.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Automatic version: 3 | * Uses light theme by default but switches to dark theme 4 | * if a system-wide theme preference is set on the user's device. 5 | */ 6 | 7 | :root { 8 | --background-body: #fff; 9 | --background: #efefef; 10 | --background-alt: #f7f7f7; 11 | --selection: #9e9e9e; 12 | --text-main: #363636; 13 | --text-bright: #000; 14 | --text-muted: #70777f; 15 | --links: #0076d1; 16 | --focus: #0096bfab; 17 | --border: #dbdbdb; 18 | --code: #000; 19 | --animation-duration: 0.1s; 20 | --button-base: #d0cfcf; 21 | --button-hover: #9b9b9b; 22 | --scrollbar-thumb: rgb(170, 170, 170); 23 | --scrollbar-thumb-hover: var(--button-hover); 24 | --form-placeholder: #949494; 25 | --form-text: #1d1d1d; 26 | --variable: #39a33c; 27 | --highlight: #ff0; 28 | --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); 29 | } 30 | 31 | @media (prefers-color-scheme: dark) { 32 | :root { 33 | --background-body: #202b38; 34 | --background: #161f27; 35 | --background-alt: #1a242f; 36 | --selection: #1c76c5; 37 | --text-main: #dbdbdb; 38 | --text-bright: #fff; 39 | --text-muted: #a9b1ba; 40 | --links: #41adff; 41 | --focus: #0096bfab; 42 | --border: #526980; 43 | --code: #ffbe85; 44 | --animation-duration: 0.1s; 45 | --button-base: #0c151c; 46 | --button-hover: #040a0f; 47 | --scrollbar-thumb: var(--button-hover); 48 | --scrollbar-thumb-hover: rgb(0, 0, 0); 49 | --form-placeholder: #a9a9a9; 50 | --form-text: #fff; 51 | --variable: #d941e2; 52 | --highlight: #efdb43; 53 | --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E"); 54 | } 55 | } 56 | 57 | html { 58 | scrollbar-color: rgb(170, 170, 170) #fff; 59 | scrollbar-color: var(--scrollbar-thumb) var(--background-body); 60 | scrollbar-width: thin; 61 | } 62 | 63 | @media (prefers-color-scheme: dark) { 64 | 65 | html { 66 | scrollbar-color: #040a0f #202b38; 67 | scrollbar-color: var(--scrollbar-thumb) var(--background-body); 68 | } 69 | } 70 | 71 | @media (prefers-color-scheme: dark) { 72 | 73 | html { 74 | scrollbar-color: #040a0f #202b38; 75 | scrollbar-color: var(--scrollbar-thumb) var(--background-body); 76 | } 77 | } 78 | 79 | @media (prefers-color-scheme: dark) { 80 | 81 | html { 82 | scrollbar-color: #040a0f #202b38; 83 | scrollbar-color: var(--scrollbar-thumb) var(--background-body); 84 | } 85 | } 86 | 87 | @media (prefers-color-scheme: dark) { 88 | 89 | html { 90 | scrollbar-color: #040a0f #202b38; 91 | scrollbar-color: var(--scrollbar-thumb) var(--background-body); 92 | } 93 | } 94 | 95 | @media (prefers-color-scheme: dark) { 96 | 97 | html { 98 | scrollbar-color: #040a0f #202b38; 99 | scrollbar-color: var(--scrollbar-thumb) var(--background-body); 100 | } 101 | } 102 | 103 | @media (prefers-color-scheme: dark) { 104 | 105 | html { 106 | scrollbar-color: #040a0f #202b38; 107 | scrollbar-color: var(--scrollbar-thumb) var(--background-body); 108 | } 109 | } 110 | 111 | body { 112 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif; 113 | line-height: 1.4; 114 | max-width: 800px; 115 | margin: 20px auto; 116 | padding: 0 10px; 117 | word-wrap: break-word; 118 | color: #363636; 119 | color: var(--text-main); 120 | background: #fff; 121 | background: var(--background-body); 122 | text-rendering: optimizeLegibility; 123 | } 124 | 125 | @media (prefers-color-scheme: dark) { 126 | 127 | body { 128 | background: #202b38; 129 | background: var(--background-body); 130 | } 131 | } 132 | 133 | @media (prefers-color-scheme: dark) { 134 | 135 | body { 136 | color: #dbdbdb; 137 | color: var(--text-main); 138 | } 139 | } 140 | 141 | button { 142 | transition: 143 | background-color 0.1s linear, 144 | border-color 0.1s linear, 145 | color 0.1s linear, 146 | box-shadow 0.1s linear, 147 | transform 0.1s ease; 148 | transition: 149 | background-color var(--animation-duration) linear, 150 | border-color var(--animation-duration) linear, 151 | color var(--animation-duration) linear, 152 | box-shadow var(--animation-duration) linear, 153 | transform var(--animation-duration) ease; 154 | } 155 | 156 | @media (prefers-color-scheme: dark) { 157 | 158 | button { 159 | transition: 160 | background-color 0.1s linear, 161 | border-color 0.1s linear, 162 | color 0.1s linear, 163 | box-shadow 0.1s linear, 164 | transform 0.1s ease; 165 | transition: 166 | background-color var(--animation-duration) linear, 167 | border-color var(--animation-duration) linear, 168 | color var(--animation-duration) linear, 169 | box-shadow var(--animation-duration) linear, 170 | transform var(--animation-duration) ease; 171 | } 172 | } 173 | 174 | input { 175 | transition: 176 | background-color 0.1s linear, 177 | border-color 0.1s linear, 178 | color 0.1s linear, 179 | box-shadow 0.1s linear, 180 | transform 0.1s ease; 181 | transition: 182 | background-color var(--animation-duration) linear, 183 | border-color var(--animation-duration) linear, 184 | color var(--animation-duration) linear, 185 | box-shadow var(--animation-duration) linear, 186 | transform var(--animation-duration) ease; 187 | } 188 | 189 | @media (prefers-color-scheme: dark) { 190 | 191 | input { 192 | transition: 193 | background-color 0.1s linear, 194 | border-color 0.1s linear, 195 | color 0.1s linear, 196 | box-shadow 0.1s linear, 197 | transform 0.1s ease; 198 | transition: 199 | background-color var(--animation-duration) linear, 200 | border-color var(--animation-duration) linear, 201 | color var(--animation-duration) linear, 202 | box-shadow var(--animation-duration) linear, 203 | transform var(--animation-duration) ease; 204 | } 205 | } 206 | 207 | textarea { 208 | transition: 209 | background-color 0.1s linear, 210 | border-color 0.1s linear, 211 | color 0.1s linear, 212 | box-shadow 0.1s linear, 213 | transform 0.1s ease; 214 | transition: 215 | background-color var(--animation-duration) linear, 216 | border-color var(--animation-duration) linear, 217 | color var(--animation-duration) linear, 218 | box-shadow var(--animation-duration) linear, 219 | transform var(--animation-duration) ease; 220 | } 221 | 222 | @media (prefers-color-scheme: dark) { 223 | 224 | textarea { 225 | transition: 226 | background-color 0.1s linear, 227 | border-color 0.1s linear, 228 | color 0.1s linear, 229 | box-shadow 0.1s linear, 230 | transform 0.1s ease; 231 | transition: 232 | background-color var(--animation-duration) linear, 233 | border-color var(--animation-duration) linear, 234 | color var(--animation-duration) linear, 235 | box-shadow var(--animation-duration) linear, 236 | transform var(--animation-duration) ease; 237 | } 238 | } 239 | 240 | h1 { 241 | font-size: 2.2em; 242 | margin-top: 0; 243 | } 244 | 245 | h1, 246 | h2, 247 | h3, 248 | h4, 249 | h5, 250 | h6 { 251 | margin-bottom: 12px; 252 | margin-top: 24px; 253 | } 254 | 255 | h1 { 256 | color: #000; 257 | color: var(--text-bright); 258 | } 259 | 260 | @media (prefers-color-scheme: dark) { 261 | 262 | h1 { 263 | color: #fff; 264 | color: var(--text-bright); 265 | } 266 | } 267 | 268 | h2 { 269 | color: #000; 270 | color: var(--text-bright); 271 | } 272 | 273 | @media (prefers-color-scheme: dark) { 274 | 275 | h2 { 276 | color: #fff; 277 | color: var(--text-bright); 278 | } 279 | } 280 | 281 | h3 { 282 | color: #000; 283 | color: var(--text-bright); 284 | } 285 | 286 | @media (prefers-color-scheme: dark) { 287 | 288 | h3 { 289 | color: #fff; 290 | color: var(--text-bright); 291 | } 292 | } 293 | 294 | h4 { 295 | color: #000; 296 | color: var(--text-bright); 297 | } 298 | 299 | @media (prefers-color-scheme: dark) { 300 | 301 | h4 { 302 | color: #fff; 303 | color: var(--text-bright); 304 | } 305 | } 306 | 307 | h5 { 308 | color: #000; 309 | color: var(--text-bright); 310 | } 311 | 312 | @media (prefers-color-scheme: dark) { 313 | 314 | h5 { 315 | color: #fff; 316 | color: var(--text-bright); 317 | } 318 | } 319 | 320 | h6 { 321 | color: #000; 322 | color: var(--text-bright); 323 | } 324 | 325 | @media (prefers-color-scheme: dark) { 326 | 327 | h6 { 328 | color: #fff; 329 | color: var(--text-bright); 330 | } 331 | } 332 | 333 | strong { 334 | color: #000; 335 | color: var(--text-bright); 336 | } 337 | 338 | @media (prefers-color-scheme: dark) { 339 | 340 | strong { 341 | color: #fff; 342 | color: var(--text-bright); 343 | } 344 | } 345 | 346 | h1, 347 | h2, 348 | h3, 349 | h4, 350 | h5, 351 | h6, 352 | b, 353 | strong, 354 | th { 355 | font-weight: 600; 356 | } 357 | 358 | q::before { 359 | content: none; 360 | } 361 | 362 | q::after { 363 | content: none; 364 | } 365 | 366 | blockquote { 367 | border-left: 4px solid #0096bfab; 368 | border-left: 4px solid var(--focus); 369 | margin: 1.5em 0; 370 | padding: 0.5em 1em; 371 | font-style: italic; 372 | } 373 | 374 | @media (prefers-color-scheme: dark) { 375 | 376 | blockquote { 377 | border-left: 4px solid #0096bfab; 378 | border-left: 4px solid var(--focus); 379 | } 380 | } 381 | 382 | q { 383 | border-left: 4px solid #0096bfab; 384 | border-left: 4px solid var(--focus); 385 | margin: 1.5em 0; 386 | padding: 0.5em 1em; 387 | font-style: italic; 388 | } 389 | 390 | @media (prefers-color-scheme: dark) { 391 | 392 | q { 393 | border-left: 4px solid #0096bfab; 394 | border-left: 4px solid var(--focus); 395 | } 396 | } 397 | 398 | blockquote > footer { 399 | font-style: normal; 400 | border: 0; 401 | } 402 | 403 | blockquote cite { 404 | font-style: normal; 405 | } 406 | 407 | address { 408 | font-style: normal; 409 | } 410 | 411 | a[href^='mailto\:']::before { 412 | content: '📧 '; 413 | } 414 | 415 | a[href^='tel\:']::before { 416 | content: '📞 '; 417 | } 418 | 419 | a[href^='sms\:']::before { 420 | content: '💬 '; 421 | } 422 | 423 | mark { 424 | background-color: #ff0; 425 | background-color: var(--highlight); 426 | border-radius: 2px; 427 | padding: 0 2px 0 2px; 428 | color: #000; 429 | } 430 | 431 | @media (prefers-color-scheme: dark) { 432 | 433 | mark { 434 | background-color: #efdb43; 435 | background-color: var(--highlight); 436 | } 437 | } 438 | 439 | a > code, 440 | a > strong { 441 | color: inherit; 442 | } 443 | 444 | button, 445 | select, 446 | input[type='submit'], 447 | input[type='reset'], 448 | input[type='button'], 449 | input[type='checkbox'], 450 | input[type='range'], 451 | input[type='radio'] { 452 | cursor: pointer; 453 | } 454 | 455 | input, 456 | select { 457 | display: block; 458 | } 459 | 460 | [type='checkbox'], 461 | [type='radio'] { 462 | display: initial; 463 | } 464 | 465 | input { 466 | color: #1d1d1d; 467 | color: var(--form-text); 468 | background-color: #efefef; 469 | background-color: var(--background); 470 | font-family: inherit; 471 | font-size: inherit; 472 | margin-right: 6px; 473 | margin-bottom: 6px; 474 | padding: 10px; 475 | border: none; 476 | border-radius: 6px; 477 | outline: none; 478 | } 479 | 480 | @media (prefers-color-scheme: dark) { 481 | 482 | input { 483 | background-color: #161f27; 484 | background-color: var(--background); 485 | } 486 | } 487 | 488 | @media (prefers-color-scheme: dark) { 489 | 490 | input { 491 | color: #fff; 492 | color: var(--form-text); 493 | } 494 | } 495 | 496 | button { 497 | color: #1d1d1d; 498 | color: var(--form-text); 499 | background-color: #efefef; 500 | background-color: var(--background); 501 | font-family: inherit; 502 | font-size: inherit; 503 | margin-right: 6px; 504 | margin-bottom: 6px; 505 | padding: 10px; 506 | border: none; 507 | border-radius: 6px; 508 | outline: none; 509 | } 510 | 511 | @media (prefers-color-scheme: dark) { 512 | 513 | button { 514 | background-color: #161f27; 515 | background-color: var(--background); 516 | } 517 | } 518 | 519 | @media (prefers-color-scheme: dark) { 520 | 521 | button { 522 | color: #fff; 523 | color: var(--form-text); 524 | } 525 | } 526 | 527 | textarea { 528 | color: #1d1d1d; 529 | color: var(--form-text); 530 | background-color: #efefef; 531 | background-color: var(--background); 532 | font-family: inherit; 533 | font-size: inherit; 534 | margin-right: 6px; 535 | margin-bottom: 6px; 536 | padding: 10px; 537 | border: none; 538 | border-radius: 6px; 539 | outline: none; 540 | } 541 | 542 | @media (prefers-color-scheme: dark) { 543 | 544 | textarea { 545 | background-color: #161f27; 546 | background-color: var(--background); 547 | } 548 | } 549 | 550 | @media (prefers-color-scheme: dark) { 551 | 552 | textarea { 553 | color: #fff; 554 | color: var(--form-text); 555 | } 556 | } 557 | 558 | select { 559 | color: #1d1d1d; 560 | color: var(--form-text); 561 | background-color: #efefef; 562 | background-color: var(--background); 563 | font-family: inherit; 564 | font-size: inherit; 565 | margin-right: 6px; 566 | margin-bottom: 6px; 567 | padding: 10px; 568 | border: none; 569 | border-radius: 6px; 570 | outline: none; 571 | } 572 | 573 | @media (prefers-color-scheme: dark) { 574 | 575 | select { 576 | background-color: #161f27; 577 | background-color: var(--background); 578 | } 579 | } 580 | 581 | @media (prefers-color-scheme: dark) { 582 | 583 | select { 584 | color: #fff; 585 | color: var(--form-text); 586 | } 587 | } 588 | 589 | button { 590 | background-color: #d0cfcf; 591 | background-color: var(--button-base); 592 | padding-right: 30px; 593 | padding-left: 30px; 594 | } 595 | 596 | @media (prefers-color-scheme: dark) { 597 | 598 | button { 599 | background-color: #0c151c; 600 | background-color: var(--button-base); 601 | } 602 | } 603 | 604 | input[type='submit'] { 605 | background-color: #d0cfcf; 606 | background-color: var(--button-base); 607 | padding-right: 30px; 608 | padding-left: 30px; 609 | } 610 | 611 | @media (prefers-color-scheme: dark) { 612 | 613 | input[type='submit'] { 614 | background-color: #0c151c; 615 | background-color: var(--button-base); 616 | } 617 | } 618 | 619 | input[type='reset'] { 620 | background-color: #d0cfcf; 621 | background-color: var(--button-base); 622 | padding-right: 30px; 623 | padding-left: 30px; 624 | } 625 | 626 | @media (prefers-color-scheme: dark) { 627 | 628 | input[type='reset'] { 629 | background-color: #0c151c; 630 | background-color: var(--button-base); 631 | } 632 | } 633 | 634 | input[type='button'] { 635 | background-color: #d0cfcf; 636 | background-color: var(--button-base); 637 | padding-right: 30px; 638 | padding-left: 30px; 639 | } 640 | 641 | @media (prefers-color-scheme: dark) { 642 | 643 | input[type='button'] { 644 | background-color: #0c151c; 645 | background-color: var(--button-base); 646 | } 647 | } 648 | 649 | button:hover { 650 | background: #9b9b9b; 651 | background: var(--button-hover); 652 | } 653 | 654 | @media (prefers-color-scheme: dark) { 655 | 656 | button:hover { 657 | background: #040a0f; 658 | background: var(--button-hover); 659 | } 660 | } 661 | 662 | input[type='submit']:hover { 663 | background: #9b9b9b; 664 | background: var(--button-hover); 665 | } 666 | 667 | @media (prefers-color-scheme: dark) { 668 | 669 | input[type='submit']:hover { 670 | background: #040a0f; 671 | background: var(--button-hover); 672 | } 673 | } 674 | 675 | input[type='reset']:hover { 676 | background: #9b9b9b; 677 | background: var(--button-hover); 678 | } 679 | 680 | @media (prefers-color-scheme: dark) { 681 | 682 | input[type='reset']:hover { 683 | background: #040a0f; 684 | background: var(--button-hover); 685 | } 686 | } 687 | 688 | input[type='button']:hover { 689 | background: #9b9b9b; 690 | background: var(--button-hover); 691 | } 692 | 693 | @media (prefers-color-scheme: dark) { 694 | 695 | input[type='button']:hover { 696 | background: #040a0f; 697 | background: var(--button-hover); 698 | } 699 | } 700 | 701 | input[type='color'] { 702 | min-height: 2rem; 703 | padding: 8px; 704 | cursor: pointer; 705 | } 706 | 707 | input[type='checkbox'], 708 | input[type='radio'] { 709 | height: 1em; 710 | width: 1em; 711 | } 712 | 713 | input[type='radio'] { 714 | border-radius: 100%; 715 | } 716 | 717 | input { 718 | vertical-align: top; 719 | } 720 | 721 | label { 722 | vertical-align: middle; 723 | margin-bottom: 4px; 724 | display: inline-block; 725 | } 726 | 727 | input:not([type='checkbox']):not([type='radio']), 728 | input[type='range'], 729 | select, 730 | button, 731 | textarea { 732 | -webkit-appearance: none; 733 | } 734 | 735 | textarea { 736 | display: block; 737 | margin-right: 0; 738 | box-sizing: border-box; 739 | resize: vertical; 740 | } 741 | 742 | textarea:not([cols]) { 743 | width: 100%; 744 | } 745 | 746 | textarea:not([rows]) { 747 | min-height: 40px; 748 | height: 140px; 749 | } 750 | 751 | select { 752 | background: #efefef url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23161f27'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; 753 | background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; 754 | padding-right: 35px; 755 | } 756 | 757 | @media (prefers-color-scheme: dark) { 758 | 759 | select { 760 | background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; 761 | background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; 762 | } 763 | } 764 | 765 | @media (prefers-color-scheme: dark) { 766 | 767 | select { 768 | background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; 769 | background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; 770 | } 771 | } 772 | 773 | @media (prefers-color-scheme: dark) { 774 | 775 | select { 776 | background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; 777 | background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; 778 | } 779 | } 780 | 781 | @media (prefers-color-scheme: dark) { 782 | 783 | select { 784 | background: #161f27 url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E") calc(100% - 12px) 50% / 12px no-repeat; 785 | background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat; 786 | } 787 | } 788 | 789 | select::-ms-expand { 790 | display: none; 791 | } 792 | 793 | select[multiple] { 794 | padding-right: 10px; 795 | background-image: none; 796 | overflow-y: auto; 797 | } 798 | 799 | input:focus { 800 | box-shadow: 0 0 0 2px #0096bfab; 801 | box-shadow: 0 0 0 2px var(--focus); 802 | } 803 | 804 | @media (prefers-color-scheme: dark) { 805 | 806 | input:focus { 807 | box-shadow: 0 0 0 2px #0096bfab; 808 | box-shadow: 0 0 0 2px var(--focus); 809 | } 810 | } 811 | 812 | select:focus { 813 | box-shadow: 0 0 0 2px #0096bfab; 814 | box-shadow: 0 0 0 2px var(--focus); 815 | } 816 | 817 | @media (prefers-color-scheme: dark) { 818 | 819 | select:focus { 820 | box-shadow: 0 0 0 2px #0096bfab; 821 | box-shadow: 0 0 0 2px var(--focus); 822 | } 823 | } 824 | 825 | button:focus { 826 | box-shadow: 0 0 0 2px #0096bfab; 827 | box-shadow: 0 0 0 2px var(--focus); 828 | } 829 | 830 | @media (prefers-color-scheme: dark) { 831 | 832 | button:focus { 833 | box-shadow: 0 0 0 2px #0096bfab; 834 | box-shadow: 0 0 0 2px var(--focus); 835 | } 836 | } 837 | 838 | textarea:focus { 839 | box-shadow: 0 0 0 2px #0096bfab; 840 | box-shadow: 0 0 0 2px var(--focus); 841 | } 842 | 843 | @media (prefers-color-scheme: dark) { 844 | 845 | textarea:focus { 846 | box-shadow: 0 0 0 2px #0096bfab; 847 | box-shadow: 0 0 0 2px var(--focus); 848 | } 849 | } 850 | 851 | input[type='checkbox']:active, 852 | input[type='radio']:active, 853 | input[type='submit']:active, 854 | input[type='reset']:active, 855 | input[type='button']:active, 856 | input[type='range']:active, 857 | button:active { 858 | transform: translateY(2px); 859 | } 860 | 861 | input:disabled, 862 | select:disabled, 863 | button:disabled, 864 | textarea:disabled { 865 | cursor: not-allowed; 866 | opacity: 0.5; 867 | } 868 | 869 | ::-moz-placeholder { 870 | color: #949494; 871 | color: var(--form-placeholder); 872 | } 873 | 874 | :-ms-input-placeholder { 875 | color: #949494; 876 | color: var(--form-placeholder); 877 | } 878 | 879 | ::-ms-input-placeholder { 880 | color: #949494; 881 | color: var(--form-placeholder); 882 | } 883 | 884 | ::placeholder { 885 | color: #949494; 886 | color: var(--form-placeholder); 887 | } 888 | 889 | @media (prefers-color-scheme: dark) { 890 | 891 | ::-moz-placeholder { 892 | color: #a9a9a9; 893 | color: var(--form-placeholder); 894 | } 895 | 896 | :-ms-input-placeholder { 897 | color: #a9a9a9; 898 | color: var(--form-placeholder); 899 | } 900 | 901 | ::-ms-input-placeholder { 902 | color: #a9a9a9; 903 | color: var(--form-placeholder); 904 | } 905 | 906 | ::placeholder { 907 | color: #a9a9a9; 908 | color: var(--form-placeholder); 909 | } 910 | } 911 | 912 | fieldset { 913 | border: 1px #0096bfab solid; 914 | border: 1px var(--focus) solid; 915 | border-radius: 6px; 916 | margin: 0; 917 | margin-bottom: 12px; 918 | padding: 10px; 919 | } 920 | 921 | @media (prefers-color-scheme: dark) { 922 | 923 | fieldset { 924 | border: 1px #0096bfab solid; 925 | border: 1px var(--focus) solid; 926 | } 927 | } 928 | 929 | legend { 930 | font-size: 0.9em; 931 | font-weight: 600; 932 | } 933 | 934 | input[type='range'] { 935 | margin: 10px 0; 936 | padding: 10px 0; 937 | background: transparent; 938 | } 939 | 940 | input[type='range']:focus { 941 | outline: none; 942 | } 943 | 944 | input[type='range']::-webkit-slider-runnable-track { 945 | width: 100%; 946 | height: 9.5px; 947 | -webkit-transition: 0.2s; 948 | transition: 0.2s; 949 | background: #efefef; 950 | background: var(--background); 951 | border-radius: 3px; 952 | } 953 | 954 | @media (prefers-color-scheme: dark) { 955 | 956 | input[type='range']::-webkit-slider-runnable-track { 957 | background: #161f27; 958 | background: var(--background); 959 | } 960 | } 961 | 962 | input[type='range']::-webkit-slider-thumb { 963 | box-shadow: 0 1px 1px #000, 0 0 1px #0d0d0d; 964 | height: 20px; 965 | width: 20px; 966 | border-radius: 50%; 967 | background: #dbdbdb; 968 | background: var(--border); 969 | -webkit-appearance: none; 970 | margin-top: -7px; 971 | } 972 | 973 | @media (prefers-color-scheme: dark) { 974 | 975 | input[type='range']::-webkit-slider-thumb { 976 | background: #526980; 977 | background: var(--border); 978 | } 979 | } 980 | 981 | input[type='range']:focus::-webkit-slider-runnable-track { 982 | background: #efefef; 983 | background: var(--background); 984 | } 985 | 986 | @media (prefers-color-scheme: dark) { 987 | 988 | input[type='range']:focus::-webkit-slider-runnable-track { 989 | background: #161f27; 990 | background: var(--background); 991 | } 992 | } 993 | 994 | input[type='range']::-moz-range-track { 995 | width: 100%; 996 | height: 9.5px; 997 | -moz-transition: 0.2s; 998 | transition: 0.2s; 999 | background: #efefef; 1000 | background: var(--background); 1001 | border-radius: 3px; 1002 | } 1003 | 1004 | @media (prefers-color-scheme: dark) { 1005 | 1006 | input[type='range']::-moz-range-track { 1007 | background: #161f27; 1008 | background: var(--background); 1009 | } 1010 | } 1011 | 1012 | input[type='range']::-moz-range-thumb { 1013 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 1014 | height: 20px; 1015 | width: 20px; 1016 | border-radius: 50%; 1017 | background: #dbdbdb; 1018 | background: var(--border); 1019 | } 1020 | 1021 | @media (prefers-color-scheme: dark) { 1022 | 1023 | input[type='range']::-moz-range-thumb { 1024 | background: #526980; 1025 | background: var(--border); 1026 | } 1027 | } 1028 | 1029 | input[type='range']::-ms-track { 1030 | width: 100%; 1031 | height: 9.5px; 1032 | background: transparent; 1033 | border-color: transparent; 1034 | border-width: 16px 0; 1035 | color: transparent; 1036 | } 1037 | 1038 | input[type='range']::-ms-fill-lower { 1039 | background: #efefef; 1040 | background: var(--background); 1041 | border: 0.2px solid #010101; 1042 | border-radius: 3px; 1043 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 1044 | } 1045 | 1046 | @media (prefers-color-scheme: dark) { 1047 | 1048 | input[type='range']::-ms-fill-lower { 1049 | background: #161f27; 1050 | background: var(--background); 1051 | } 1052 | } 1053 | 1054 | input[type='range']::-ms-fill-upper { 1055 | background: #efefef; 1056 | background: var(--background); 1057 | border: 0.2px solid #010101; 1058 | border-radius: 3px; 1059 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 1060 | } 1061 | 1062 | @media (prefers-color-scheme: dark) { 1063 | 1064 | input[type='range']::-ms-fill-upper { 1065 | background: #161f27; 1066 | background: var(--background); 1067 | } 1068 | } 1069 | 1070 | input[type='range']::-ms-thumb { 1071 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; 1072 | border: 1px solid #000; 1073 | height: 20px; 1074 | width: 20px; 1075 | border-radius: 50%; 1076 | background: #dbdbdb; 1077 | background: var(--border); 1078 | } 1079 | 1080 | @media (prefers-color-scheme: dark) { 1081 | 1082 | input[type='range']::-ms-thumb { 1083 | background: #526980; 1084 | background: var(--border); 1085 | } 1086 | } 1087 | 1088 | input[type='range']:focus::-ms-fill-lower { 1089 | background: #efefef; 1090 | background: var(--background); 1091 | } 1092 | 1093 | @media (prefers-color-scheme: dark) { 1094 | 1095 | input[type='range']:focus::-ms-fill-lower { 1096 | background: #161f27; 1097 | background: var(--background); 1098 | } 1099 | } 1100 | 1101 | input[type='range']:focus::-ms-fill-upper { 1102 | background: #efefef; 1103 | background: var(--background); 1104 | } 1105 | 1106 | @media (prefers-color-scheme: dark) { 1107 | 1108 | input[type='range']:focus::-ms-fill-upper { 1109 | background: #161f27; 1110 | background: var(--background); 1111 | } 1112 | } 1113 | 1114 | a { 1115 | text-decoration: none; 1116 | color: #0076d1; 1117 | color: var(--links); 1118 | } 1119 | 1120 | @media (prefers-color-scheme: dark) { 1121 | 1122 | a { 1123 | color: #41adff; 1124 | color: var(--links); 1125 | } 1126 | } 1127 | 1128 | a:hover { 1129 | text-decoration: underline; 1130 | } 1131 | 1132 | code { 1133 | background: #efefef; 1134 | background: var(--background); 1135 | color: #000; 1136 | color: var(--code); 1137 | padding: 2.5px 5px; 1138 | border-radius: 6px; 1139 | font-size: 1em; 1140 | } 1141 | 1142 | @media (prefers-color-scheme: dark) { 1143 | 1144 | code { 1145 | color: #ffbe85; 1146 | color: var(--code); 1147 | } 1148 | } 1149 | 1150 | @media (prefers-color-scheme: dark) { 1151 | 1152 | code { 1153 | background: #161f27; 1154 | background: var(--background); 1155 | } 1156 | } 1157 | 1158 | samp { 1159 | background: #efefef; 1160 | background: var(--background); 1161 | color: #000; 1162 | color: var(--code); 1163 | padding: 2.5px 5px; 1164 | border-radius: 6px; 1165 | font-size: 1em; 1166 | } 1167 | 1168 | @media (prefers-color-scheme: dark) { 1169 | 1170 | samp { 1171 | color: #ffbe85; 1172 | color: var(--code); 1173 | } 1174 | } 1175 | 1176 | @media (prefers-color-scheme: dark) { 1177 | 1178 | samp { 1179 | background: #161f27; 1180 | background: var(--background); 1181 | } 1182 | } 1183 | 1184 | time { 1185 | background: #efefef; 1186 | background: var(--background); 1187 | color: #000; 1188 | color: var(--code); 1189 | padding: 2.5px 5px; 1190 | border-radius: 6px; 1191 | font-size: 1em; 1192 | } 1193 | 1194 | @media (prefers-color-scheme: dark) { 1195 | 1196 | time { 1197 | color: #ffbe85; 1198 | color: var(--code); 1199 | } 1200 | } 1201 | 1202 | @media (prefers-color-scheme: dark) { 1203 | 1204 | time { 1205 | background: #161f27; 1206 | background: var(--background); 1207 | } 1208 | } 1209 | 1210 | pre > code { 1211 | padding: 10px; 1212 | display: block; 1213 | overflow-x: auto; 1214 | } 1215 | 1216 | var { 1217 | color: #39a33c; 1218 | color: var(--variable); 1219 | font-style: normal; 1220 | font-family: monospace; 1221 | } 1222 | 1223 | @media (prefers-color-scheme: dark) { 1224 | 1225 | var { 1226 | color: #d941e2; 1227 | color: var(--variable); 1228 | } 1229 | } 1230 | 1231 | kbd { 1232 | background: #efefef; 1233 | background: var(--background); 1234 | border: 1px solid #dbdbdb; 1235 | border: 1px solid var(--border); 1236 | border-radius: 2px; 1237 | color: #363636; 1238 | color: var(--text-main); 1239 | padding: 2px 4px 2px 4px; 1240 | } 1241 | 1242 | @media (prefers-color-scheme: dark) { 1243 | 1244 | kbd { 1245 | color: #dbdbdb; 1246 | color: var(--text-main); 1247 | } 1248 | } 1249 | 1250 | @media (prefers-color-scheme: dark) { 1251 | 1252 | kbd { 1253 | border: 1px solid #526980; 1254 | border: 1px solid var(--border); 1255 | } 1256 | } 1257 | 1258 | @media (prefers-color-scheme: dark) { 1259 | 1260 | kbd { 1261 | background: #161f27; 1262 | background: var(--background); 1263 | } 1264 | } 1265 | 1266 | img, 1267 | video { 1268 | max-width: 100%; 1269 | height: auto; 1270 | } 1271 | 1272 | hr { 1273 | border: none; 1274 | border-top: 1px solid #dbdbdb; 1275 | border-top: 1px solid var(--border); 1276 | } 1277 | 1278 | @media (prefers-color-scheme: dark) { 1279 | 1280 | hr { 1281 | border-top: 1px solid #526980; 1282 | border-top: 1px solid var(--border); 1283 | } 1284 | } 1285 | 1286 | table { 1287 | border-collapse: collapse; 1288 | margin-bottom: 10px; 1289 | width: 100%; 1290 | table-layout: fixed; 1291 | } 1292 | 1293 | table caption { 1294 | text-align: left; 1295 | } 1296 | 1297 | td, 1298 | th { 1299 | padding: 6px; 1300 | text-align: left; 1301 | vertical-align: top; 1302 | word-wrap: break-word; 1303 | } 1304 | 1305 | thead { 1306 | border-bottom: 1px solid #dbdbdb; 1307 | border-bottom: 1px solid var(--border); 1308 | } 1309 | 1310 | @media (prefers-color-scheme: dark) { 1311 | 1312 | thead { 1313 | border-bottom: 1px solid #526980; 1314 | border-bottom: 1px solid var(--border); 1315 | } 1316 | } 1317 | 1318 | tfoot { 1319 | border-top: 1px solid #dbdbdb; 1320 | border-top: 1px solid var(--border); 1321 | } 1322 | 1323 | @media (prefers-color-scheme: dark) { 1324 | 1325 | tfoot { 1326 | border-top: 1px solid #526980; 1327 | border-top: 1px solid var(--border); 1328 | } 1329 | } 1330 | 1331 | tbody tr:nth-child(even) { 1332 | background-color: #efefef; 1333 | background-color: var(--background); 1334 | } 1335 | 1336 | @media (prefers-color-scheme: dark) { 1337 | 1338 | tbody tr:nth-child(even) { 1339 | background-color: #161f27; 1340 | background-color: var(--background); 1341 | } 1342 | } 1343 | 1344 | tbody tr:nth-child(even) button { 1345 | background-color: #f7f7f7; 1346 | background-color: var(--background-alt); 1347 | } 1348 | 1349 | @media (prefers-color-scheme: dark) { 1350 | 1351 | tbody tr:nth-child(even) button { 1352 | background-color: #1a242f; 1353 | background-color: var(--background-alt); 1354 | } 1355 | } 1356 | 1357 | tbody tr:nth-child(even) button:hover { 1358 | background-color: #fff; 1359 | background-color: var(--background-body); 1360 | } 1361 | 1362 | @media (prefers-color-scheme: dark) { 1363 | 1364 | tbody tr:nth-child(even) button:hover { 1365 | background-color: #202b38; 1366 | background-color: var(--background-body); 1367 | } 1368 | } 1369 | 1370 | ::-webkit-scrollbar { 1371 | height: 10px; 1372 | width: 10px; 1373 | } 1374 | 1375 | ::-webkit-scrollbar-track { 1376 | background: #efefef; 1377 | background: var(--background); 1378 | border-radius: 6px; 1379 | } 1380 | 1381 | @media (prefers-color-scheme: dark) { 1382 | 1383 | ::-webkit-scrollbar-track { 1384 | background: #161f27; 1385 | background: var(--background); 1386 | } 1387 | } 1388 | 1389 | ::-webkit-scrollbar-thumb { 1390 | background: rgb(170, 170, 170); 1391 | background: var(--scrollbar-thumb); 1392 | border-radius: 6px; 1393 | } 1394 | 1395 | @media (prefers-color-scheme: dark) { 1396 | 1397 | ::-webkit-scrollbar-thumb { 1398 | background: #040a0f; 1399 | background: var(--scrollbar-thumb); 1400 | } 1401 | } 1402 | 1403 | @media (prefers-color-scheme: dark) { 1404 | 1405 | ::-webkit-scrollbar-thumb { 1406 | background: #040a0f; 1407 | background: var(--scrollbar-thumb); 1408 | } 1409 | } 1410 | 1411 | ::-webkit-scrollbar-thumb:hover { 1412 | background: #9b9b9b; 1413 | background: var(--scrollbar-thumb-hover); 1414 | } 1415 | 1416 | @media (prefers-color-scheme: dark) { 1417 | 1418 | ::-webkit-scrollbar-thumb:hover { 1419 | background: rgb(0, 0, 0); 1420 | background: var(--scrollbar-thumb-hover); 1421 | } 1422 | } 1423 | 1424 | @media (prefers-color-scheme: dark) { 1425 | 1426 | ::-webkit-scrollbar-thumb:hover { 1427 | background: rgb(0, 0, 0); 1428 | background: var(--scrollbar-thumb-hover); 1429 | } 1430 | } 1431 | 1432 | ::-moz-selection { 1433 | background-color: #9e9e9e; 1434 | background-color: var(--selection); 1435 | color: #000; 1436 | color: var(--text-bright); 1437 | } 1438 | 1439 | ::selection { 1440 | background-color: #9e9e9e; 1441 | background-color: var(--selection); 1442 | color: #000; 1443 | color: var(--text-bright); 1444 | } 1445 | 1446 | @media (prefers-color-scheme: dark) { 1447 | 1448 | ::-moz-selection { 1449 | color: #fff; 1450 | color: var(--text-bright); 1451 | } 1452 | 1453 | ::selection { 1454 | color: #fff; 1455 | color: var(--text-bright); 1456 | } 1457 | } 1458 | 1459 | @media (prefers-color-scheme: dark) { 1460 | 1461 | ::-moz-selection { 1462 | background-color: #1c76c5; 1463 | background-color: var(--selection); 1464 | } 1465 | 1466 | ::selection { 1467 | background-color: #1c76c5; 1468 | background-color: var(--selection); 1469 | } 1470 | } 1471 | 1472 | details { 1473 | display: flex; 1474 | flex-direction: column; 1475 | align-items: flex-start; 1476 | background-color: #f7f7f7; 1477 | background-color: var(--background-alt); 1478 | padding: 10px 10px 0; 1479 | margin: 1em 0; 1480 | border-radius: 6px; 1481 | overflow: hidden; 1482 | } 1483 | 1484 | @media (prefers-color-scheme: dark) { 1485 | 1486 | details { 1487 | background-color: #1a242f; 1488 | background-color: var(--background-alt); 1489 | } 1490 | } 1491 | 1492 | details[open] { 1493 | padding: 10px; 1494 | } 1495 | 1496 | details > :last-child { 1497 | margin-bottom: 0; 1498 | } 1499 | 1500 | details[open] summary { 1501 | margin-bottom: 10px; 1502 | } 1503 | 1504 | summary { 1505 | display: list-item; 1506 | background-color: #efefef; 1507 | background-color: var(--background); 1508 | padding: 10px; 1509 | margin: -10px -10px 0; 1510 | cursor: pointer; 1511 | outline: none; 1512 | } 1513 | 1514 | @media (prefers-color-scheme: dark) { 1515 | 1516 | summary { 1517 | background-color: #161f27; 1518 | background-color: var(--background); 1519 | } 1520 | } 1521 | 1522 | summary:hover, 1523 | summary:focus { 1524 | text-decoration: underline; 1525 | } 1526 | 1527 | details > :not(summary) { 1528 | margin-top: 0; 1529 | } 1530 | 1531 | summary::-webkit-details-marker { 1532 | color: #363636; 1533 | color: var(--text-main); 1534 | } 1535 | 1536 | @media (prefers-color-scheme: dark) { 1537 | 1538 | summary::-webkit-details-marker { 1539 | color: #dbdbdb; 1540 | color: var(--text-main); 1541 | } 1542 | } 1543 | 1544 | dialog { 1545 | background-color: #f7f7f7; 1546 | background-color: var(--background-alt); 1547 | color: #363636; 1548 | color: var(--text-main); 1549 | border: none; 1550 | border-radius: 6px; 1551 | border-color: #dbdbdb; 1552 | border-color: var(--border); 1553 | padding: 10px 30px; 1554 | } 1555 | 1556 | @media (prefers-color-scheme: dark) { 1557 | 1558 | dialog { 1559 | border-color: #526980; 1560 | border-color: var(--border); 1561 | } 1562 | } 1563 | 1564 | @media (prefers-color-scheme: dark) { 1565 | 1566 | dialog { 1567 | color: #dbdbdb; 1568 | color: var(--text-main); 1569 | } 1570 | } 1571 | 1572 | @media (prefers-color-scheme: dark) { 1573 | 1574 | dialog { 1575 | background-color: #1a242f; 1576 | background-color: var(--background-alt); 1577 | } 1578 | } 1579 | 1580 | dialog > header:first-child { 1581 | background-color: #efefef; 1582 | background-color: var(--background); 1583 | border-radius: 6px 6px 0 0; 1584 | margin: -10px -30px 10px; 1585 | padding: 10px; 1586 | text-align: center; 1587 | } 1588 | 1589 | @media (prefers-color-scheme: dark) { 1590 | 1591 | dialog > header:first-child { 1592 | background-color: #161f27; 1593 | background-color: var(--background); 1594 | } 1595 | } 1596 | 1597 | dialog::-webkit-backdrop { 1598 | background: #0000009c; 1599 | -webkit-backdrop-filter: blur(4px); 1600 | backdrop-filter: blur(4px); 1601 | } 1602 | 1603 | dialog::backdrop { 1604 | background: #0000009c; 1605 | -webkit-backdrop-filter: blur(4px); 1606 | backdrop-filter: blur(4px); 1607 | } 1608 | 1609 | footer { 1610 | border-top: 1px solid #dbdbdb; 1611 | border-top: 1px solid var(--border); 1612 | padding-top: 10px; 1613 | color: #70777f; 1614 | color: var(--text-muted); 1615 | } 1616 | 1617 | @media (prefers-color-scheme: dark) { 1618 | 1619 | footer { 1620 | color: #a9b1ba; 1621 | color: var(--text-muted); 1622 | } 1623 | } 1624 | 1625 | @media (prefers-color-scheme: dark) { 1626 | 1627 | footer { 1628 | border-top: 1px solid #526980; 1629 | border-top: 1px solid var(--border); 1630 | } 1631 | } 1632 | 1633 | body > footer { 1634 | margin-top: 40px; 1635 | } 1636 | 1637 | @media print { 1638 | body, 1639 | pre, 1640 | code, 1641 | summary, 1642 | details, 1643 | button, 1644 | input, 1645 | textarea { 1646 | background-color: #fff; 1647 | } 1648 | 1649 | button, 1650 | input, 1651 | textarea { 1652 | border: 1px solid #000; 1653 | } 1654 | 1655 | body, 1656 | h1, 1657 | h2, 1658 | h3, 1659 | h4, 1660 | h5, 1661 | h6, 1662 | pre, 1663 | code, 1664 | button, 1665 | input, 1666 | textarea, 1667 | footer, 1668 | summary, 1669 | strong { 1670 | color: #000; 1671 | } 1672 | 1673 | summary::marker { 1674 | color: #000; 1675 | } 1676 | 1677 | summary::-webkit-details-marker { 1678 | color: #000; 1679 | } 1680 | 1681 | tbody tr:nth-child(even) { 1682 | background-color: #f2f2f2; 1683 | } 1684 | 1685 | a { 1686 | color: #00f; 1687 | text-decoration: underline; 1688 | } 1689 | } 1690 | -------------------------------------------------------------------------------- /vim.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programmerhat/vim-online-editor/5bb574bca71e4076829a5ec455dd9bdd88967f3e/vim.wasm --------------------------------------------------------------------------------