Files
YunShu/pkg/termui/input.go

190 lines
3.5 KiB
Go
Raw Normal View History

package termui
import (
"bufio"
"fmt"
"os"
"strings"
"syscall"
"unsafe"
"hub.gaomia.site/titor/YunShu/pkg/style"
)
const (
stdInputHandle = ^uintptr(9)
stdOutputHandle = ^uintptr(10)
enableProcessed = 0x0001
enableLineInput = 0x0002
enableEchoInput = 0x0004
enableVtProcessing = 0x0004
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetStdHandle = kernel32.NewProc("GetStdHandle")
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
)
func ensureLineMode() {
h, _, _ := procGetStdHandle.Call(stdInputHandle)
if h == 0 || h == uintptr(syscall.InvalidHandle) {
return
}
var mode uint32
ret, _, _ := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode)))
if ret == 0 {
return
}
need := uint32(enableProcessed | enableLineInput | enableEchoInput)
if mode&need != need {
procSetConsoleMode.Call(h, uintptr(need))
}
}
type InputConfig struct {
Label string
Help string
Default string
Required bool
Validator Validator
}
type InputOption func(*InputConfig)
func WithDefault(v string) InputOption {
return func(c *InputConfig) { c.Default = v }
}
func WithHelp(v string) InputOption {
return func(c *InputConfig) { c.Help = v }
}
func WithRequired(v bool) InputOption {
return func(c *InputConfig) { c.Required = v }
}
func WithValidator(v Validator) InputOption {
return func(c *InputConfig) { c.Validator = v }
}
func applyOpts(opts []InputOption) InputConfig {
c := InputConfig{}
for _, o := range opts {
o(&c)
}
return c
}
func printLabel(label string, required bool) {
s := style.New().Bold().Render(label)
if required {
s += "(必填)"
}
fmt.Println(s)
}
func ReadLine() string {
ensureLineMode()
r := bufio.NewReader(os.Stdin)
s, err := r.ReadString('\n')
if err != nil {
return ""
}
return strings.TrimRight(s, "\r\n")
}
func TextInput(label string, opts ...InputOption) string {
cfg := applyOpts(opts)
printLabel(label, cfg.Required)
for {
fmt.Print(style.Cyan.Render("? "))
line := ReadLine()
if line == "" {
line = cfg.Default
}
var err error
if cfg.Required && line == "" {
err = fmt.Errorf("不能为空")
} else if cfg.Validator != nil {
err = cfg.Validator(line)
}
if err != nil {
fmt.Println(style.Red.Render("⚠ " + err.Error()))
continue
}
fmt.Println(style.Green.Render("✔ " + line))
return line
}
}
func PasswordInput(label string, opts ...InputOption) string {
cfg := applyOpts(opts)
printLabel(label, cfg.Required)
for {
fmt.Print(style.Cyan.Render("? "))
line := ReadLine()
fmt.Print("\033[A\r\033[K")
if line == "" {
line = cfg.Default
}
var err error
if cfg.Required && line == "" {
err = fmt.Errorf("不能为空")
} else if cfg.Validator != nil {
err = cfg.Validator(line)
}
if err != nil {
fmt.Println(style.Red.Render("⚠ " + err.Error()))
continue
}
masked := strings.Repeat("*", len(line))
fmt.Println(style.Green.Render("✔ " + masked))
return line
}
}
func Confirm(label string, defaultValue bool) bool {
hint := "Y/n"
if !defaultValue {
hint = "y/N"
}
fmt.Print(style.Cyan.Render("?"), label, "", hint, "")
line := ReadLine()
line = strings.ToLower(line)
fmt.Print("\033[A\r\033[K")
ok := defaultValue
if line == "y" || line == "yes" {
ok = true
} else if line == "n" || line == "no" {
ok = false
} else if line != "" {
ok = false
}
if ok {
fmt.Println(style.Green.Render("✔ " + label))
} else {
fmt.Println(style.Red.Render("✘ " + label))
}
return ok
}