package bin; 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; class Terminal implements Process { private var context:WindowContext; private var input:String = ""; private var backlog:Array = []; private var handle:ProcessHandle; private var requestRender: () -> Void; public function new() {} public function run(handle: ProcessHandle): Void { this.handle = handle; var statelessContext = handle.createStatelessWindowContext(); this.context = statelessContext.ctx; this.requestRender = statelessContext.requestRender; statelessContext.setRenderFunc(this.render); this.context.onChar.handle(char -> { this.input += char; this.requestRender(); }); this.context.onKey.handle(e -> { if (e.keyCode == 259) { // Backspace this.input = this.input.substr(0, this.input.length - 1); this.requestRender(); } else if (e.keyCode == 257) { // Enter this.backlog.push("> " + this.input); var command = this.input; this.input = ""; this.requestRender(); this.invokeCommand(command); } }); this.requestRender(); } private function render() { redrawBacklog(); 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 ps = getProgByName(commandName); if (ps == null) { this.backlog.push("Unknown command: " + commandName); this.redrawBacklog(); this.redrawInput(); return; } ProcessManager.run(ps,{ args: commandArgs, onWrite: (s:String) -> { if (s == "") { return; } if (!hadInput) { // Add a new line, so that the input is not on the same line as the command this.backlog.push(""); hadInput = true; } for (line in s.split("\n")) { if (line == ""){ this.backlog.push(""); } else { this.backlog[this.backlog.length - 1] += s; } } this.requestRender(); }, onExit: (success:Bool) -> { if (this.backlog[this.backlog.length - 1] == "") { this.backlog.pop(); } this.requestRender(); } }); this.context.setCursorBlink(false); } 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):Process { var bin = BinStore.instance.getBinByAlias(name); if (bin == null) { return null; } return Type.createInstance(bin.c,[]); } private function moveCursorToInput() { var size = this.context.getSize(); this.context.setCursorPos(this.input.length + 2, size.y - 1); } }