blueloveTH 1 year ago
parent
commit
ca459f6b65

+ 5 - 1
build_web.sh

@@ -7,4 +7,8 @@ mkdir web/lib
 
 SRC=$(find src/ -name "*.c")
 
-emcc $SRC -Iinclude/ -s -Os -sEXPORTED_FUNCTIONS=_py_initialize,_py_finalize,_py_exec,_py_replinput -sEXPORTED_RUNTIME_METHODS=ccall -o web/lib/pocketpy.js
+emcc $SRC -Iinclude/ -s -Os \
+    -sEXPORTED_FUNCTIONS=_py_initialize,_py_exec,_py_finalize,_py_printexc,_py_clearexc \
+    -sEXPORTED_RUNTIME_METHODS=ccall \
+    -sALLOW_MEMORY_GROWTH=1 \
+    -o web/lib/pocketpy.js

+ 256 - 15
web/index.html

@@ -1,15 +1,256 @@
-<!doctype html>
-  <html>
-    <head>
-      <meta name="viewport" content="width=device-width, initial-scale=1.0">
-      <title>pocketpy demo</title>
-      <link rel="stylesheet" href="xterm/xterm.css" />
-      <script src="xterm/xterm.js"></script>
-      <script src="index.js" ></script>
-    </head>
-    <body>
-      <div id="terminal"></div>
-      <script>term_init();</script>
-      <script src="./lib/pocketpy.js"></script>
-    </body>
-  </html>
+
+<!-- 
+
+Note:
+This site was created by modifying code pen https://codepen.io/antonmedv/pen/PoPoGwg
+written by 'Anton Medvedev' the license can be found bellow.
+
+LICENSE
+-------
+Copyright (c) 2022 by Anton Medvedev (https://codepen.io/antonmedv/pen/PoPoGwg)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+-->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+  <script src="static/codejar/codejar.js"></script>
+  <script src="static/codejar/linenumbers.js"></script>
+  <script src="static/highlight.js/highlight.min.js"></script>
+  <link rel="stylesheet" href="static/highlight.js/github-dark-dimmed.min.css">
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
+
+<style>
+@import url("https://fonts.googleapis.com/css2?family=Lato:wght@300&family=PT+Mono&display=swap");
+
+.hljs{display:block;overflow-x:auto;padding:.5em;background:#282a36}
+.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-section,.hljs-link{color:#73cbde}
+.hljs,.hljs-subst{color:#f8f8f2}
+.hljs-string,.hljs-title,.hljs-name,.hljs-type,.hljs-attribute,.hljs-symbol,
+.hljs-bullet,.hljs-addition,.hljs-variable,.hljs-template-tag,.hljs-template-variable{color:#f1fa8c}
+.hljs-comment,.hljs-quote,.hljs-deletion,.hljs-meta{color:#6272a4}
+.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-title,.hljs-section,.hljs-doctag,
+.hljs-type,.hljs-name,.hljs-strong{font-weight:bold}.hljs-emphasis{font-style:italic}
+
+:root {
+  --window-width: 80%;
+}
+
+body {
+background-color: #778abb;
+font-family: Lato, sans-serif;
+font-weight: 300;
+font-size: 15px;
+margin: 0;
+}
+*,
+*:before,
+*:after {
+box-sizing: border-box;
+}
+*:focus {
+  outline: none;
+}
+a,
+a:visited,
+a:active {
+color: black;
+}
+main {
+min-height: 100vh;
+display: flex;
+align-items: center;
+flex-direction: column;
+}
+.title {
+color: #fff;
+text-align: center;
+font-weight: 300;
+font-size: 34px;
+margin-top: 20px;
+}
+.window {
+width: var(--window-width);
+border-radius: 6px;
+box-shadow: 0 8px 12px rgba(0, 0, 0, 0.1);
+overflow: hidden;
+margin-bottom: 20px;
+}
+.window .window-header {
+height: 25px;
+background: Gainsboro;
+position: relative;
+}
+.window .window-header .action-buttons {
+position: absolute;
+top: 50%;
+left: 10px;
+margin-top: -5px;
+width: 10px;
+height: 10px;
+background: Crimson;
+border-radius: 50%;
+box-shadow: 15px 0 0 Orange, 30px 0 0 LimeGreen;
+}
+#code-editor {
+border-bottom-left-radius: 6px;
+border-bottom-right-radius: 6px;
+box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),
+  0 3px 1px -2px rgba(0, 0, 0, 0.2);
+font-family: "PT Mono", monospace;
+font-size: 14px;
+font-weight: 400;
+min-height: 300px;
+letter-spacing: normal;
+line-height: 20px;
+padding: 10px;
+resize: none !important;
+tab-size: 4;
+}
+#run-button {
+  padding-left:  10px;
+  padding-right: 10px;
+  font-weight: bold;
+  cursor: pointer;
+}
+#code-editor.hljs {
+padding: 10px;
+}
+#output-wrapper {
+font-family: "PT Mono", monospace;
+width: var(--window-width);
+min-height: 50px;
+background-color: #282a36;
+border-radius: 6px;
+padding: 10px;
+color: white;
+margin:0 !important;
+}
+#code-output span.error-line {
+color: #ec5424;
+}
+.controls {
+font-size: 14px;
+position: absolute;
+top: 50%;
+right: 10px;
+margin-top: -10px;
+display: flex;
+}
+.controls > div:first-child > a {
+display: inline-block;
+width: 40px;
+}
+.features {
+width: 547px;
+font-size: 16px;
+margin-bottom: 30px;
+} 
+</style>
+
+  <title>Try Online</title>
+</head>
+<body id="tryonline-body">
+
+  <main>
+    <br>
+    <div class="window">
+      <div class="window-header">
+        <div class="action-buttons"></div>
+        <div class="controls">
+          <div id="run-button">Run</div>
+        </div>
+      </div>
+      <div class="window-body">
+        <div id="code-editor" class="language-python" data-gramm="false"># A recursive fibonacci function.
+def fib(n):
+  if n &lt; 2:
+    return n
+  return fib(n-1) + fib(n-2)
+
+# Prints all fibonacci from 0 to 10 exclusive.
+for i in range(10):
+  print(f"fib({i}) = {fib(i)}")
+
+</div>
+      </div>
+    </div>
+    <pre id="output-wrapper"><div id="code-output">...</div></pre>
+    <br>
+  </main>
+
+
+<script>
+
+let code_editor = document.querySelector('#code-editor');
+let code_output = document.querySelector('#code-output');
+let run_button  = document.querySelector('#run-button');
+
+let highlight = withLineNumbers(function(editor) {
+  editor.textContent = editor.textContent;
+  hljs.highlightElement(editor);
+});
+
+highlight(code_editor);
+
+CodeJar(code_editor, highlight); //, { indentOn: /[(\[{:]$/});
+
+
+var Module = {
+  onRuntimeInitialized: function() {
+    Module.ccall('py_initialize', null, [], []);
+  },
+  print: function(text) {
+    console.log(text);
+    code_output.innerText += text + '\n';
+  },
+  onabort: function(what) {
+    code_output.innerText += 'Aborted: ' + what + '\n';
+    Module.ccall('py_finalize', null, [], []);
+  }
+};
+
+window.onload = function() {
+  var script = document.createElement('script');
+  script.src = 'lib/pocketpy.js';
+  document.head.appendChild(script);
+
+  run_button.onclick = function() {
+    code_output.innerText = '';
+    const source = code_editor.textContent;
+    var ok = Module.ccall(
+      'py_exec', 'boolean', ['string', 'string', 'number', 'number'],
+      [source, 'main.py', 0, 0]
+    );
+    if (!ok) {
+      Module.ccall('py_printexc', null, [], []);
+      Module.ccall('py_clearexc', null, ['number'], [0]);
+    }
+  }
+}
+
+</script>
+
+</body>
+</html>

