diff options
| -rw-r--r-- | go.mod | 5 | ||||
| -rw-r--r-- | go.sum | 4 | ||||
| -rw-r--r-- | internal/shell/shell.go | 36 |
3 files changed, 23 insertions, 22 deletions
@@ -1,6 +1,6 @@ module codeberg.org/snonux/geheim -go 1.24 +go 1.24.0 require ( github.com/ergochat/readline v0.1.3 @@ -8,6 +8,7 @@ require ( ) require ( - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.9.0 // indirect ) @@ -4,5 +4,9 @@ github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= diff --git a/internal/shell/shell.go b/internal/shell/shell.go index 5395c97..1d231b9 100644 --- a/internal/shell/shell.go +++ b/internal/shell/shell.go @@ -6,10 +6,13 @@ package shell import ( "context" + "fmt" "io" + "os" "strings" "github.com/ergochat/readline" + "golang.org/x/term" ) // Shell manages an interactive readline loop with vi mode and tab completion. @@ -83,9 +86,9 @@ func New(completionFn func(prefix string) []string) (*Shell, error) { // ReadLine reads one line from the terminal, applying history deduplication. // -// Behaviour mirrors the Ruby shell_loop: +// Behaviour: // - Ctrl+D (EOF) → returns ("", io.EOF) — caller should exit -// - Ctrl+C (interrupt) → returns ("", nil) — empty line, continue +// - Ctrl+C (interrupt) → returns ("", io.EOF) — caller should exit (same as Ruby's SIGINT) // - non-empty line → saved to history only if it differs from the // previous entry, then returned to the caller // @@ -99,8 +102,9 @@ func (s *Shell) ReadLine(ctx context.Context) (string, error) { return "", io.EOF } if err == readline.ErrInterrupt { - // Ctrl+C — return an empty line so the caller loops again. - return "", nil + // Ctrl+C — exit the shell, mirroring the Ruby behaviour where + // SIGINT terminates the process. + return "", io.EOF } return "", err } @@ -137,25 +141,17 @@ func (s *Shell) ReadPassword(prompt string) (string, error) { return string(bytes), nil } -// ReadPassword reads a password from stdin without echoing characters. -// It is a package-level convenience function for use before the Shell is -// created, such as during initial PIN entry at startup. +// ReadPassword prints prompt then reads a password from the terminal without +// echoing characters. It uses golang.org/x/term for reliable cross-platform +// masked input, bypassing the readline library which does not always display +// the prompt correctly before the process is fully interactive. func ReadPassword(prompt string) (string, error) { - // Create a minimal, temporary readline instance solely for masked input. - // VimMode and history are irrelevant here; we just need the password- - // reading capability. - rl, err := readline.NewFromConfig(&readline.Config{ - Prompt: prompt, - HistoryLimit: -1, // disable history for this throwaway instance - }) - if err != nil { - return "", err - } - defer rl.Close() //nolint:errcheck + fmt.Print(prompt) + defer fmt.Println() // move to next line after the user presses Enter - bytes, err := rl.ReadPassword(prompt) + b, err := term.ReadPassword(int(os.Stdin.Fd())) if err != nil { return "", err } - return string(bytes), nil + return string(b), nil } |
