+
JavaScript Terminal
+
A completely useless project!
+
+
+
+
+
diff --git a/public/terminal/js/jash.js b/public/terminal/js/jash.js
new file mode 100644
index 0000000..2786665
--- /dev/null
+++ b/public/terminal/js/jash.js
@@ -0,0 +1,299 @@
+/**
+* Copyright (c) 2016 Aleksi 'Allexit' Talarmo
+*
+*
+* 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.
+*/
+var Jash;
+(function (Jash) {
+ class SH {
+ constructor() {
+ this.cursor = { x: 0, y: 0 };
+ this.scroll = 0;
+ this.messages = [];
+ this.defaultStyle = "";
+ this.currentPrintStyle = "";
+ this.listenerId = 0;
+ this.currentReadlines = [];
+ this.currentReadline = "";
+ }
+ /**
+ * Inherited from Program-interface. Not to be manually called.
+ */
+ create(terminal, stdio, args) {
+ this.terminal = terminal;
+ this.stdio = stdio;
+ this.stdio.setOutput(this);
+ this.stdio.setInput(this);
+ this.stdio.addListener(this);
+ }
+ /**
+ * Inherited from Program-interface. Not to be manually called.
+ */
+ enable() {
+ this.refresh();
+ if (this.listenerId < 0) {
+ this.stdio.addListener(this);
+ }
+ }
+ /**
+ * Inherited from Program-interface. Not to be manually called.
+ */
+ disable() {
+ if (this.listenerId >= 0) {
+ this.stdio.removeListener(this.listenerId);
+ this.listenerId = -1;
+ }
+ }
+ /**
+ * Inherited from Program-interface. Not to be manually called.
+ */
+ onClose() {
+ if (this.listenerId >= 0) {
+ this.stdio.removeListener(this.listenerId);
+ this.listenerId = -1;
+ }
+ return true;
+ }
+ /**
+ * Inherited from TerminalInput.KeyListener-interface. Not to be manually called.
+ */
+ onListen(listenerId) {
+ this.listenerId = listenerId;
+ }
+ /**
+ * Inherited from TerminalInput.KeyListener-interface. Not to be manually called.
+ */
+ handleKeypress(keypress) {
+ }
+ /**
+ * Inherited from TerminalInput.KeyListener-interface. Not to be manually called.
+ */
+ handleKeydown(keydown) {
+ if (this.currentReadlines.length == 0) {
+ return;
+ }
+ let key = keydown.key;
+ if (key.length == 1) {
+ this.currentReadline += key;
+ if (this.currentReadlines[0].onChangeCallback !== undefined) {
+ this.currentReadlines[0].onChangeCallback(this.currentReadline);
+ }
+ if (key == "/") {
+ keydown.preventDefault();
+ }
+ }
+ else {
+ switch (keydown.keyCode) {
+ case (32): {
+ this.currentReadline += " ";
+ if (this.currentReadlines[0].onChangeCallback !== undefined) {
+ this.currentReadlines[0].onChangeCallback(this.currentReadline);
+ }
+ break;
+ }
+ case (8): {
+ this.currentReadline = this.currentReadline.substr(0, this.currentReadline.length - 1);
+ if (this.currentReadlines[0].onChangeCallback !== undefined) {
+ this.currentReadlines[0].onChangeCallback(this.currentReadline);
+ }
+ keydown.preventDefault();
+ break;
+ }
+ case (13): {
+ let currReadline = this.currentReadlines[0];
+ if (currReadline.printAfterDone) {
+ let text = currReadline.prefix + this.currentReadline;
+ this.print(text, currReadline.style);
+ }
+ this.currentReadlines.shift().callback(this.currentReadline);
+ this.currentReadline = "";
+ }
+ case (0): {
+ if (keydown.key == "Dead") {
+ this.currentReadline += "~";
+ if (this.currentReadlines[0].onChangeCallback !== undefined) {
+ this.currentReadlines[0].onChangeCallback(this.currentReadline);
+ }
+ }
+ }
+ }
+ }
+ this.refresh();
+ }
+ /**
+ * Inherited from TerminalInput.KeyListener-interface. Not to be manually called.
+ */
+ handleKeyup(keyup) {
+ }
+ /**
+ * Inherited from Std.Input-interface. Not to be manually called.
+ */
+ readline(readline) {
+ this.currentReadline = readline.default || "";
+ this.currentReadlines.push(readline);
+ this.refresh();
+ }
+ /**
+ * Inherited from Std.Output-interface. Not to be manually called.
+ */
+ print(text, style) {
+ style = style || this.currentPrintStyle || this.defaultStyle;
+ let isAtEnd = (this.messages.length - this.terminal.height - this.scroll) <= 0;
+ for (let i = 0; i < text.length; i++) {
+ let char = text[i];
+ if (char == "\n") {
+ if (this.cursor.y != 0 || this.cursor.x != 0) {
+ this.moveCursorNewline();
+ }
+ continue;
+ }
+ this.putCharacter(new JSTerminal.Character(char, style));
+ }
+ if (this.messages.length > this.terminal.height) {
+ this.scroll = this.messages.length - this.terminal.height;
+ }
+ }
+ /**
+ * Inherited from Std.Output-interface. Not to be manually called.
+ */
+ println(text, style) {
+ this.print("\n" + text, style || this.currentPrintStyle || this.defaultStyle);
+ }
+ /**
+ * Inherited from Std.Output-interface. Not to be manually called.
+ */
+ clear(style) {
+ this.messages = [];
+ this.scroll = 0;
+ }
+ /**
+ * Inherited from Std.Output-interface. Not to be manually called.
+ */
+ refresh(alsoRenderTerminal = true) {
+ this.terminal.clearScreen(this.defaultStyle);
+ let toDraw = [];
+ let messages = [];
+ for (let i in this.messages) {
+ messages.push(this.messages[i].slice());
+ }
+ if (this.currentReadlines.length > 0 && !this.currentReadlines[0].hidden) {
+ let readline = this.currentReadlines[0];
+ let readlinePart = (readline.prefix || "") + this.currentReadline;
+ let tempCursorPos = { x: this.cursor.x, y: this.cursor.y };
+ for (let i = 0; i < readlinePart.length; i++) {
+ let char = readlinePart[i];
+ if (char == "\n") {
+ this.moveCursorNewline();
+ continue;
+ }
+ this.putCharacterTo(new JSTerminal.Character(char, (readline.style || this.defaultStyle)), messages);
+ }
+ this.cursor = tempCursorPos;
+ }
+ let start = messages.length - 1;
+ for (let i = this.scroll; i < messages.length && toDraw.length < this.terminal.height; i++) {
+ if (i < 0 || messages[i].length == 0) {
+ toDraw.push([]);
+ continue;
+ }
+ let rows = Math.ceil(messages[i].length / this.terminal.width);
+ for (let r = 0; r < rows; r++) {
+ toDraw.push(messages[i].slice(r * this.terminal.width, (r + 1) * this.terminal.width));
+ }
+ }
+ toDraw = toDraw.slice(-this.terminal.height);
+ toDraw.forEach((row, y) => {
+ row.forEach((c, x) => {
+ this.terminal.putTrueChar(c, x, y);
+ });
+ });
+ if (!alsoRenderTerminal) {
+ return;
+ }
+ this.terminal.render();
+ }
+ /**
+ * Utility function for print. Prints out one character at cursor.
+ */
+ putCharacter(character) {
+ this.putCharacterTo(character, this.messages);
+ }
+ putCharacterTo(character, charArray) {
+ while (this.cursor.y >= charArray.length) {
+ charArray.push([]);
+ }
+ if (this.cursor.x >= charArray[this.cursor.y].length) {
+ charArray[this.cursor.y].push(character);
+ this.moveCursor(1);
+ return;
+ }
+ charArray[this.cursor.y][this.cursor.x] = character;
+ this.moveCursor(1);
+ }
+ /**
+ * Removes a character and moves the cursor backwards.
+ */
+ removeCharacter() {
+ if (this.messages[this.cursor.y] == []) {
+ let rowToRemove = this.cursor.y;
+ this.moveCursor(-1);
+ this.messages.splice(this.cursor.y, 1);
+ return;
+ }
+ let toRemove = { x: this.cursor.x, y: this.cursor.y };
+ this.moveCursor(-1);
+ this.messages[toRemove.y].splice(toRemove.x - 1, 1);
+ }
+ /**
+ * Set the default style to be used when printing and clearing the screen.
+ */
+ setDefaultStyle(style) {
+ this.defaultStyle = style;
+ }
+ /**
+ * Set the current 'default' style for printing.
+ * This is only temporary and can be reversed with calling it with a null-parameter.
+ */
+ setStyle(style) {
+ this.currentPrintStyle = style;
+ }
+ /**
+ * Moves the cursor by 'amount' forwards or backwards.
+ * Handles text wrapping.
+ */
+ moveCursor(amount) {
+ this.cursor.x += amount;
+ this.cursor.y += Math.floor((this.cursor.x) / (this.terminal.width));
+ this.cursor.x = this.cursor.x % (this.terminal.width);
+ if (this.cursor.x < 0) {
+ this.cursor.x = this.messages[this.cursor.y].length - 1 - this.cursor.x;
+ }
+ }
+ /*
+ * Moves the cursor vertically by amount. Works weirdly if cursor.x not handled manually.
+ */
+ moveCursorVertically(amount) {
+ this.cursor.y += amount;
+ }
+ /*
+ * Moves the cursor down to the next line and sets the x to 0.
+ */
+ moveCursorNewline() {
+ this.moveCursorVertically(1);
+ this.cursor.x = 0;
+ }
+ /*
+ * Does the opposite as moveCursorNewline
+ */
+ moveCursorPreviousLine() {
+ this.moveCursorVertically(-1);
+ this.cursor.x = this.terminal.width - 1;
+ }
+ }
+ Jash.SH = SH;
+})(Jash || (Jash = {}));
diff --git a/public/terminal/js/jsterminal.js b/public/terminal/js/jsterminal.js
new file mode 100644
index 0000000..58fa597
--- /dev/null
+++ b/public/terminal/js/jsterminal.js
@@ -0,0 +1,156 @@
+/**
+* Copyright (c) 2016 Aleksi 'Allexit' Talarmo
+*
+*
+* 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.
+*/
+var JSTerminal;
+(function (JSTerminal) {
+ class TerminalProgram {
+ create(terminal, stdio) { }
+ enable() { }
+ disable() { }
+ onClose() { return false; }
+ }
+ class Terminal {
+ constructor(selector, width, height) {
+ this.selector = selector;
+ this.width = width;
+ this.height = height;
+ this.lineElems = [];
+ this.characters = [];
+ for (let i = 0; i < this.height; i++) {
+ let characterLine = [];
+ for (let i2 = 0; i2 < this.width; i2++) {
+ characterLine.push(new Character(" ", ""));
+ }
+ this.characters.push(characterLine);
+ let line = " ".repeat(this.width);
+ let element = $(document.createElement("p"));
+ element.text(line);
+ this.lineElems.push(element);
+ $(selector).append(element);
+ }
+ this.stdio = new Std.IO(this);
+ this.programStack = [];
+ this.programStack.push(new TerminalProgram());
+ }
+ /**
+ * Used to simply clear the terminal screen.
+ * Useful for when you don't use Jash/JSX.
+ */
+ clearScreen(styles = "") {
+ for (let y = 0; y < this.height; y++) {
+ for (let x = 0; x < this.width; x++) {
+ this.characters[y][x] = new Character(" ", styles);
+ }
+ }
+ }
+ /**
+ * Used to write text at a certain coordinate on screen.
+ * Useful for when you want to make your own rendering systems
+ * and don't want to use regular text-based systems.
+ */
+ writeText(text, x, y, styles = "") {
+ for (let i = 0; i < text.length; i++) {
+ this.putChar(text[i], x + i, y, styles);
+ }
+ }
+ /**
+ * Utility function for writeText. Can also be used but probably not as useful.
+ * Prints only one char (first from given string).
+ */
+ putChar(char, x, y, styles = "") {
+ this.putTrueChar(new Character(char, styles), x, y);
+ }
+ /**
+ * Utility function when you might want to print out a true
+ * Character-class. (works similarily to putChar)
+ */
+ putTrueChar(char, x, y) {
+ if (x >= this.width || y >= this.height) {
+ return;
+ }
+ this.characters[y][x] = char;
+ }
+ /**
+ * Empties the screen and re-draws it. Required to call every time
+ * you want to change the screen.
+ */
+ render() {
+ for (let y = 0; y < this.characters.length; y++) {
+ let line = this.characters[y];
+ let lineElem = this.lineElems[y];
+ lineElem.empty();
+ let currSpan = null;
+ let currStyle = null;
+ let currText = "";
+ for (let x = 0; x < line.length + 1; x++) {
+ let currChar = line[x];
+ if (x == line.length || currStyle != currChar.styles) {
+ if (currSpan != null) {
+ currSpan.text(currText);
+ currSpan.addClass(currStyle);
+ lineElem.append(currSpan);
+ }
+ if (x >= line.length) {
+ break;
+ }
+ currText = "";
+ currSpan = $(document.createElement("span"));
+ currStyle = currChar.styles;
+ }
+ currText += currChar.char;
+ }
+ }
+ }
+ /**
+ * Used to launch programs like Jash.
+ */
+ launchProgram(program, args) {
+ this.programStack.slice(-1).pop().disable();
+ this.programStack.push(program);
+ program.create(this, this.stdio, args);
+ }
+ /**
+ * Used to close programs like Jash.
+ */
+ closeProgram(program) {
+ let idx = this.programStack.indexOf(program);
+ if (idx == -1) {
+ return;
+ }
+ if (this.programStack[idx].onClose()) {
+ if (idx == this.programStack.length - 1) {
+ this.programStack[this.programStack.length - 2].enable();
+ }
+ this.programStack.splice(idx, 1);
+ }
+ }
+ }
+ JSTerminal.Terminal = Terminal;
+ /**
+ * Utility function for creating the terminal.
+ * Initializes the class and sets up some classes you might want to use.
+ */
+ function createTerminal(selector, width, height) {
+ console.info(`Creating a terminal with ${width}x${height}`);
+ $(selector).addClass("js-terminal-style");
+ $(selector).css('width', width + "ch");
+ $(selector).css('height', height + "em");
+ $(selector).empty();
+ return new Terminal(selector, width, height);
+ }
+ JSTerminal.createTerminal = createTerminal;
+ class Character {
+ constructor(char, styles) {
+ this.char = char[0];
+ this.styles = styles;
+ }
+ }
+ JSTerminal.Character = Character;
+})(JSTerminal || (JSTerminal = {}));
diff --git a/public/terminal/js/jsx.js b/public/terminal/js/jsx.js
new file mode 100644
index 0000000..2971043
--- /dev/null
+++ b/public/terminal/js/jsx.js
@@ -0,0 +1,336 @@
+/**
+* This file is licensed under the GPLv3 license (c) Aleksi 'Allexit' Talarmo
+* See COPYING-GPL and README.md for more information.
+*/
+var JavaScriptX;
+(function (JavaScriptX) {
+ class JSX {
+ constructor() {
+ this.environmentVariables = {
+ "system": "~/.system/"
+ };
+ }
+ create(terminal, stdio, args) {
+ this.terminal = terminal;
+ this.stdio = stdio;
+ this.filesystem = new FileSystem();
+ this.currentFolder = this.filesystem.rootFolder;
+ let system = new Folder(".system", true);
+ this.filesystem.rootFolder.addFile(system);
+ system.addFile(new BinaryFile("echo", new JSXCommands.Echo()));
+ system.addFile(new BinaryFile("ls", new JSXCommands.List(this)));
+ system.addFile(new BinaryFile("cd", new JSXCommands.ChangeDirectory(this)));
+ system.addFile(new BinaryFile("mkdir", new JSXCommands.MakeDirectory(this)));
+ system.addFile(new BinaryFile("woman", new JSXCommands.Woman(this)));
+ system.addFile(new BinaryFile("cat", new JSXCommands.Caternate(this)));
+ system.addFile(new BinaryFile("touch", new JSXCommands.Touch(this)));
+ system.addFile(new BinaryFile("jedi", new JSXCommands.JEdi(this)));
+ this.filesystem.rootFolder.addFile(new TextFile("textfile.txt", "Hello, world!"));
+ stdio.println("Welcome to ");
+ stdio.print("JSX", "red");
+ stdio.print(" version ");
+ stdio.print("0.4.0 beta", "green");
+ stdio.print(" (K) All rites reversed");
+ stdio.println("\nTo get started: call a ");
+ stdio.print("woman", "blue");
+ stdio.print(" for help.");
+ stdio.refresh();
+ this.takeCommand();
+ }
+ /**
+ * Inherited from Program-interface. Not to be manually called.
+ */
+ enable() {
+ this.takeCommand();
+ }
+ /**
+ * Inherited from Program-interface. Not to be manually called.
+ */
+ disable() {
+ }
+ /**
+ * Inherited from Program-interface. Not to be manually called.
+ */
+ onClose() {
+ return true;
+ }
+ /**
+ * Utility function. Writes a line such as 'Anonymous@localhost: ~/' and then reads line.
+ * The result of the readline is used to call commands and such.
+ */
+ takeCommand() {
+ let name = window.location.hostname || "localhost";
+ this.stdio.println(`\nAnonymous@${name}`, "yellow");
+ this.stdio.print(":");
+ let path = this.currentFolder.getPath();
+ this.stdio.print(path, "green");
+ this.stdio.readline({
+ callback: (result) => {
+ let split = result.split(" ");
+ let command = split[0];
+ let args = split.slice(1, split.length);
+ for (let v in this.environmentVariables) {
+ if (command == v) {
+ command = this.environmentVariables[v];
+ break;
+ }
+ let file = this.findPath(this.environmentVariables[v]);
+ if (file instanceof Folder) {
+ for (let i = 0; i < file.files.length; i++) {
+ let f = file.files[i];
+ if (command == f.name) {
+ command = f.getPath();
+ break;
+ }
+ }
+ }
+ }
+ for (let i = 0; i < args.length; i++) {
+ let regexp = /\${[\w]*}/g;
+ let arg = args[i];
+ let result;
+ while ((result = regexp.exec(arg)) != null) {
+ let match = result[0];
+ let v = match.substr(2, match.length - 3);
+ let toReplace = null;
+ for (let env in this.environmentVariables) {
+ if (env == v) {
+ toReplace = this.environmentVariables[env];
+ break;
+ }
+ }
+ if (toReplace != null) {
+ args[i] = arg.replace(new RegExp("\\" + match, "g"), toReplace);
+ }
+ }
+ }
+ let file = this.findPath(command);
+ if (file && (command.startsWith("./")
+ || command.startsWith("~/")
+ || command.startsWith("../"))) {
+ if (file instanceof BinaryFile) {
+ file.run(this.terminal, args);
+ return;
+ }
+ else {
+ this.stdio.println(`${file.name} is not executable`);
+ }
+ }
+ else {
+ if (command.startsWith(".")) {
+ this.stdio.println(`File not found`);
+ }
+ else {
+ this.stdio.println(`Command not found`);
+ }
+ }
+ this.takeCommand();
+ },
+ prefix: " > ",
+ printAfterDone: true,
+ style: "red"
+ });
+ this.stdio.refresh();
+ }
+ /**
+ * Returns the file at the specified path.
+ * Example usage: findPath("~/somefolder/hello/");
+ */
+ findPath(path) {
+ let originalPath = path.slice();
+ let pathPieces = path.split("/");
+ let relativeFolder = this.currentFolder;
+ let fileFound = null;
+ for (let i = 0; i < pathPieces.length; i++) {
+ let p = pathPieces[i];
+ if (p == "") {
+ if (relativeFolder == null) {
+ fileFound = null;
+ break;
+ }
+ if (i == pathPieces.length - 1 && relativeFolder instanceof Folder) {
+ fileFound = relativeFolder;
+ break;
+ }
+ fileFound = null;
+ break;
+ }
+ else if (p == "..") {
+ if (i == 0) {
+ if (this.currentFolder.parentFolder == null) {
+ fileFound = null;
+ break;
+ }
+ relativeFolder = this.currentFolder.parentFolder;
+ }
+ else if (relativeFolder == null) {
+ fileFound = null;
+ break;
+ }
+ else if (relativeFolder.parentFolder == null) {
+ fileFound = null;
+ break;
+ }
+ else {
+ relativeFolder = relativeFolder.parentFolder;
+ }
+ if (i == pathPieces.length - 1) {
+ fileFound = relativeFolder;
+ break;
+ }
+ }
+ else if (p == ".") {
+ if (i == 0) {
+ relativeFolder = this.currentFolder;
+ }
+ if (i == pathPieces.length - 1) {
+ fileFound = relativeFolder;
+ break;
+ }
+ continue;
+ }
+ else if (p == "~" && i == 0) {
+ relativeFolder = this.filesystem.rootFolder;
+ if (i == pathPieces.length - 1) {
+ fileFound = relativeFolder;
+ break;
+ }
+ continue;
+ }
+ else if (i == pathPieces.length - 1) {
+ if (relativeFolder == null) {
+ fileFound = null;
+ break;
+ }
+ let idx = relativeFolder.files.map((f) => { return f.name; })
+ .indexOf(p);
+ let found = relativeFolder.files[idx];
+ if (idx == -1) {
+ break;
+ }
+ else {
+ fileFound = found;
+ }
+ }
+ else {
+ if (relativeFolder == null) {
+ fileFound = null;
+ break;
+ }
+ let idx = relativeFolder.files.map((f) => { return f.name; })
+ .indexOf(p);
+ let found = relativeFolder.files[idx];
+ if (idx == -1) {
+ break;
+ }
+ else if (found instanceof Folder) {
+ relativeFolder = found;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ return fileFound;
+ }
+ }
+ JavaScriptX.JSX = JSX;
+ class FileSystem {
+ constructor() {
+ this.rootFolder = new Folder("~");
+ }
+ }
+ class File {
+ constructor(name, hidden = false) {
+ this.created = new Date().getTime();
+ this.name = name;
+ this.hidden = hidden;
+ }
+ /**
+ * Returns the string path of the file.
+ * Example: If the file is in root folder (~) and inside a 'somefolder'-folder, it would return "~/somefolder/