+ 0 - 163
web/index.js

@@ -1,163 +0,0 @@
-const MINIMUM_COLS = 2
-const MINIMUM_ROWS = 1
-
-class FitAddon {
-  constructor() {}
-
-  activate(terminal) {
-    this._terminal = terminal
-  }
-
-  dispose() {}
-
-  fit() {
-    const dims = this.proposeDimensions()
-    if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) {
-      return
-    }
-
-    // TODO: Remove reliance on private API
-    const core = this._terminal._core
-
-    // Force a full render
-    if (
-      this._terminal.rows !== dims.rows ||
-      this._terminal.cols !== dims.cols
-    ) {
-      core._renderService.clear()
-      this._terminal.resize(dims.cols, dims.rows)
-    }
-  }
-
-  proposeDimensions() {
-    if (!this._terminal) {
-      return undefined
-    }
-
-    if (!this._terminal.element || !this._terminal.element.parentElement) {
-      return undefined
-    }
-
-    // TODO: Remove reliance on private API
-    const core = this._terminal._core
-    const dims = core._renderService.dimensions
-
-    if (dims.actualCellWidth === 0 || dims.actualCellHeight === 0) {
-      return undefined
-    }
-
-    const scrollbarWidth =
-      this._terminal.options.scrollback === 0 ? 0 : core.viewport.scrollBarWidth
-
-    const parentElementStyle = window.getComputedStyle(
-      this._terminal.element.parentElement
-    )
-    const parentElementHeight = parseInt(
-      parentElementStyle.getPropertyValue("height")
-    )
-    const parentElementWidth = Math.max(
-      0,
-      parseInt(parentElementStyle.getPropertyValue("width"))
-    )
-    const elementStyle = window.getComputedStyle(this._terminal.element)
-    const elementPadding = {
-      top: parseInt(elementStyle.getPropertyValue("padding-top")),
-      bottom: parseInt(elementStyle.getPropertyValue("padding-bottom")),
-      right: parseInt(elementStyle.getPropertyValue("padding-right")),
-      left: parseInt(elementStyle.getPropertyValue("padding-left"))
-    }
-    const elementPaddingVer = elementPadding.top + elementPadding.bottom
-    const elementPaddingHor = elementPadding.right + elementPadding.left
-    const availableHeight = parentElementHeight - elementPaddingVer
-    const availableWidth =
-      parentElementWidth - elementPaddingHor - scrollbarWidth
-    const geometry = {
-      cols: Math.max(
-        MINIMUM_COLS,
-        Math.floor(availableWidth / dims.actualCellWidth)
-      ),
-      rows: Math.max(
-        MINIMUM_ROWS,
-        Math.floor(availableHeight / dims.actualCellHeight)
-      )
-    }
-    return geometry
-  }
-}
-
-/******************************************************/
-
-const term = new Terminal(
-  {
-    cursorBlink: true,
-    fontSize: 16,
-    theme: {
-      background: '#282C34',
-      foreground: '#ffffff',
-      cursor: '#ffffff',
-      cursorAccent: '#282C34',
-      selection: '#41454E',
-    },
-  }
-);
-
-var command = "";
-var need_more_lines = false;
-var stopped = false;
-var repl = 0;
-
-var Module = {
-    'print': function(text) { 
-      term.write(text + "\r\n");
-    },
-    'printErr': function(text) {
-      term.write(text + "\r\n");
-    },
-    'onRuntimeInitialized': function(text) {
-      var vm = Module.ccall('pkpy_new_vm', 'number', ['boolean'], [true]);
-      repl = Module.ccall('pkpy_new_repl', 'number', ['number'], [vm]);
-      term.write(need_more_lines ? "... " : ">>> ");
-    },
-    'onAbort': function(text) { 
-      stopped = true;
-    },
-  };
-
-function term_init() {
-    term.open(document.getElementById('terminal'));
-    const addon = new FitAddon();
-    term.loadAddon(addon);
-    addon.fit();
-
-    // refit when window is resized
-    window.addEventListener('resize', () => {
-      addon.fit();
-    });
-
-    term.onData(e => {
-      if (stopped) return;
-      switch (e) {
-        case '\r': // Enter
-          term.write("\r\n");
-          need_more_lines = Module.ccall('pkpy_repl_input', 'bool', ['number', 'string'], [repl, command]);
-          command = '';
-          term.write(need_more_lines ? "... " : ">>> ");
-          break;
-        case '\u007F': // Backspace (DEL)
-          var cnt = term._core.buffer.x-4;
-          if(cnt<=0 || command.length==0) break;
-          // delete the last unicode char
-          command = command.replace(/.$/u, "");
-          // clear the whole line
-          term.write('\b \b'.repeat(cnt));
-          // re-write the command
-          term.write(command);
-          break;
-        default: // Print all other characters for demo
-          if (e >= String.fromCharCode(0x20) && e <= String.fromCharCode(0x7E) || e >= '\u00a0') {
-            command += e;
-            term.write(e);
-          }
-      }
-    });
-}

