2019-10-20 23:55:04 +02:00
|
|
|
import {addOnLoad, asciiHeaderHtml, moveCaretToEndOf, q} from "./shared.js";
|
|
|
|
import {FileSystem} from "./fs.js";
|
|
|
|
import {Commands} from "./commands.js";
|
|
|
|
|
|
|
|
|
|
|
|
export class Terminal {
|
|
|
|
private _terminal: any;
|
|
|
|
private _input: any;
|
|
|
|
private _output: any;
|
|
|
|
private _prefixDiv: any;
|
|
|
|
private _user: string;
|
|
|
|
private _loggedIn: boolean;
|
|
|
|
private _inputHistory: InputHistory;
|
|
|
|
private _fs:FileSystem;
|
|
|
|
private _commands: Commands;
|
|
|
|
|
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
constructor(terminal, input, output, prefixDiv) {
|
|
|
|
this._terminal = terminal;
|
|
|
|
this._input = input;
|
|
|
|
this._output = output;
|
|
|
|
this._prefixDiv = prefixDiv;
|
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
this._user = "felix";
|
|
|
|
this._loggedIn = true;
|
|
|
|
|
|
|
|
this._inputHistory = new InputHistory();
|
2018-11-28 22:23:11 +01:00
|
|
|
this._fs = new FileSystem();
|
|
|
|
this._commands = new Commands(this, this._fs);
|
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
this._terminal.addEventListener("click", this._onclick.bind(this));
|
|
|
|
this._terminal.addEventListener("keypress", this._onkeypress.bind(this));
|
2018-11-29 01:08:58 +01:00
|
|
|
this._terminal.addEventListener("keydown", this._onkeydown.bind(this));
|
2018-11-28 21:29:28 +01:00
|
|
|
|
|
|
|
this.reset();
|
|
|
|
this._input.focus();
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
get inputText() {
|
|
|
|
return this._input.innerHTML
|
2018-11-28 22:41:59 +01:00
|
|
|
.replaceAll(/<br>/, "");
|
2018-11-28 21:29:28 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
set inputText(inputText) {
|
|
|
|
this._input.innerHTML = inputText;
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
get outputText() {
|
|
|
|
return this._output.innerHTML;
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
set outputText(outputText) {
|
|
|
|
this._output.innerHTML = outputText;
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
get prefixText() {
|
|
|
|
return this._prefixDiv.innerHTML;
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
set prefixText(prefixText) {
|
|
|
|
this._prefixDiv.innerHTML = prefixText;
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
static generateHeader() {
|
2018-11-28 21:36:04 +01:00
|
|
|
return "" +
|
2018-11-30 17:38:33 +01:00
|
|
|
`${asciiHeaderHtml}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 01:08:58 +01:00
|
|
|
Student MSc Computer Science <span class="smallScreenOnly">
|
|
|
|
</span>@ <a href="https://www.tudelft.nl/en/">TU Delft</a>, the Netherlands
|
|
|
|
<span class="wideScreenOnly">${(new Date()).toISOString()}
|
|
|
|
</span>
|
2019-06-10 15:36:03 +02:00
|
|
|
Type "<a href="#" onclick="run('help');">help</a>" for help.
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 01:08:58 +01:00
|
|
|
`.trimLines();
|
2018-11-28 21:29:28 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 22:23:11 +01:00
|
|
|
generatePrefix() {
|
2018-11-29 16:10:02 +01:00
|
|
|
if (!this._loggedIn) {
|
|
|
|
if (this._user === undefined) {
|
|
|
|
return "login as: ";
|
|
|
|
} else {
|
|
|
|
return `Password for ${this._user}@fwdekker.com: `;
|
|
|
|
}
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
return `${this._user}@fwdekker.com <span style="color: green;">${this._fs.pwd}</span>> `;
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
clear() {
|
|
|
|
this.outputText = "";
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
reset() {
|
2018-11-28 22:23:11 +01:00
|
|
|
this._fs.reset();
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
this.outputText = Terminal.generateHeader();
|
2018-11-28 22:23:11 +01:00
|
|
|
this.prefixText = this.generatePrefix();
|
2018-11-28 21:29:28 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
continueLogin(input) {
|
|
|
|
if (this._user === undefined) {
|
2018-11-29 16:19:50 +01:00
|
|
|
this.outputText += `${this.prefixText}${input}\n`;
|
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
this._user = input.trim();
|
2018-11-29 16:19:50 +01:00
|
|
|
this._input.classList.add("terminalCurrentFocusInputHidden");
|
2018-11-29 16:10:02 +01:00
|
|
|
} else {
|
2018-11-29 16:19:50 +01:00
|
|
|
this.outputText += `${this.prefixText}\n`;
|
|
|
|
|
2018-11-29 18:31:39 +01:00
|
|
|
if ((this._user === "felix" && input === "hotel123")
|
|
|
|
|| (this._user === "root" && input === "password")) {
|
2018-11-29 16:10:02 +01:00
|
|
|
this._loggedIn = true;
|
2018-11-29 18:28:37 +01:00
|
|
|
this.outputText += Terminal.generateHeader();
|
2018-11-29 16:10:02 +01:00
|
|
|
} else {
|
|
|
|
this._user = undefined;
|
2018-11-29 18:28:37 +01:00
|
|
|
this.outputText += "Access denied\n";
|
2018-11-29 16:10:02 +01:00
|
|
|
}
|
2018-11-29 16:19:50 +01:00
|
|
|
|
|
|
|
this._input.classList.remove("terminalCurrentFocusInputHidden");
|
2018-11-29 16:10:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logOut() {
|
|
|
|
this._user = undefined;
|
|
|
|
this._loggedIn = false;
|
|
|
|
this._inputHistory.clear();
|
|
|
|
}
|
|
|
|
|
2019-02-09 14:37:36 +01:00
|
|
|
ignoreInput() {
|
|
|
|
this.outputText += `${this.prefixText}${this.inputText}\n`;
|
|
|
|
this.prefixText = this.generatePrefix();
|
|
|
|
this.inputText = "";
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
processInput(input) {
|
|
|
|
this.inputText = "";
|
|
|
|
|
|
|
|
if (!this._loggedIn) {
|
|
|
|
this.continueLogin(input);
|
|
|
|
} else {
|
2018-11-29 16:19:50 +01:00
|
|
|
this.outputText += `${this.prefixText}${input}\n`;
|
2018-11-29 16:10:02 +01:00
|
|
|
this._inputHistory.addEntry(input);
|
|
|
|
|
|
|
|
const output = this._commands.parse(input.trim());
|
|
|
|
if (output !== "") {
|
|
|
|
this.outputText += output + `\n`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.prefixText = this.generatePrefix();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
_onclick() {
|
|
|
|
this._input.focus();
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
_onkeypress(e) {
|
|
|
|
switch (e.key.toLowerCase()) {
|
2018-11-28 22:41:59 +01:00
|
|
|
case "enter":
|
|
|
|
this.processInput(this.inputText.replaceAll(/ /, " "));
|
2019-02-09 14:32:19 +01:00
|
|
|
e.preventDefault();
|
2018-11-28 21:29:28 +01:00
|
|
|
break;
|
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
_onkeydown(e) {
|
|
|
|
switch (e.key.toLowerCase()) {
|
2018-11-28 22:41:59 +01:00
|
|
|
case "arrowup":
|
2018-11-28 21:29:28 +01:00
|
|
|
this.inputText = this._inputHistory.previousEntry();
|
2018-11-29 01:38:04 +01:00
|
|
|
window.setTimeout(() => moveCaretToEndOf(this._input), 0);
|
2018-11-28 21:29:28 +01:00
|
|
|
break;
|
2018-11-28 22:41:59 +01:00
|
|
|
case "arrowdown":
|
2018-11-28 21:29:28 +01:00
|
|
|
this.inputText = this._inputHistory.nextEntry();
|
2018-11-29 01:38:04 +01:00
|
|
|
window.setTimeout(() => moveCaretToEndOf(this._input), 0);
|
2018-11-29 00:31:43 +01:00
|
|
|
break;
|
2019-02-09 14:37:36 +01:00
|
|
|
case "c":
|
|
|
|
if (e.ctrlKey) {
|
|
|
|
this.ignoreInput();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
break;
|
2018-11-28 21:29:28 +01:00
|
|
|
}
|
2018-11-28 19:51:48 +01:00
|
|
|
}
|
2018-11-28 21:29:28 +01:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
class InputHistory {
|
2019-10-20 23:55:04 +02:00
|
|
|
private _history: string[];
|
|
|
|
private _index: number;
|
|
|
|
|
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
constructor() {
|
|
|
|
this._history = [];
|
|
|
|
this._index = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-20 23:55:04 +02:00
|
|
|
addEntry(entry: string) {
|
2019-06-10 15:31:46 +02:00
|
|
|
if (entry.trim() !== "")
|
2018-11-29 16:10:02 +01:00
|
|
|
this._history.unshift(entry);
|
2019-10-20 23:55:04 +02:00
|
|
|
|
2018-11-29 16:10:02 +01:00
|
|
|
this._index = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
clear() {
|
|
|
|
this._history = [];
|
|
|
|
this._index = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
getEntry(index) {
|
2019-06-10 15:31:46 +02:00
|
|
|
if (index >= 0)
|
2018-11-29 16:10:02 +01:00
|
|
|
return this._history[index];
|
2019-06-10 15:31:46 +02:00
|
|
|
else
|
2018-11-29 16:10:02 +01:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
nextEntry() {
|
|
|
|
this._index--;
|
2019-06-10 15:31:46 +02:00
|
|
|
if (this._index < -1)
|
2018-11-29 16:10:02 +01:00
|
|
|
this._index = -1;
|
|
|
|
|
|
|
|
return this.getEntry(this._index);
|
|
|
|
}
|
|
|
|
|
|
|
|
previousEntry() {
|
|
|
|
this._index++;
|
2019-06-10 15:31:46 +02:00
|
|
|
if (this._index >= this._history.length)
|
2018-11-29 16:10:02 +01:00
|
|
|
this._index = this._history.length - 1;
|
|
|
|
|
|
|
|
return this.getEntry(this._index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-28 21:29:28 +01:00
|
|
|
|
2019-10-20 23:55:04 +02:00
|
|
|
export let terminal;
|
2018-11-28 21:29:28 +01:00
|
|
|
|
|
|
|
addOnLoad(() => {
|
|
|
|
terminal = new Terminal(
|
2018-11-28 22:41:59 +01:00
|
|
|
q("#terminal"),
|
|
|
|
q("#terminalCurrentFocusInput"),
|
|
|
|
q("#terminalOutput"),
|
|
|
|
q("#terminalCurrentPrefix")
|
2018-11-28 21:29:28 +01:00
|
|
|
);
|
|
|
|
|
2018-11-28 22:41:59 +01:00
|
|
|
terminal.processInput("ls");
|
2018-11-28 19:51:48 +01:00
|
|
|
});
|
2019-04-08 22:42:11 +02:00
|
|
|
|
2019-10-20 23:55:04 +02:00
|
|
|
export function run(command: string) {
|
2019-04-08 22:42:11 +02:00
|
|
|
terminal.processInput(command);
|
|
|
|
}
|
|
|
|
|
2019-10-20 23:55:04 +02:00
|
|
|
export function relToAbs(filename) {
|
2019-04-08 22:42:11 +02:00
|
|
|
return terminal._fs.pwd + filename;
|
|
|
|
}
|