// Web-Uno Shell // the best name I could come up with lmao import { Clear } from '../program/Clear' import { Eval } from '../program/Eval' import { Loadprg } from '../program/Loadprg' import { Lsprg } from '../program/Lsprg' import { Program } from '../program/Program' import { Terminal } from '../terminal/Terminal' import { EventBroadcaster } from '../utils/EventBroadcaster' import { SimpleStream } from '../utils/SimpleStream' import { Shell } from './Shell' export class Wush extends Shell { // buffer private buffer: string[] = [] private bufferPos: number = 0 // exec stuff /** * -1 if the exec is currently running and anything else when it's not */ private execExitCode: number = 0 // streams readonly stdin: SimpleStream readonly stdout: SimpleStream private programs: { [name: string]: Program } = {} constructor(broadcaster: EventBroadcaster, terminal: Terminal) { super(broadcaster, terminal) // create streams this.stdin = new SimpleStream() this.stdout = new SimpleStream() } Init() { this.broadcaster.on('keydown', (key: string, isCharacter: boolean) => this.HandleKeyInput(key, isCharacter), ) // load core programs this.programs['clear'] = new Clear() this.programs['eval'] = new Eval() this.programs['loadprg'] = new Loadprg(this) this.programs['lsprg'] = new Lsprg(this) this.stdout.on(data => this.WriteEscapedString(data)) this.Prompt() } GetPrograms(): { [name: string]: Program } { return this.programs } LoadProgram(program: Program, name: string) { this.programs[name] = program } UnloadProgram(name: string) { delete this.programs[name] } Prompt() { this.terminal.Write(`hi [${this.execExitCode}] -> `) } WriteStdin(data: string) { this.stdin.emit(data) } WriteEscapedString(data: string) { let buf = data.split('') buf.forEach((char, i) => { if (this.ProcessControlCode(char)) { buf.splice(i, 1) } else { this.terminal.Write(char) } }) } PushToBuffer(text: string) { text.split('').forEach(char => { this.buffer.splice(this.bufferPos, 0, char) this.bufferPos++ }) console.log(this.buffer) } RemoveLastCharsFromBuffer(amount: number) { this.buffer.splice(this.buffer.length - amount, amount) this.bufferPos -= amount console.log(this.buffer) } MoveBufferPos(index: number, absolute: boolean = false) { this.bufferPos = Math.max(absolute ? index : this.bufferPos + index, 0) } FlushBuffer() { this.buffer = [] this.bufferPos = 0 } ExecuteBuffer() { const args = this.buffer.join('').split(' ') this.FlushBuffer() console.log(`Executing ${args[0]} with args '${args}'`) this.execExitCode = -1 if (this.programs[args[0]] instanceof Program) { this.programs[args[0]].Exec(this.stdin, this.stdout, args) .then(code => { this.execExitCode = code != -1 ? code : -2 }) .catch(() => { this.WriteEscapedString("lol") }) .finally(() => { // check if the exec actually exited with an exit code // and if not, set it to -2 to indicate that it didn't exit with a valid code if (this.execExitCode === -1) this.execExitCode = -2 // this.terminal.Write(`The program exited with exit code ${this.execExitCode}.`) this.Prompt() }) } else { this.execExitCode = 0 // don't print anything if there are no arguments if (args[0].length !== 0) { this.terminal.Write(`wush: unknown command: ${args[0]}.`) this.terminal.MoveCursor(0, 1, { x: true, y: false }) } this.Prompt() } } ProcessControlCode(code: string): boolean { switch (code) { case '\f': this.terminal.NewPage() return true case '\n': this.terminal.MoveCursor(0, 1, { x: true, y: false }) return true default: return false } } HandleKeyInput(key: string, isCharacter: boolean) { if (this.execExitCode === -1) console.log('program running') if (!isCharacter) { switch (key) { case 'ArrowLeft': if (this.bufferPos > 0) { this.terminal.MoveCursor(-1, 0) this.MoveBufferPos(-1) } break case 'ArrowRight': if (this.bufferPos < this.buffer.length) { this.terminal.MoveCursor(1, 0) this.MoveBufferPos(1) } break case 'Backspace': // don't erase anything if there's nothing left in the buffer if (this.bufferPos === 0) break this.terminal.RemoveCell() this.RemoveLastCharsFromBuffer(1) this.terminal.MoveCursor(-1, 0) break case 'Enter': // send the buffer to stdin if an exec is running if (this.execExitCode === -1) { this.WriteStdin(`${this.buffer.join('')}\n`) this.FlushBuffer() } else { // "execute" the buffer this.terminal.MoveCursor(0, 1, { x: true, y: false }) this.ExecuteBuffer() } break case 'F5': location.reload() break } } else { // push the character into the buffer this.terminal.InsertCell(key) this.PushToBuffer(key) this.terminal.MoveCursor(1, 0) } } }