diff --git a/README.md b/README.md index 33029aa..c2bdb1b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,6 @@ It’s got enough to feel usable, but not overloaded with stuff. ### Things I might add later - [ ] Piping (`|`) and redirection -- [ ] Command history (with arrow keys) +- [X] Command history (with arrow keys) - [X] Tab completion - [x] Maybe themes or aliases if I get fancy diff --git a/go.mod b/go.mod index 6e7d0c7..1f454c6 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,9 @@ module shell go 1.24.0 +require github.com/peterh/liner v1.2.2 + require ( - github.com/chzyer/readline v1.5.1 // indirect github.com/mattn/go-runewidth v0.0.3 // indirect - github.com/peterh/liner v1.2.2 // indirect golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect ) diff --git a/go.sum b/go.sum index a44a0b1..95523ad 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,7 @@ -github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= -github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= -github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= -github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= -golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index 38e8314..3365f19 100644 --- a/main.go +++ b/main.go @@ -9,36 +9,58 @@ import ( "runtime" "strings" "time" + "github.com/peterh/liner" ) func main() { commands := map[string]func(args []string){ - "exit": exitCommand, - "ping": pingCommand, - "pwd": pwdCommand, - "ls": lsCommand, - "cd": cdCommand, - "echo": echoCommand, - "mkdir": mkdirCommand, - "touch": touchCommand, - "rm": rmCommand, - "cat": catCommand, - "clear": clearCommand, - "date": dateCommand, - "curl": curlGetCommand, - "ip": ipCommand, - "vim": vimCommand, - "theme": themeCommand, + "exit": exitCommand, + "ping": pingCommand, + "pwd": pwdCommand, + "ls": lsCommand, + "cd": cdCommand, + "echo": echoCommand, + "mkdir": mkdirCommand, + "touch": touchCommand, + "rm": rmCommand, + "cat": catCommand, + "clear": clearCommand, + "date": dateCommand, + "curl": curlGetCommand, + "ip": ipCommand, + "vim": vimCommand, + "theme": themeCommand, } line := liner.NewLiner() - defer line.Close(); + defer line.Close() lineAutoComplete(line) + + historyFile := ".shell_history" + + if f, err := os.Open(historyFile); err == nil { + line.ReadHistory(f) + f.Close() + } + + defer func() { + if f, err := os.Create(historyFile); err == nil { + line.WriteHistory(f) + f.Close() + } + }() + for { - if input, err := line.Prompt("> "); err == nil { - + if input, err := line.Prompt("> "); err == nil { + input = strings.TrimSpace(input) + if input == "" { + continue + } + + line.AppendHistory(input) + parts := strings.Fields(input) cmd := parts[0] args := parts[1:] @@ -107,7 +129,6 @@ func lineAutoComplete(line *liner.State) { // --- Command Handlers --- - func vimCommand(args []string) { if len(args) == 0 { printError("vim: missing file name") @@ -278,4 +299,77 @@ func lsCommand(args []string) { for _, file := range files { printInfo(file.Name()) } -} \ No newline at end of file +} + +// If In Future you switch to manual history management this'll come handy! + +// // Arrow Handler for shell history +// func historyCommand(history *[]string, historyIdx *int, currInput *[]rune) { +// char, err := readCharToByte() +// if err != nil { +// printError("Failed to read Input!") +// return +// } + +// if char == 27 { +// char2, _ := readCharToByte() +// char3, _ := readCharToByte() + +// if char2 == '[' { +// switch char3 { +// case 'A': // Up Arrow +// // Go backwards +// if *historyIdx > 0 { +// *historyIdx-- +// *currInput = []rune((*history)[*historyIdx]) +// } +// case 'B': +// if *historyIdx < len(*history)-1 { +// *historyIdx++ +// *currInput = []rune((*history)[*historyIdx]) +// } else if *historyIdx == len(*history)-1 { +// *historyIdx++ +// *currInput = []rune{} +// } +// } +// } +// } else if char == 13 { +// cmd := string(*currInput) +// if cmd != "" { +// *history = append(*history, cmd) +// *historyIdx = len(*history) +// } +// *currInput = []rune{} +// } else if char == 127 || char == 8 { // Backspace or Delete +// if len(*currInput) > 0 { +// *currInput = (*currInput)[:len(*currInput)-1] +// } +// } else { +// *currInput = append(*currInput, rune(char)) +// } +// } + +// // Helper functions for Shell History +// // setRawMode sets +// func setRawMode() func() { +// // Save Current state for re-stating! +// oldState, err := exec.Command("stty", "-g").Output() +// if err != nil { +// panic("Failed to store previous terminal state") +// } + +// _, err = exec.Command("stty", []string{"-raw", "echo"}...).Output() +// if err != nil { +// panic("Failed to set terminal to raw mode") +// } + +// return func() { +// exec.Command("stty", string(oldState)).Run() +// } +// } + +// func readCharToByte() (byte, error) { +// buf := make([]byte, 1) +// _, err := os.Stdin.Read(buf) +// return buf[0], err +// }