From 4f5602a5df4d3f61bb47686a2f2f73cd0a49ddcd Mon Sep 17 00:00:00 2001 From: rhpidfyre Date: Thu, 20 Feb 2025 22:01:04 -0500 Subject: [PATCH] builder for commands now and history help --- src/components/client/elements/stdout.ts | 43 +++++++++++++++ src/components/client/keys.ts | 34 ++++++++++++ .../client/shell/command/history.ts | 24 +++++++++ src/components/client/shell/command/list.ts | 18 +++---- .../client/shell/command/subcommand.ts | 28 ++++++++++ src/components/client/shell/history.ts | 49 ++++++++++++++--- src/components/client/terminal.ts | 52 +++---------------- src/scss/terminal.scss | 10 ++++ 8 files changed, 196 insertions(+), 62 deletions(-) create mode 100644 src/components/client/elements/stdout.ts create mode 100644 src/components/client/keys.ts create mode 100644 src/components/client/shell/command/history.ts create mode 100644 src/components/client/shell/command/subcommand.ts diff --git a/src/components/client/elements/stdout.ts b/src/components/client/elements/stdout.ts new file mode 100644 index 0000000..3cfa53c --- /dev/null +++ b/src/components/client/elements/stdout.ts @@ -0,0 +1,43 @@ +import { bold } from "../shell/color"; + +import create from "./create"; + +function stdout_grid(left: string[], right: string[]) { + const root = create("div", "stdout-horizontal") + const container_left = create("div", "stdout-vertical") + const container_right = create("div", "stdout-vertical") + + left.forEach(str => container_left.appendChild(stdout_bold(str))) + right.forEach(str => container_right.appendChild(stdout(str))) + + root.appendChild(container_left) + root.appendChild(container_right) + return root +} + +function stdout_horizontal(strs: string[]) { + const p = create("p") + strs.forEach((str, i) => { + const tab = i !== strs.length-1 ? "\t" : "" + p.innerText+=str+tab + }) + return p +} + +function stdout_bold(str: string) { + const p = stdout("") + p.appendChild(bold(str)) + return p +} + +export default function stdout(str: string) { + const p = create("p") + p.innerText = str + return p +} + +export { + stdout_grid, + stdout_horizontal, + stdout_bold +} \ No newline at end of file diff --git a/src/components/client/keys.ts b/src/components/client/keys.ts new file mode 100644 index 0000000..210f9f2 --- /dev/null +++ b/src/components/client/keys.ts @@ -0,0 +1,34 @@ +import run from "./shell/command/run" +import history from "./shell/history" + +type InputClosure = (key_event: KeyboardEvent) => void +interface EnterArgs { + readonly term_win_safe: HTMLElement, + readonly ps1input: HTMLInputElement, + readonly closure: InputClosure +} +interface Keys { + enter: (input: EnterArgs) => void, + up_arrow: (ps1input: HTMLInputElement) => void, + down_arrow: (ps1input: HTMLInputElement) => void, +} + +const keys = {} as Keys + +keys.enter = function(input: EnterArgs) { + const unknown_command_msg = run(input.term_win_safe, input.ps1input.value) + if (unknown_command_msg) { + input.term_win_safe.appendChild(unknown_command_msg) + } + input.ps1input.removeEventListener("keydown", input.closure) +} + +keys.up_arrow = function(ps1input: HTMLInputElement) { + history.index_up(ps1input) +} + +keys.down_arrow = function(ps1input: HTMLInputElement) { + history.index_down(ps1input) +} + +export default keys \ No newline at end of file diff --git a/src/components/client/shell/command/history.ts b/src/components/client/shell/command/history.ts new file mode 100644 index 0000000..d4c0af1 --- /dev/null +++ b/src/components/client/shell/command/history.ts @@ -0,0 +1,24 @@ +import type { Args, Term } from "./list"; +import { bold } from "../color"; + +import stdout, { stdout_horizontal } from "../../elements/stdout"; +import subcmd, { type SubCommandAction } from "./subcommand"; +import history from "../history"; +import create from "../../elements/create"; + +const history_command = subcmd() + +history_command.show = {} as SubCommandAction +history_command.show.inner = function(term: Term) { + history.file.inner.forEach((entry, ind) => term.appendChild(stdout(`${ind} ${entry}`))) +} +history_command.show.description = "Show the history" + +export default function history_cmd(term: Term, args: Args) { + const subc = args[1] + if (subc) { + const subc_f = history_command[subc] + if (subc_f) { subc_f.inner(term) } else { history_command.help.inner(term) } + } + return true +} \ No newline at end of file diff --git a/src/components/client/shell/command/list.ts b/src/components/client/shell/command/list.ts index c504b72..0232ca8 100644 --- a/src/components/client/shell/command/list.ts +++ b/src/components/client/shell/command/list.ts @@ -1,17 +1,12 @@ -import { bold } from "../color" import { get_working_dir_name_full, set_working_dir, SetDirStatus } from "../fs/fn" import create from "../../elements/create" +import history_cmd from "./history" +import stdout from "../../elements/stdout" type Term = HTMLElement type Args = string[] -function strout(term: Term, s: string) { - const p = create("p") - p.innerText = s - term.appendChild(p) -} - function clear(term: Term, args: Args): boolean { Array.from(term.children).forEach(node => { if (node.tagName === "DIV") { @@ -52,7 +47,7 @@ function ls(term: Term, args: Args): boolean { } function pwd(term: Term, args: Args): boolean { - strout(term, get_working_dir_name_full()) + term.appendChild(stdout(get_working_dir_name_full())) return true } @@ -70,7 +65,12 @@ const commands: CommandsList = { ["ls"]: ls, ["pwd"]: pwd, ["cat"]: cat, + ["history"]: history_cmd, } export default commands -export { type Command } \ No newline at end of file +export { + type Command, + type Term, + type Args +} \ No newline at end of file diff --git a/src/components/client/shell/command/subcommand.ts b/src/components/client/shell/command/subcommand.ts new file mode 100644 index 0000000..1815817 --- /dev/null +++ b/src/components/client/shell/command/subcommand.ts @@ -0,0 +1,28 @@ +import { stdout_grid } from "../../elements/stdout"; +import type { Term } from "./list"; + +interface SubCommandAction { + inner: (term: Term) => void, + description: string +} +interface SubCommand { + [index: string]: SubCommandAction +} + +export default function subcmd(help_description?: string): SubCommand { + const subcommand = {} as SubCommand + subcommand.help = {} as SubCommandAction + subcommand.help.inner = function(term: Term) { + const descriptions: string[] = [] + Object.values(subcommand).forEach(v => descriptions.push(v.description)) + term.appendChild(stdout_grid(Object.keys(subcommand), descriptions)) + } + subcommand.help.description = help_description ? help_description : "Show the help page" + + return subcommand +} + +export { + type SubCommand, + type SubCommandAction +} \ No newline at end of file diff --git a/src/components/client/shell/history.ts b/src/components/client/shell/history.ts index d3f1863..1a50eaa 100644 --- a/src/components/client/shell/history.ts +++ b/src/components/client/shell/history.ts @@ -1,15 +1,48 @@ -const history_list: string[] = [] - -interface History { - add: (cmd: string) => void +interface HistoryFile { + inner: string[], + cursor: number, + cursor_reset: () => void } +interface History { + file: HistoryFile + add: (cmd: string) => void, + index_up: (ps1input: HTMLInputElement) => void, + index_down: (ps1input: HTMLInputElement) => void +} + const history = {} as History +history.file = {} as HistoryFile +history.file.inner = [] +history.file.cursor = 0 +history.file.cursor_reset = function() { + this.cursor = 0 +} history.add = function(cmd: string) { - if (history_list[history_list.length-1] !== cmd) { - history_list.push(cmd) + if (this.file.inner[this.file.inner.length-1] !== cmd) { + this.file.inner.push(cmd) } } -export default history -export { history_list } \ No newline at end of file +history.index_up = function(ps1input: HTMLInputElement) { + const item = this.file.inner[this.file.cursor] + if (item) { + this.file.cursor+=1 + ps1input.value = item + } +} + +history.index_down = function(ps1input: HTMLInputElement) { + if (this.file.cursor!==0) { + this.file.cursor-=1 + if (this.file.cursor!==0) { + const item = this.file.inner[this.file.cursor] + if (item) { ps1input.value = item } + } else { + this.file.cursor_reset() + ps1input.value = "" + } + } +} + +export default history \ No newline at end of file diff --git a/src/components/client/terminal.ts b/src/components/client/terminal.ts index 3ced967..d88c0bd 100644 --- a/src/components/client/terminal.ts +++ b/src/components/client/terminal.ts @@ -1,9 +1,6 @@ -import { history_list } from "./shell/history" - +import history from "./shell/history" import prompt from "./elements/prompt" -import run from "./shell/command/run" - -let history_index = 0 +import keys from "./keys" const term_win_unsafe = document.querySelector("main") const enum Key { @@ -14,47 +11,11 @@ const enum Key { Tab = "Tab" } -type InputClosure = (key_event: KeyboardEvent) => void -interface EnterArgs { - readonly term_win_safe: HTMLElement, - readonly ps1input: HTMLInputElement, - readonly closure: InputClosure -} -function key_enter(input: EnterArgs) { - const unknown_command_msg = run(input.term_win_safe, input.ps1input.value) - - if (unknown_command_msg) { - input.term_win_safe.appendChild(unknown_command_msg) - } - input.ps1input.removeEventListener("keydown", input.closure) - spawnps1(input.term_win_safe) -} - -function key_up_arrow(ps1input: HTMLInputElement) { - const history_item = history_list[history_index] - if (history_item) { - ps1input.value = history_item - history_index+=1 - } -} - -function key_down_arrow(ps1input: HTMLInputElement) { - const history_item = history_list[history_index] - if (history_item) { - history_index-=1 - if (history_index === -1) { - history_index = 0 - } else { - ps1input.value = history_item - } - } -} - function spawnps1(term_win_safe: HTMLElement) { const ps1prompt = prompt() term_win_safe.appendChild(ps1prompt.body) bind_processor(term_win_safe, ps1prompt.input) - history_index = 0 + history.file.cursor_reset() ps1prompt.input.focus() } @@ -62,11 +23,12 @@ function bind_processor(term_win_safe: HTMLElement, ps1prompt_input: HTMLInputEl const input_closure = (key_event: KeyboardEvent) => { if (key_event.key === Key.Enter) { key_event.preventDefault() - key_enter({ + keys.enter({ term_win_safe: term_win_safe, ps1input: ps1prompt_input, closure: input_closure }) + spawnps1(term_win_safe) } else if (key_event.key === Key.Tab) { key_event.preventDefault() @@ -75,10 +37,10 @@ function bind_processor(term_win_safe: HTMLElement, ps1prompt_input: HTMLInputEl } else if (key_event.key === Key.ArrowUp) { key_event.preventDefault() - key_up_arrow(ps1prompt_input) + keys.up_arrow(ps1prompt_input) } else if (key_event.key === Key.ArrowDown) { key_event.preventDefault() - key_down_arrow(ps1prompt_input) + keys.down_arrow(ps1prompt_input) } } ps1prompt_input.addEventListener("keydown", input_closure) diff --git a/src/scss/terminal.scss b/src/scss/terminal.scss index 9f923b5..db0ddb9 100644 --- a/src/scss/terminal.scss +++ b/src/scss/terminal.scss @@ -9,7 +9,17 @@ .bold { font-weight: bold; } } +@mixin stdout-layouts { + .stdout-vertical { display: grid; } + .stdout-horizontal { + display: flex; + gap: 30px; + } +} + @mixin formatting { + @include stdout-layouts; + .return { margin-top: 25px; } .shell-prompt { display: flex; }