2022-12-15 13:21:26 +00:00
|
|
|
package bin;
|
|
|
|
|
2023-05-23 12:36:51 +00:00
|
|
|
import kernel.binstore.BinStore;
|
2023-05-22 20:25:37 +00:00
|
|
|
import kernel.ps.ProcessHandle;
|
2023-04-13 22:19:49 +00:00
|
|
|
import kernel.ps.Process;
|
|
|
|
import kernel.ps.ProcessManager;
|
2022-12-19 20:06:23 +00:00
|
|
|
import lib.Color;
|
2022-12-15 13:21:26 +00:00
|
|
|
import kernel.ui.WindowContext;
|
|
|
|
|
2023-01-28 02:55:32 +00:00
|
|
|
using tink.CoreApi;
|
|
|
|
|
2023-05-22 20:25:37 +00:00
|
|
|
class Terminal implements Process {
|
2023-06-26 18:04:26 +00:00
|
|
|
private static inline final MAX_BACKLOG:Int = 100;
|
|
|
|
|
2023-05-22 20:25:37 +00:00
|
|
|
private var handle:ProcessHandle;
|
2023-06-26 18:04:26 +00:00
|
|
|
|
|
|
|
private var ctx:WindowContext;
|
2023-07-30 13:55:22 +00:00
|
|
|
private var requestRender:() -> Void;
|
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
private var input:String = "";
|
|
|
|
private var backlog:Array<String> = [];
|
|
|
|
private var history:Array<String> = [];
|
|
|
|
private var historyIndex:Int = 0;
|
|
|
|
|
2023-07-30 13:55:22 +00:00
|
|
|
private var runningPID:PID = -1;
|
2022-12-15 13:21:26 +00:00
|
|
|
|
|
|
|
public function new() {}
|
|
|
|
|
2023-07-30 13:55:22 +00:00
|
|
|
public function run(handle:ProcessHandle):Void {
|
2023-05-22 20:25:37 +00:00
|
|
|
this.handle = handle;
|
|
|
|
|
|
|
|
var statelessContext = handle.createStatelessWindowContext();
|
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
this.ctx = statelessContext.ctx;
|
2023-05-22 20:25:37 +00:00
|
|
|
this.requestRender = statelessContext.requestRender;
|
|
|
|
|
|
|
|
statelessContext.setRenderFunc(this.render);
|
2022-12-15 13:21:26 +00:00
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
// Add input event handlers
|
|
|
|
handle.addCallbackLink(this.ctx.onChar.handle(char -> {
|
2023-07-30 13:55:22 +00:00
|
|
|
if (this.runningPID > 0)
|
|
|
|
return;
|
2022-12-15 13:21:26 +00:00
|
|
|
this.input += char;
|
2023-05-22 20:25:37 +00:00
|
|
|
this.requestRender();
|
2023-06-07 18:10:51 +00:00
|
|
|
}));
|
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
// Add key event handlers
|
|
|
|
handle.addCallbackLink(this.ctx.onKey.handle(e -> {
|
2023-06-07 18:10:51 +00:00
|
|
|
switch (e.keyCode) {
|
|
|
|
case 259: // Backspace
|
2023-07-30 13:55:22 +00:00
|
|
|
if (this.runningPID > 0)
|
|
|
|
return;
|
2023-06-07 18:10:51 +00:00
|
|
|
this.input = this.input.substr(0, this.input.length - 1);
|
|
|
|
this.requestRender();
|
|
|
|
case 257: // Enter
|
2023-07-30 13:55:22 +00:00
|
|
|
if (this.runningPID > 0)
|
|
|
|
return;
|
2023-06-07 18:10:51 +00:00
|
|
|
this.backlog.push("> " + this.input);
|
|
|
|
var command = this.input;
|
|
|
|
this.input = "";
|
|
|
|
this.requestRender();
|
2023-06-26 18:04:26 +00:00
|
|
|
this.historyIndex = 0;
|
2023-06-07 18:10:51 +00:00
|
|
|
this.invokeCommand(command);
|
|
|
|
case 269: // END
|
|
|
|
this.stopCurrentPS();
|
2023-06-26 18:04:26 +00:00
|
|
|
case 265: // UP
|
|
|
|
if (this.historyIndex < this.history.length) {
|
|
|
|
this.historyIndex++;
|
|
|
|
this.input = this.history[this.history.length - this.historyIndex];
|
|
|
|
this.requestRender();
|
|
|
|
}
|
2022-12-15 13:21:26 +00:00
|
|
|
}
|
2023-06-07 18:10:51 +00:00
|
|
|
}));
|
2022-12-15 13:21:26 +00:00
|
|
|
|
2023-05-22 20:25:37 +00:00
|
|
|
this.requestRender();
|
|
|
|
}
|
2023-01-28 02:55:32 +00:00
|
|
|
|
2023-06-07 18:10:51 +00:00
|
|
|
private function stopCurrentPS() {
|
|
|
|
if (this.runningPID < 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ProcessManager.kill(this.runningPID);
|
|
|
|
}
|
|
|
|
|
2023-05-22 20:25:37 +00:00
|
|
|
private function render() {
|
2023-06-26 18:04:26 +00:00
|
|
|
var size = this.ctx.getSize();
|
2022-12-15 13:21:26 +00:00
|
|
|
var linesAvailable = size.y - 1;
|
|
|
|
|
|
|
|
var start:Int = this.backlog.length - linesAvailable;
|
|
|
|
|
2022-12-19 16:35:52 +00:00
|
|
|
for (i in 0...linesAvailable) {
|
2022-12-15 13:21:26 +00:00
|
|
|
var line = this.backlog[start + i];
|
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
this.ctx.setCursorPos(0, i);
|
|
|
|
this.ctx.clearLine();
|
2022-12-15 13:21:26 +00:00
|
|
|
|
|
|
|
if (line != null) {
|
2023-06-26 18:04:26 +00:00
|
|
|
this.ctx.write(line);
|
2022-12-15 13:21:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
this.ctx.setCursorPos(0, size.y - 1);
|
|
|
|
this.ctx.clearLine();
|
2022-12-15 13:21:26 +00:00
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
this.ctx.setTextColor(Color.Blue);
|
|
|
|
this.ctx.write("> ");
|
2022-12-15 13:21:26 +00:00
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
this.ctx.setTextColor(Color.White);
|
|
|
|
this.ctx.write(this.input);
|
2022-12-15 13:21:26 +00:00
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
if (this.runningPID < 0) {
|
|
|
|
this.ctx.setCursorBlink(true);
|
|
|
|
} else {
|
|
|
|
this.ctx.setCursorBlink(false);
|
|
|
|
}
|
2022-12-15 13:21:26 +00:00
|
|
|
}
|
|
|
|
|
2022-12-19 16:35:52 +00:00
|
|
|
private function invokeCommand(command:String):Void {
|
2022-12-15 13:21:26 +00:00
|
|
|
var args = this.parseArgs(command);
|
|
|
|
|
|
|
|
if (args.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
2023-06-26 18:04:26 +00:00
|
|
|
|
|
|
|
this.history.push(command);
|
|
|
|
|
|
|
|
if (this.history.length > MAX_BACKLOG) {
|
|
|
|
this.history.shift();
|
|
|
|
}
|
|
|
|
|
2022-12-15 13:21:26 +00:00
|
|
|
var commandName = args[0];
|
|
|
|
|
|
|
|
// Handle built-in commands
|
|
|
|
switch (commandName) {
|
|
|
|
case "clear":
|
|
|
|
this.clear();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var commandArgs:Array<String> = args.slice(1);
|
|
|
|
var hadInput = false;
|
2023-04-13 22:19:49 +00:00
|
|
|
|
|
|
|
var ps = getProgByName(commandName);
|
|
|
|
if (ps == null) {
|
|
|
|
this.backlog.push("Unknown command: " + commandName);
|
2023-06-26 18:04:26 +00:00
|
|
|
this.requestRender();
|
2023-04-13 22:19:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-07-30 13:55:22 +00:00
|
|
|
this.runningPID = ProcessManager.run(ps, {
|
2023-04-13 22:19:49 +00:00
|
|
|
args: commandArgs,
|
2022-12-15 13:21:26 +00:00
|
|
|
onWrite: (s:String) -> {
|
2023-05-29 20:10:25 +00:00
|
|
|
if (s == "") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-15 13:21:26 +00:00
|
|
|
if (!hadInput) {
|
2023-05-29 20:10:25 +00:00
|
|
|
// Add a new line, so that the input is not on the same line as the command
|
2022-12-15 13:21:26 +00:00
|
|
|
this.backlog.push("");
|
|
|
|
hadInput = true;
|
|
|
|
}
|
2023-05-29 20:10:25 +00:00
|
|
|
|
|
|
|
for (line in s.split("\n")) {
|
2023-07-30 13:55:22 +00:00
|
|
|
if (line == "") {
|
2023-05-29 20:10:25 +00:00
|
|
|
this.backlog.push("");
|
|
|
|
} else {
|
|
|
|
this.backlog[this.backlog.length - 1] += s;
|
|
|
|
}
|
2023-06-26 18:04:26 +00:00
|
|
|
|
|
|
|
// Trim the backlog if it's too long
|
|
|
|
if (this.backlog.length > MAX_BACKLOG) {
|
|
|
|
this.backlog.shift();
|
|
|
|
}
|
2023-05-29 20:10:25 +00:00
|
|
|
}
|
2023-07-30 13:55:22 +00:00
|
|
|
|
2023-05-22 20:25:37 +00:00
|
|
|
this.requestRender();
|
2022-12-15 13:21:26 +00:00
|
|
|
},
|
2023-04-13 22:19:49 +00:00
|
|
|
onExit: (success:Bool) -> {
|
2023-06-07 18:10:51 +00:00
|
|
|
this.runningPID = -1;
|
2023-04-13 22:19:49 +00:00
|
|
|
if (this.backlog[this.backlog.length - 1] == "") {
|
|
|
|
this.backlog.pop();
|
|
|
|
}
|
|
|
|
|
2023-05-22 20:25:37 +00:00
|
|
|
this.requestRender();
|
2022-12-15 13:21:26 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
this.ctx.setCursorBlink(false);
|
2022-12-15 13:21:26 +00:00
|
|
|
}
|
|
|
|
|
2023-06-26 18:04:26 +00:00
|
|
|
/**
|
|
|
|
Convter a command string into an array of arguments where the first element is the command name
|
|
|
|
**/
|
2022-12-19 16:35:52 +00:00
|
|
|
private function parseArgs(command:String):Array<String> {
|
2022-12-15 13:21:26 +00:00
|
|
|
// TODO: tim and quote handling
|
|
|
|
return command.split(" ");
|
|
|
|
}
|
|
|
|
|
|
|
|
private function clear() {
|
|
|
|
this.backlog = [];
|
2023-06-26 18:04:26 +00:00
|
|
|
this.requestRender();
|
2022-12-15 13:21:26 +00:00
|
|
|
}
|
|
|
|
|
2023-04-13 22:19:49 +00:00
|
|
|
private function getProgByName(name:String):Process {
|
2023-07-27 18:45:37 +00:00
|
|
|
var bin = BinStore.getBinByAlias(name);
|
2023-05-23 12:36:51 +00:00
|
|
|
if (bin == null) {
|
|
|
|
return null;
|
2022-12-15 13:21:26 +00:00
|
|
|
}
|
2023-05-23 12:36:51 +00:00
|
|
|
|
2023-07-30 13:55:22 +00:00
|
|
|
return Type.createInstance(bin.c, []);
|
2022-12-15 13:21:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private function moveCursorToInput() {
|
2023-06-26 18:04:26 +00:00
|
|
|
var size = this.ctx.getSize();
|
|
|
|
this.ctx.setCursorPos(this.input.length + 2, size.y - 1);
|
2022-12-15 13:21:26 +00:00
|
|
|
}
|
|
|
|
}
|