cc-haxe/src/bin/Terminal.hx

223 lines
4.7 KiB
Haxe

package bin;
import kernel.EndOfLoop;
import lua.NativeStringTools;
import kernel.binstore.BinStore;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.ProcessManager;
import lib.Color;
import kernel.ui.WindowContext;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Terminal", ["terminal"]))
class Terminal implements Process {
private static inline final MAX_BACKLOG:Int = 100;
private var handle:ProcessHandle;
private var ctx:WindowContext;
private var requestRender:() -> Void;
private var input:String = "";
private var backlog:Array<String> = [];
private var history:Array<String> = [];
private var historyIndex:Int = 0;
private var runningPID:PID = -1;
public function new() {}
public function run(handle:ProcessHandle):Void {
this.handle = handle;
var statelessContext = handle.createStatelessWindowContext();
this.ctx = statelessContext.ctx;
this.requestRender = statelessContext.requestRender;
statelessContext.setRenderFunc(this.render);
// Add input event handlers
handle.addCallbackLink(this.ctx.onChar.handle(char -> {
if (this.runningPID > 0)
return;
this.input += char;
this.requestRender();
}));
// Add key event handlers
handle.addCallbackLink(this.ctx.onKey.handle(e -> {
switch (e.keyCode) {
case 259: // Backspace
if (this.runningPID > 0)
return;
this.input = this.input.substr(0, this.input.length - 1);
this.requestRender();
case 257: // Enter
if (this.runningPID > 0)
return;
this.backlog.push("> " + this.input);
var command = this.input;
this.input = "";
this.requestRender();
this.historyIndex = 0;
this.invokeCommand(command);
case 269: // END
this.stopCurrentPS();
case 265: // UP
if (this.historyIndex < this.history.length) {
this.historyIndex++;
this.input = this.history[this.history.length - this.historyIndex];
this.requestRender();
}
}
}));
this.requestRender();
if (handle.args.length > 0) {
var arg1 = handle.args[0];
this.backlog.push("> " + arg1);
EndOfLoop.endOfLoop(() -> {
this.invokeCommand(arg1);
});
}
}
private function stopCurrentPS() {
if (this.runningPID < 0) {
return;
}
ProcessManager.kill(this.runningPID);
}
private function render() {
var size = this.ctx.getSize();
var linesAvailable = size.y - 1;
var start:Int = this.backlog.length - linesAvailable;
for (i in 0...linesAvailable) {
var line = this.backlog[start + i];
this.ctx.setCursorPos(0, i);
this.ctx.clearLine();
if (line != null) {
this.ctx.write(line);
}
}
this.ctx.setCursorPos(0, size.y - 1);
this.ctx.clearLine();
this.ctx.setTextColor(Color.Blue);
this.ctx.write("> ");
this.ctx.setTextColor(Color.White);
this.ctx.write(this.input);
if (this.runningPID < 0) {
this.ctx.setCursorBlink(true);
} else {
this.ctx.setCursorBlink(false);
}
}
private function invokeCommand(command:String):Void {
var args = this.parseArgs(command);
if (args.length == 0) {
return;
}
this.history.push(command);
if (this.history.length > MAX_BACKLOG) {
this.history.shift();
}
var commandName = args[0];
// Handle built-in commands
switch (commandName) {
case "clear":
this.clear();
return;
}
var commandArgs:Array<String> = args.slice(1);
var ps = BinStore.instantiate(commandName);
if (ps == null) {
this.backlog.push("Unknown command: " + commandName);
this.requestRender();
return;
}
this.runningPID = ProcessManager.run(ps, {
args: commandArgs,
onWrite: (s:String) -> {
if (s == "") {
return;
}
var mfunc = NativeStringTools.gmatch(s, "(.-)(\n)");
while (true) {
var found = mfunc();
if (found == null) {
break;
}
if (found == "\n") {
this.backlog.push("");
} else {
this.backlog.push(found);
}
// Trim the backlog if it's too long
if (this.backlog.length > MAX_BACKLOG) {
this.backlog.shift();
}
}
this.requestRender();
},
onExit: (success:Bool) -> {
this.runningPID = -1;
if (this.backlog[this.backlog.length - 1] == "") {
this.backlog.pop();
}
this.requestRender();
}
});
this.ctx.setCursorBlink(false);
}
/**
Convter a command string into an array of arguments where the first element is the command name
**/
private function parseArgs(command:String):Array<String> {
// TODO: tim and quote handling
return command.split(" ");
}
private function clear() {
this.backlog = [];
this.requestRender();
}
private function moveCursorToInput() {
var size = this.ctx.getSize();
this.ctx.setCursorPos(this.input.length + 2, size.y - 1);
}
}