diff --git a/src/bin/HelloWorld.hx b/src/bin/HelloWorld.hx new file mode 100644 index 0000000..789626c --- /dev/null +++ b/src/bin/HelloWorld.hx @@ -0,0 +1,17 @@ +package bin; + +import lib.TermHandle; +import kernel.fs.FileHandler.WriteHandle; +import lib.CLIBase; + +class HelloWorld extends CLIBase { + public function invoke(handle: TermHandle):Bool { + var world:String = "world"; + if (handle.args.length > 0) { + world = handle.args[0]; + } + + handle.write('Hello, $world!'); + return true; + } +} diff --git a/src/bin/Terminal.hx b/src/bin/Terminal.hx new file mode 100644 index 0000000..85ef723 --- /dev/null +++ b/src/bin/Terminal.hx @@ -0,0 +1,151 @@ +package bin; + +import lib.TermHandle; +import lib.CLIBase; +import util.Color; +import kernel.ui.WindowContext; +import kernel.ui.WindowManager; + +class Terminal { + private var context:WindowContext; + private var input:String = ""; + private var backlog: Array = []; + + public function new() {} + + public function execute() { + this.context = WindowManager.instance.createNewContext(); + + this.context.onChar.handle(char -> { + this.input += char; + this.redrawInput(); + }); + + this.context.onKey.handle(e -> { + if (e.keyCode == 259) { + this.input = this.input.substr(0, this.input.length - 1); + this.redrawInput(); + } else if (e.keyCode == 257) { + this.backlog.push("> " + this.input); + var command = this.input; + this.input = ""; + this.redrawBacklog(); + this.redrawInput(); + this.invokeCommand(command); + } + }); + + WindowManager.instance.focusContextToOutput(context, "main"); + this.redrawInput(); + } + + private function redrawBacklog() { + var size = this.context.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.context.setCursorPos(0, i); + this.context.clearLine(); + + if (line != null) { + this.context.write(line); + } + } + this.moveCursorToInput(); + } + + private function redrawInput() { + var size = this.context.getSize(); + + this.context.setCursorPos(0, size.y - 1); + this.context.clearLine(); + + this.context.setTextColor(Color.Blue); + this.context.write("> "); + + this.context.setTextColor(Color.White); + this.context.write(this.input); + + this.context.setCursorBlink(true); + } + + private function invokeCommand(command: String):Void{ + var args = this.parseArgs(command); + + if (args.length == 0) { + return; + } + var commandName = args[0]; + + // Handle built-in commands + switch (commandName) { + case "clear": + this.clear(); + return; + } + + var commandArgs:Array = args.slice(1); + var hadInput = false; + var handle = new TermHandle(commandArgs,{ + onWrite: (s:String) -> { + if (!hadInput) { + this.backlog.push(""); + hadInput = true; + } + this.backlog[this.backlog.length - 1] += s; + this.redrawBacklog(); + }, + onNewLine: () -> { + this.backlog.push(""); + this.redrawBacklog(); + } + }); + + var prog:CLIBase = getProgByName(commandName); + + if (prog == null) { + this.backlog.push("Command not found: " + commandName); + this.redrawBacklog(); + return; + } + + this.context.setCursorBlink(false); + + prog.invoke(handle); + + // Cleanup extra newline + if (this.backlog[this.backlog.length - 1] == "") { + this.backlog.pop(); + } + + this.redrawInput(); + } + + private function parseArgs(command: String): Array { + // TODO: tim and quote handling + return command.split(" "); + } + + private function clear() { + this.backlog = []; + this.redrawBacklog(); + } + + private function getProgByName(name: String): CLIBase { + switch (name) { + case "hello": + return new HelloWorld(); + default: + return null; + } + } + + private function moveCursorToInput() { + var size = this.context.getSize(); + this.context.setCursorPos(this.input.length + 2, size.y - 1); + } +} diff --git a/src/lib/CLIBase.hx b/src/lib/CLIBase.hx new file mode 100644 index 0000000..6460e2e --- /dev/null +++ b/src/lib/CLIBase.hx @@ -0,0 +1,6 @@ +package lib; + +abstract class CLIBase { + public function new() {}; + public abstract function invoke(handle: TermHandle): Bool; +} diff --git a/src/lib/TermHandle.hx b/src/lib/TermHandle.hx new file mode 100644 index 0000000..77970ac --- /dev/null +++ b/src/lib/TermHandle.hx @@ -0,0 +1,29 @@ +package lib; + +import haxe.ds.ReadOnlyArray; + +typedef TermHandleEvents = { + onWrite: String->Void, + onNewLine: Void->Void, +} + +class TermHandle { + public final args:ReadOnlyArray; + private final events:TermHandleEvents; + + @:allow(bin.Terminal) + private function new(args: Array, events: TermHandleEvents) { + this.args = args; + this.events = events; + } + + public function write(s:String) { + this.events.onWrite(s); + } + + public function writeLn(s:String = "") { + if (s != "") + this.events.onWrite(s); + this.events.onNewLine(); + } +}