+ 423 - 0
web/static/codejar/codejar.js

@@ -0,0 +1,423 @@
+const globalWindow = window;
+function CodeJar(editor, highlight, opt = {}) {
+    const options = Object.assign({ tab: '\t', indentOn: /{$/, spellcheck: false, catchTab: true, preserveIdent: true, addClosing: true, history: true, window: globalWindow }, opt);
+    const window = options.window;
+    const document = window.document;
+    let listeners = [];
+    let history = [];
+    let at = -1;
+    let focus = false;
+    let callback;
+    let prev; // code content prior keydown event
+    let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+    editor.setAttribute('contentEditable', isFirefox ? 'true' : 'plaintext-only');
+    editor.setAttribute('spellcheck', options.spellcheck ? 'true' : 'false');
+    editor.style.outline = 'none';
+    editor.style.overflowWrap = 'break-word';
+    editor.style.overflowY = 'auto';
+    editor.style.resize = 'vertical';
+    editor.style.whiteSpace = 'pre-wrap';
+    highlight(editor);
+    const debounceHighlight = debounce(() => {
+        const pos = save();
+        highlight(editor, pos);
+        restore(pos);
+    }, 30);
+    let recording = false;
+    const shouldRecord = (event) => {
+        return !isUndo(event) && !isRedo(event)
+            && event.key !== 'Meta'
+            && event.key !== 'Control'
+            && event.key !== 'Alt'
+            && !event.key.startsWith('Arrow');
+    };
+    const debounceRecordHistory = debounce((event) => {
+        if (shouldRecord(event)) {
+            recordHistory();
+            recording = false;
+        }
+    }, 300);
+    const on = (type, fn) => {
+        listeners.push([type, fn]);
+        editor.addEventListener(type, fn);
+    };
+    on('keydown', event => {
+        if (event.defaultPrevented)
+            return;
+        prev = toString();
+        if (options.preserveIdent)
+            handleNewLine(event);
+        else
+            firefoxNewLineFix(event);
+        if (options.catchTab)
+            handleTabCharacters(event);
+        if (options.addClosing)
+            handleSelfClosingCharacters(event);
+        if (options.history) {
+            handleUndoRedo(event);
+            if (shouldRecord(event) && !recording) {
+                recordHistory();
+                recording = true;
+            }
+        }
+    });
+    on('keyup', event => {
+        if (event.defaultPrevented)
+            return;
+        if (event.isComposing)
+            return;
+        if (prev !== toString())
+            debounceHighlight();
+        debounceRecordHistory(event);
+        if (callback)
+            callback(toString());
+    });
+    on('focus', _event => {
+        focus = true;
+    });
+    on('blur', _event => {
+        focus = false;
+    });
+    on('paste', event => {
+        recordHistory();
+        handlePaste(event);
+        recordHistory();
+        if (callback)
+            callback(toString());
+    });
+    function save() {
+        const s = getSelection();
+        const pos = { start: 0, end: 0, dir: undefined };
+        visit(editor, el => {
+            if (el === s.anchorNode && el === s.focusNode) {
+                pos.start += s.anchorOffset;
+                pos.end += s.focusOffset;
+                pos.dir = s.anchorOffset <= s.focusOffset ? '->' : '<-';
+                return 'stop';
+            }
+            if (el === s.anchorNode) {
+                pos.start += s.anchorOffset;
+                if (!pos.dir) {
+                    pos.dir = '->';
+                }
+                else {
+                    return 'stop';
+                }
+            }
+            else if (el === s.focusNode) {
+                pos.end += s.focusOffset;
+                if (!pos.dir) {
+                    pos.dir = '<-';
+                }
+                else {
+                    return 'stop';
+                }
+            }
+            if (el.nodeType === Node.TEXT_NODE) {
+                if (pos.dir != '->')
+                    pos.start += el.nodeValue.length;
+                if (pos.dir != '<-')
+                    pos.end += el.nodeValue.length;
+            }
+        });
+        return pos;
+    }
+    function restore(pos) {
+        const s = getSelection();
+        let startNode, startOffset = 0;
+        let endNode, endOffset = 0;
+        if (!pos.dir)
+            pos.dir = '->';
+        if (pos.start < 0)
+            pos.start = 0;
+        if (pos.end < 0)
+            pos.end = 0;
+        // Flip start and end if the direction reversed
+        if (pos.dir == '<-') {
+            const { start, end } = pos;
+            pos.start = end;
+            pos.end = start;
+        }
+        let current = 0;
+        visit(editor, el => {
+            if (el.nodeType !== Node.TEXT_NODE)
+                return;
+            const len = (el.nodeValue || '').length;
+            if (current + len >= pos.start) {
+                if (!startNode) {
+                    startNode = el;
+                    startOffset = pos.start - current;
+                }
+                if (current + len >= pos.end) {
+                    endNode = el;
+                    endOffset = pos.end - current;
+                    return 'stop';
+                }
+            }
+            current += len;
+        });
+        // If everything deleted place cursor at editor
+        if (!startNode)
+            startNode = editor;
+        if (!endNode)
+            endNode = editor;
+        // Flip back the selection
+        if (pos.dir == '<-') {
+            [startNode, startOffset, endNode, endOffset] = [endNode, endOffset, startNode, startOffset];
+        }
+        s.setBaseAndExtent(startNode, startOffset, endNode, endOffset);
+    }
+    function beforeCursor() {
+        const s = getSelection();
+        const r0 = s.getRangeAt(0);
+        const r = document.createRange();
+        r.selectNodeContents(editor);
+        r.setEnd(r0.startContainer, r0.startOffset);
+        return r.toString();
+    }
+    function afterCursor() {
+        const s = getSelection();
+        const r0 = s.getRangeAt(0);
+        const r = document.createRange();
+        r.selectNodeContents(editor);
+        r.setStart(r0.endContainer, r0.endOffset);
+        return r.toString();
+    }
+    function handleNewLine(event) {
+        if (event.key === 'Enter') {
+            const before = beforeCursor();
+            const after = afterCursor();
+            let [padding] = findPadding(before);
+            let newLinePadding = padding;
+            // If last symbol is "{" ident new line
+            // Allow user defines indent rule
+            if (options.indentOn.test(before)) {
+                newLinePadding += options.tab;
+            }
+            // Preserve padding
+            if (newLinePadding.length > 0) {
+                preventDefault(event);
+                event.stopPropagation();
+                insert('\n' + newLinePadding);
+            }
+            else {
+                firefoxNewLineFix(event);
+            }
+            // Place adjacent "}" on next line
+            if (newLinePadding !== padding && after[0] === '}') {
+                const pos = save();
+                insert('\n' + padding);
+                restore(pos);
+            }
+        }
+    }
+    function firefoxNewLineFix(event) {
+        // Firefox does not support plaintext-only mode
+        // and puts <div><br></div> on Enter. Let's help.
+        if (isFirefox && event.key === 'Enter') {
+            preventDefault(event);
+            event.stopPropagation();
+            if (afterCursor() == '') {
+                insert('\n ');
+                const pos = save();
+                pos.start = --pos.end;
+                restore(pos);
+            }
+            else {
+                insert('\n');
+            }
+        }
+    }
+    function handleSelfClosingCharacters(event) {
+        const open = `([{'"`;
+        const close = `)]}'"`;
+        const codeAfter = afterCursor();
+        const codeBefore = beforeCursor();
+        const escapeCharacter = codeBefore.substr(codeBefore.length - 1) === '\\';
+        const charAfter = codeAfter.substr(0, 1);
+        if (close.includes(event.key) && !escapeCharacter && charAfter === event.key) {
+            // We already have closing char next to cursor.
+            // Move one char to right.
+            const pos = save();
+            preventDefault(event);
+            pos.start = ++pos.end;
+            restore(pos);
+        }
+        else if (open.includes(event.key)
+            && !escapeCharacter
+            && (`"'`.includes(event.key) || ['', ' ', '\n'].includes(charAfter))) {
+            preventDefault(event);
+            const pos = save();
+            const wrapText = pos.start == pos.end ? '' : getSelection().toString();
+            const text = event.key + wrapText + close[open.indexOf(event.key)];
+            insert(text);
+            pos.start++;
+            pos.end++;
+            restore(pos);
+        }
+    }
+    function handleTabCharacters(event) {
+        if (event.key === 'Tab') {
+            preventDefault(event);
+            if (event.shiftKey) {
+                const before = beforeCursor();
+                let [padding, start,] = findPadding(before);
+                if (padding.length > 0) {
+                    const pos = save();
+                    // Remove full length tab or just remaining padding
+                    const len = Math.min(options.tab.length, padding.length);
+                    restore({ start, end: start + len });
+                    document.execCommand('delete');
+                    pos.start -= len;
+                    pos.end -= len;
+                    restore(pos);
+                }
+            }
+            else {
+                insert(options.tab);
+            }
+        }
+    }
+    function handleUndoRedo(event) {
+        if (isUndo(event)) {
+            preventDefault(event);
+            at--;
+            const record = history[at];
+            if (record) {
+                editor.innerHTML = record.html;
+                restore(record.pos);
+            }
+            if (at < 0)
+                at = 0;
+        }
+        if (isRedo(event)) {
+            preventDefault(event);
+            at++;
+            const record = history[at];
+            if (record) {
+                editor.innerHTML = record.html;
+                restore(record.pos);
+            }
+            if (at >= history.length)
+                at--;
+        }
+    }
+    function recordHistory() {
+        if (!focus)
+            return;
+        const html = editor.innerHTML;
+        const pos = save();
+        const lastRecord = history[at];
+        if (lastRecord) {
+            if (lastRecord.html === html
+                && lastRecord.pos.start === pos.start
+                && lastRecord.pos.end === pos.end)
+                return;
+        }
+        at++;
+        history[at] = { html, pos };
+        history.splice(at + 1);
+        const maxHistory = 300;
+        if (at > maxHistory) {
+            at = maxHistory;
+            history.splice(0, 1);
+        }
+    }
+    function handlePaste(event) {
+        preventDefault(event);
+        const text = (event.originalEvent || event)
+            .clipboardData
+            .getData('text/plain')
+            .replace(/\r/g, '');
+        const pos = save();
+        insert(text);
+        highlight(editor);
+        restore({ start: pos.start + text.length, end: pos.start + text.length });
+    }
+    function visit(editor, visitor) {
+        const queue = [];
+        if (editor.firstChild)
+            queue.push(editor.firstChild);
+        let el = queue.pop();
+        while (el) {
+            if (visitor(el) === 'stop')
+                break;
+            if (el.nextSibling)
+                queue.push(el.nextSibling);
+            if (el.firstChild)
+                queue.push(el.firstChild);
+            el = queue.pop();
+        }
+    }
+    function isCtrl(event) {
+        return event.metaKey || event.ctrlKey;
+    }
+    function isUndo(event) {
+        return isCtrl(event) && !event.shiftKey && event.key === 'z';
+    }
+    function isRedo(event) {
+        return isCtrl(event) && event.shiftKey && event.key === 'z';
+    }
+    function insert(text) {
+        text = text
+            .replace(/&/g, '&amp;')
+            .replace(/</g, '&lt;')
+            .replace(/>/g, '&gt;')
+            .replace(/"/g, '&quot;')
+            .replace(/'/g, '&#039;');
+        document.execCommand('insertHTML', false, text);
+    }
+    function debounce(cb, wait) {
+        let timeout = 0;
+        return (...args) => {
+            clearTimeout(timeout);
+            timeout = window.setTimeout(() => cb(...args), wait);
+        };
+    }
+    function findPadding(text) {
+        // Find beginning of previous line.
+        let i = text.length - 1;
+        while (i >= 0 && text[i] !== '\n')
+            i--;
+        i++;
+        // Find padding of the line.
+        let j = i;
+        while (j < text.length && /[ \t]/.test(text[j]))
+            j++;
+        return [text.substring(i, j) || '', i, j];
+    }
+    function toString() {
+        return editor.textContent || '';
+    }
+    function preventDefault(event) {
+        event.preventDefault();
+    }
+    function getSelection() {
+        var _a;
+        if (((_a = editor.parentNode) === null || _a === void 0 ? void 0 : _a.nodeType) == Node.DOCUMENT_FRAGMENT_NODE) {
+            return editor.parentNode.getSelection();
+        }
+        return window.getSelection();
+    }
+    return {
+        updateOptions(options) {
+            options = Object.assign(Object.assign({}, options), options);
+        },
+        updateCode(code) {
+            editor.textContent = code;
+            highlight(editor);
+        },
+        onUpdate(cb) {
+            callback = cb;
+        },
+        toString,
+        save,
+        restore,
+        recordHistory,
+        destroy() {
+            for (let [type, fn] of listeners) {
+                editor.removeEventListener(type, fn);
+            }
+        },
+    };
+}

+ 60 - 0
web/static/codejar/linenumbers.js

@@ -0,0 +1,60 @@
+function withLineNumbers(highlight, options = {}) {
+    const opts = Object.assign({ class: "codejar-linenumbers", wrapClass: "codejar-wrap", width: "35px", backgroundColor: "rgba(128, 128, 128, 0.15)", color: "" }, options);
+    let lineNumbers;
+    return function (editor) {
+        highlight(editor);
+        if (!lineNumbers) {
+            lineNumbers = init(editor, opts);
+            editor.addEventListener("scroll", () => lineNumbers.style.top = `-${editor.scrollTop}px`);
+        }
+        const code = editor.textContent || "";
+
+        //const linesCount = code.replace(/\n+$/, "\n").split("\n").length + 1;
+        const linesCount = code.split("\n").length;
+
+        let text = "";
+        for (let i = 1; i < linesCount; i++) {
+            text += `${i}\n`;
+        }
+        lineNumbers.innerText = text;
+    };
+}
+function init(editor, opts) {
+    const css = getComputedStyle(editor);
+    const wrap = document.createElement("div");
+    wrap.className = opts.wrapClass;
+    wrap.style.position = "relative";
+    const gutter = document.createElement("div");
+    gutter.className = opts.class;
+    wrap.appendChild(gutter);
+    // Add own styles
+    gutter.style.position = "absolute";
+    gutter.style.top = "0px";
+    gutter.style.left = "0px";
+    gutter.style.bottom = "0px";
+    gutter.style.width = opts.width;
+    gutter.style.overflow = "hidden";
+    gutter.style.backgroundColor = opts.backgroundColor;
+    gutter.style.color = opts.color || css.color;
+    gutter.style.setProperty("mix-blend-mode", "difference");
+    // Copy editor styles
+    gutter.style.fontFamily = css.fontFamily;
+    gutter.style.fontSize = css.fontSize;
+    gutter.style.lineHeight = css.lineHeight;
+    gutter.style.paddingTop = css.paddingTop;
+    gutter.style.paddingLeft = css.paddingLeft;
+    gutter.style.borderTopLeftRadius = css.borderTopLeftRadius;
+    gutter.style.borderBottomLeftRadius = css.borderBottomLeftRadius;
+    // Add line numbers
+    const lineNumbers = document.createElement("div");
+    lineNumbers.style.position = "relative";
+    lineNumbers.style.top = "0px";
+    gutter.appendChild(lineNumbers);
+    // Tweak editor styles
+    editor.style.paddingLeft = `calc(${opts.width} + ${gutter.style.paddingLeft})`;
+    editor.style.whiteSpace = "pre";
+    // Swap editor with a wrap
+    editor.parentNode.insertBefore(wrap, editor);
+    wrap.appendChild(editor);
+    return lineNumbers;
+}

+ 11 - 0
web/static/highlight.js/github-dark-dimmed.min.css

@@ -0,0 +1,11 @@
+pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
+  Theme: GitHub Dark Dimmed
+  Description: Dark dimmed theme as seen on github.com
+  Author: github.com
+  Maintainer: @Hirse
+  Updated: 2021-05-15
+
+  Colors taken from GitHub's CSS
+*/
+
+.hljs{color:#adbac7;background:#22272e}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#f47067}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#dcbdfb}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#6cb6ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#96d0ff}.hljs-built_in,.hljs-symbol{color:#f69d50}.hljs-code,.hljs-comment,.hljs-formula{color:#768390}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#8ddb8c}.hljs-subst{color:#adbac7}.hljs-section{color:#316dca;font-weight:700}.hljs-bullet{color:#eac55f}.hljs-emphasis{color:#adbac7;font-style:italic}.hljs-strong{color:#adbac7;font-weight:700}.hljs-addition{color:#b4f1b4;background-color:#1b4721}.hljs-deletion{color:#ffd8d3;background-color:#78191b}

File diff suppressed because it is too large
+ 310 - 0
web/static/highlight.js/highlight.min.js


+ 0 - 174
web/xterm/xterm.css

@@ -1,174 +0,0 @@
-.xterm {
-    cursor: text;
-    position: relative;
-    user-select: none;
-    -ms-user-select: none;
-    -webkit-user-select: none;
-}
-
-.xterm.focus,
-.xterm:focus {
-    outline: none;
-}
-
-.xterm .xterm-helpers {
-    position: absolute;
-    top: 0;
-    /**
-     * The z-index of the helpers must be higher than the canvases in order for
-     * IMEs to appear on top.
-     */
-    z-index: 5;
-}
-
-.xterm .xterm-helper-textarea {
-    padding: 0;
-    border: 0;
-    margin: 0;
-    /* Move textarea out of the screen to the far left, so that the cursor is not visible */
-    position: absolute;
-    opacity: 0;
-    left: -9999em;
-    top: 0;
-    width: 0;
-    height: 0;
-    z-index: -5;
-    /** Prevent wrapping so the IME appears against the textarea at the correct position */
-    white-space: nowrap;
-    overflow: hidden;
-    resize: none;
-}
-
-.xterm .composition-view {
-    /* TODO: Composition position got messed up somewhere */
-    background: #000;
-    color: #FFF;
-    display: none;
-    position: absolute;
-    white-space: nowrap;
-    z-index: 1;
-}
-
-.xterm .composition-view.active {
-    display: block;
-}
-
-#terminal {
-    border: #FFF;
-    border-style: solid;
-    border-width: 1px;
-    padding: 4px;
-    /* zoom: 1.2 !important; */
-}
-
-body {
-    margin: 0px;
-    height: 100%;
-    width: 100%;
-    background-color: #282C34;
-
-    overflow: hidden;
-}
-
-.xterm .xterm-viewport {
-    /* On OS X this is required in order for the scroll bar to appear fully opaque */
-    /* background-color: rgb(13,17,23) !important; */
-
-    /*overflow-y: scroll;*/
-    overflow: hidden;
-    cursor: default;
-    position: absolute;
-    right: 0;
-    left: 0;
-    top: 0;
-    bottom: 0;
-}
-
-.xterm .xterm-screen {
-    position: relative;
-}
-
-.xterm .xterm-screen canvas {
-    position: absolute;
-    left: 0;
-    top: 0;
-}
-
-.xterm .xterm-scroll-area {
-    visibility: hidden;
-}
-
-.xterm-char-measure-element {
-    display: inline-block;
-    visibility: hidden;
-    position: absolute;
-    top: 0;
-    left: -9999em;
-    line-height: normal;
-}
-
-.xterm.enable-mouse-events {
-    /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
-    cursor: default;
-}
-
-.xterm.xterm-cursor-pointer,
-.xterm .xterm-cursor-pointer {
-    cursor: pointer;
-}
-
-.xterm.column-select.focus {
-    /* Column selection mode */
-    cursor: crosshair;
-}
-
-.xterm .xterm-accessibility,
-.xterm .xterm-message {
-    position: absolute;
-    left: 0;
-    top: 0;
-    bottom: 0;
-    right: 0;
-    z-index: 10;
-    color: transparent;
-}
-
-.xterm .live-region {
-    position: absolute;
-    left: -9999px;
-    width: 1px;
-    height: 1px;
-    overflow: hidden;
-}
-
-.xterm-dim {
-    opacity: 0.5;
-}
-
-.xterm-underline-1 { text-decoration: underline; }
-.xterm-underline-2 { text-decoration: double underline; }
-.xterm-underline-3 { text-decoration: wavy underline; }
-.xterm-underline-4 { text-decoration: dotted underline; }
-.xterm-underline-5 { text-decoration: dashed underline; }
-
-.xterm-strikethrough {
-    text-decoration: line-through;
-}
-
-.xterm-screen .xterm-decoration-container .xterm-decoration {
-	z-index: 6;
-	position: absolute;
-}
-
-.xterm-decoration-overview-ruler {
-    z-index: 7;
-    position: absolute;
-    top: 0;
-    right: 0;
-    pointer-events: none;
-}
-
-.xterm-decoration-top {
-    z-index: 2;
-    position: relative;
-}

File diff suppressed because it is too large
+ 0 - 0
web/xterm/xterm.js


File diff suppressed because it is too large
+ 0 - 0
web/xterm/xterm.js.map


Some files were not shown because too many files changed in this diff