./pre_terminal/readme.txt
Python programmers going into javascript miss the simplicity of console input/output using the input() function. This script sets up a command-line environment right in the browser, and can be used with your text input/output programs. 

The code below has the interface, as well as a sample multiplication training program to show you how to use it. 

Commands in this terminal are prefaced with a dot, so .ls will show a command listing, .clear clears the terminal, and .times runs the multiplication program. 

advanced practice:
write your own input/output program that uses this interface. This can be a quiz or a chat bot or anything you like.


./pre_terminal/main.html
<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <title>Ѭ</title>
<link href="style.css" rel="stylesheet"/>
</head>


<body>
			
	<pre id="terminal" contextmenu="menu"></pre>
	<script src="terminal.js"></script>
	<Script src="multiply.js"></script>

</body>
</html>
./pre_terminal/style.css
body {
	display: flex;
	background-color : #123;
	justify-content : center;
}

pre {
	background-color: #234;
	width : 85vw;
	min-height : 80vh;
	border-radius: 30px;
	padding : 36px;
	font: 18px Courier;
	letter-spacing : 2px;
	color : white;
	white-space: pre-wrap;
	word-break: break-all;
	cursor: text;
}
./pre_terminal/terminal.js
/* 
 * This script sets up a terminal input/output user interface
 * in the browser window, through a pre-formatted element <pre>.
 *
 * It offers the application-programmer-interface of these functions:
 *
 * 1) print(string)
 * 2) ask(string, callback)
 *
 * The first function just prints to screen, the second prints
 * to screen but also identifies a receiving function for the user
 * input to be sent to. This is not quite like input() in Python
 * which returns the input as a string. This ask function sends the
 * input directly to a function whenever the ENTER key is pressed.
 * That function should also be responsible for continuing execution.
 */

const terminal = document.querySelector("#terminal");
	// The terminal is just a <pre> element

const cursor = "█";
	// The cursor is just a block character for now...

const keys_down = new Set();
	// to keep track of combinations of keys being held
	// like CTRL-V for pasting

const COMMANDS = {'.clear': clear,
				  '.ls': listing};
	// storage for command line commands

function listing() {
	print('\n');
	for (command in COMMANDS)
		print(command + '\n');
	print('\n');
}

let buffer = [];
	// this is where the current input line is collected 
	// to be sent out to callback functions.

const buffer_history = [];
let history_index = 0;
	// to be able to cycle through commands with arrows

let input_lock = false;
	// optional input_lock 

function NO_OP(input) {return 0;};
let LISTENER = NO_OP;
	// placeholder for the function currently listening for input

function clear() {
	terminal.innerHTML = cursor;
}

function clear_line() {
	let content = terminal.innerHTML;
	terminal.innerHTML = content.slice(0, content.lastIndexOf("\n")+2);
}

function print(c) {
	// write a character to screen and progress the cursor
	terminal.innerHTML = terminal.innerHTML.slice(0,-1) + c + cursor;
	window.scrollTo(0,document.body.scrollHeight);
}

//TODO backspace doesn't work with this yet. 
function print_color(c, r, g, b) {
	let entry = "<span style='color:rgb(" + r + "," + g + "," + b + ")'>" + c + "</span>";
	// write a character to screen and progress the cursor
	terminal.innerHTML = terminal.innerHTML.slice(0,-1) + entry + cursor;
	window.scrollTo(0,document.body.scrollHeight);
}

// print text and set up function to receive input
function ask(text, func) {
	print(text);
	LISTENER = func;
}

function backspace() {
	let content = terminal.innerHTML;
	if (terminal.innerHTML.endsWith('\n' + cursor)){
		return;
	}
	if (terminal.innerHTML.endsWith(";" + cursor)){ 
		// for special characters like >; <; &;
		terminal.innerHTML = content.slice(0,content.lastIndexOf("&")) + cursor;
	} else {
		terminal.innerHTML = content.slice(0,-2) + cursor;
		buffer.pop();
	}
}

document.addEventListener("keydown", e => {
	let k = e.key;

	keys_down.add(k);
	
	if (["Shift", "Alt", "Tab", "'", "/", "ArrowUp", "ArrowDown"].includes(k)){
		e.preventDefault();
	}

	if (input_lock === true) return;

	// special keys----------------------------
	if (k === "Enter") {
		print('\n');
		if (LISTENER === NO_OP){ // standard cli mode
			if (buffer[0] === '.'){ // command entered
				args = buffer.join('').split(' ');
				if (COMMANDS.hasOwnProperty(args[0])){
					COMMANDS[args[0]](args.slice(1));
				}
			}
			if (buffer.join("").trim().length > 0){
				buffer_history.push(buffer);
			}
		} else {
			LISTENER(buffer);
		}
		buffer = [];	
		history_index = buffer_history.length;
	} else if (k === "Backspace"){ 	
		backspace();
	} else if (k === "Tab"){
		print("&ensp;&ensp;&ensp;&ensp;");
	} else if (k === "ArrowUp"){
		if (LISTENER === NO_OP){
			if (history_index > 0){
				history_index -= 1;
				buffer = buffer_history[history_index];
				clear_line();
				print(buffer.join(''));
			}
		}
	} else if (k === "ArrowDown"){
		if (LISTENER === NO_OP){
			if (history_index < buffer_history.length - 1){
				history_index += 1;
				buffer = buffer_history[history_index];
			} else {
				buffer = [];
				history_index = buffer_history.length;
			}
			clear_line();
			print(buffer.join(''));
		}
	
	// normal text entry:----------------------
	} else if (k.length === 1) {
		if (keys_down.has("Control") && ('+-=v'.includes(k)))
			return;
		print(k);
		buffer.push(k);
	}
});

document.addEventListener("keyup", e => {
	keys_down.delete(e.key);
});

terminal.addEventListener('paste', (event) => { // CTRL-V pasting
    let paste = (event.clipboardData || 
							window.clipboardData).getData('text');
	print(paste);
	buffer.push(paste);
    event.preventDefault();
});

./pre_terminal/multiply.js
let cards = [];
let start_time;

function check(my_answer){
	my_answer = my_answer.join("");

	if (my_answer === ':q'){ // allow users a way out
		finish();
		return;
	}

	if (my_answer === cards[cards.length-1].answer){
		cards.pop();
		if (cards.length === 0) {
			finish();
			return;
		}
	}

	ask(cards[cards.length-1].question, check);
}

function init_multiplication(args) {
	n = 12; // default
	if (args[0])
		n = Number(args[0]);
	for (let i = 1; i < n+1; i++)
		for (let j = i; j < n+1; j++)
			cards.push({question : i + ' x ' + j + ' = ',
						answer   : (i*j).toString()});
	scramble();
	print("\nLets practice multiplication!\n");
	print("Enter :q any time to quit\n\n");
	start_time = new Date().getTime()/1000;
	ask(cards[cards.length-1].question, check);
}

function finish(){
	if (cards.length === 0){
		let elapsed = new Date().getTime()/1000 - start_time;
		print("\nyou took " + Math.round(elapsed) + " seconds!\n");
		print("great job!\n-------------\n");
	} else { // early exit
		print("quit\n\n");
	}
	cards = [];
	LISTENER = NO_OP; // important: stop listening
}

function scramble(){
	// swap two random cards 300 times
	for (let i = 0; i < 300; i++){
		let a = Math.floor(Math.random()*cards.length);
		let b = Math.floor(Math.random()*cards.length);
		let temp = cards[a];
		cards[a] = cards[b];
		cards[b] = temp;
	}
	return 0;
}
	
COMMANDS['.times'] = init_multiplication;