diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/.prettierrc.yaml b/.prettierrc.yaml
new file mode 100644
index 0000000..24ae9a9
--- /dev/null
+++ b/.prettierrc.yaml
@@ -0,0 +1,4 @@
+trailingComma: "es5"
+tabWidth: 2
+semi: false
+singleQuote: false
diff --git a/demo/banner.txt b/demo/banner.txt
new file mode 100644
index 0000000..7455d35
--- /dev/null
+++ b/demo/banner.txt
@@ -0,0 +1,13 @@
+ _ _ _ _
+ (_) | | | | |
+ ___ _ __ ___ _ ___ ___| |__ ___| | |
+ / __| '_ ` _ \| / __| / __| '_ \ / _ \ | |
+ | (__| | | | | | \__ \ \__ \ | | | __/ | |
+ \___|_| |_| |_| |___/ |___/_| |_|\___|_|_|
+ _/ |
+ |__/
+
+ This is a demo of cmjs-shell. It's not really intended for browsers
+ (it was built for electron), but it should work ok. Here there's a
+ javascript interpreter provided for test purposes. Enjoy!
+
diff --git a/demo/index.css b/demo/index.css
new file mode 100644
index 0000000..0229443
--- /dev/null
+++ b/demo/index.css
@@ -0,0 +1,23 @@
+#shell-container {
+ width: 600px;
+ height: 400px;
+ border: 1px solid gray;
+ margin: 0 auto;
+ position: relative;
+}
+
+#shell-container .CodeMirror {
+ height: 100%;
+ width: 100%;
+ font-family: consolas, 'ubuntu mono', monospace;
+ font-size: 10pt;
+}
+
+.shell-error {
+ background: red;
+ color: white !important;
+}
+
+.banner {
+ color: white !important;
+}
diff --git a/demo/index.html b/demo/index.html
index aee466e..b715d54 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -1,115 +1,15 @@
-
+
-
- demo
-
-
-
+
+ js - javascript terminal in web browser
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/demo/index.js b/demo/index.js
new file mode 100644
index 0000000..677d197
--- /dev/null
+++ b/demo/index.js
@@ -0,0 +1,90 @@
+import Shell from "../shell.js"
+
+// https://vitejs.dev/guide/assets.html#importing-asset-as-string
+import banner from "./banner.txt?raw"
+
+//import {javascript} from "@codemirror/lang-javascript"
+//import {StreamLanguage} from "@codemirror/language"
+//import {lua} from "@codemirror/legacy-modes/mode/lua"
+//import {shell} from "@codemirror/legacy-modes/mode/shell"
+//import {javascript} from "@codemirror/legacy-modes/mode/javascript"
+
+import * as esprima from "esprima"
+
+/**
+ * this is our interpreter. note that writing responses to the shell
+ * is decoupled from commands -- the result of this function (via callback)
+ * only affects display of the prompt.
+ */
+function exec_function(cmd, callback) {
+ var ps = shell.PARSE_STATUS.OK
+ if (cmd.length) {
+ var composed = cmd.join("\n")
+ console.log("composed", composed)
+ const parse = esprima.parse
+ try {
+ parse(composed)
+ } catch (err) {
+ console.log("err", err)
+ if (err.description.match(/Unexpected end of input/)) {
+ ps = shell.PARSE_STATUS.INCOMPLETE
+ }
+ }
+ if (ps == shell.PARSE_STATUS.OK) {
+ /*
+ // TODO move the "shell.response" code into "composed"
+ // wrap async code
+ composed = [
+ '(async () => { ', composed, '; })()',
+ '.then((result) => TODO)',
+ '.catch((error) => TODO);'
+ ].join('');
+ */
+ try {
+ // eval javascript
+ // TODO mock/patch console.log, so we see output in gui
+ console.log("eval:\n" + composed)
+ var text,
+ result = window.eval(composed)
+ console.log("eval", { text, result })
+ try {
+ text = JSON.stringify(result)
+ } catch (e) {
+ text = result.toString()
+ }
+ // unix convention: newline after every line
+ text += "\n"
+ // send result to shell
+ shell.response(text)
+ } catch (e) {
+ shell.response(e.name + ": " + e.message + "\n", "shell-error")
+ }
+ }
+ }
+ callback.call(this, { parsestatus: ps })
+}
+
+/** one overloaded global method */
+window.print = function (a) {
+ shell.response(JSON.stringify(a))
+}
+
+/**
+ * this is the shell constructor
+ */
+var shell = new Shell({
+ container: "#shell-container",
+ //container: document.body,
+ // TODO syntax highlighting
+ //mode: 'javascript',
+ //mode: javascript,
+ exec_function,
+})
+
+/**
+ * set up style and focus
+ */
+//shell.setOption( "theme", "zenburn" );
+shell.focus();
+
+shell.response(banner, "banner")
diff --git a/demo/setImmediate.js b/demo/setImmediate.js
deleted file mode 100644
index 5abe55c..0000000
--- a/demo/setImmediate.js
+++ /dev/null
@@ -1,175 +0,0 @@
-(function (global, undefined) {
- "use strict";
-
- if (global.setImmediate) {
- return;
- }
-
- var nextHandle = 1; // Spec says greater than zero
- var tasksByHandle = {};
- var currentlyRunningATask = false;
- var doc = global.document;
- var setImmediate;
-
- function addFromSetImmediateArguments(args) {
- tasksByHandle[nextHandle] = partiallyApplied.apply(undefined, args);
- return nextHandle++;
- }
-
- // This function accepts the same arguments as setImmediate, but
- // returns a function that requires no arguments.
- function partiallyApplied(handler) {
- var args = [].slice.call(arguments, 1);
- return function() {
- if (typeof handler === "function") {
- handler.apply(undefined, args);
- } else {
- (new Function("" + handler))();
- }
- };
- }
-
- function runIfPresent(handle) {
- // From the spec: "Wait until any invocations of this algorithm started before this one have completed."
- // So if we're currently running a task, we'll need to delay this invocation.
- if (currentlyRunningATask) {
- // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a
- // "too much recursion" error.
- setTimeout(partiallyApplied(runIfPresent, handle), 0);
- } else {
- var task = tasksByHandle[handle];
- if (task) {
- currentlyRunningATask = true;
- try {
- task();
- } finally {
- clearImmediate(handle);
- currentlyRunningATask = false;
- }
- }
- }
- }
-
- function clearImmediate(handle) {
- delete tasksByHandle[handle];
- }
-
- function installNextTickImplementation() {
- setImmediate = function() {
- var handle = addFromSetImmediateArguments(arguments);
- process.nextTick(partiallyApplied(runIfPresent, handle));
- return handle;
- };
- }
-
- function canUsePostMessage() {
- // The test against `importScripts` prevents this implementation from being installed inside a web worker,
- // where `global.postMessage` means something completely different and can't be used for this purpose.
- if (global.postMessage && !global.importScripts) {
- var postMessageIsAsynchronous = true;
- var oldOnMessage = global.onmessage;
- global.onmessage = function() {
- postMessageIsAsynchronous = false;
- };
- global.postMessage("", "*");
- global.onmessage = oldOnMessage;
- return postMessageIsAsynchronous;
- }
- }
-
- function installPostMessageImplementation() {
- // Installs an event handler on `global` for the `message` event: see
- // * https://developer.mozilla.org/en/DOM/window.postMessage
- // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
-
- var messagePrefix = "setImmediate$" + Math.random() + "$";
- var onGlobalMessage = function(event) {
- if (event.source === global &&
- typeof event.data === "string" &&
- event.data.indexOf(messagePrefix) === 0) {
- runIfPresent(+event.data.slice(messagePrefix.length));
- }
- };
-
- if (global.addEventListener) {
- global.addEventListener("message", onGlobalMessage, false);
- } else {
- global.attachEvent("onmessage", onGlobalMessage);
- }
-
- setImmediate = function() {
- var handle = addFromSetImmediateArguments(arguments);
- global.postMessage(messagePrefix + handle, "*");
- return handle;
- };
- }
-
- function installMessageChannelImplementation() {
- var channel = new MessageChannel();
- channel.port1.onmessage = function(event) {
- var handle = event.data;
- runIfPresent(handle);
- };
-
- setImmediate = function() {
- var handle = addFromSetImmediateArguments(arguments);
- channel.port2.postMessage(handle);
- return handle;
- };
- }
-
- function installReadyStateChangeImplementation() {
- var html = doc.documentElement;
- setImmediate = function() {
- var handle = addFromSetImmediateArguments(arguments);
- // Create a
+
+
+
+
+
+