setup input

This commit is contained in:
2026-01-13 15:02:07 +00:00
parent 1fbee269ce
commit 7c60e2305d
2 changed files with 163 additions and 48 deletions

191
main.go
View File

@@ -25,7 +25,9 @@ import (
"unicode/utf8"
tea "github.com/charmbracelet/bubbletea"
"github.com/mattn/go-runewidth"
"github.com/pelletier/go-toml/v2"
"golang.org/x/term"
)
var (
@@ -314,9 +316,8 @@ func generateServiceName() string {
}
func promptServiceName(defaultName string) (string, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("%sService name%s [%s]: ", headerColor, remoteColorReset, defaultName)
input, err := reader.ReadString('\n')
prompt := fmt.Sprintf("%sService name%s [%s]: ", headerColor, remoteColorReset, defaultName)
input, err := readEditableLine(prompt)
if err != nil {
return "", err
}
@@ -368,13 +369,12 @@ func confirmServerConfig(pbPath string) error {
}
fmt.Printf("Confirm remote server settings (press Enter to keep the current value):\n")
reader := bufio.NewReader(os.Stdin)
ipDefault := cfg.Server.IP
if ipDefault == "" {
ipDefault = "127.0.0.1"
}
ip, err := promptWithDefault(reader, "Server IP", ipDefault)
ip, err := promptWithDefault("Server IP", ipDefault)
if err != nil {
return err
}
@@ -383,7 +383,7 @@ func confirmServerConfig(pbPath string) error {
if portDefault <= 0 {
portDefault = 8090
}
port, err := promptPort(reader, "Server port", portDefault)
port, err := promptPort("Server port", portDefault)
if err != nil {
return err
}
@@ -392,7 +392,7 @@ func confirmServerConfig(pbPath string) error {
if domainDefault == "" {
domainDefault = "example.com"
}
domain, err := promptWithDefault(reader, "Server domain", domainDefault)
domain, err := promptWithDefault("Server domain", domainDefault)
if err != nil {
return err
}
@@ -407,15 +407,15 @@ func confirmServerConfig(pbPath string) error {
return nil
}
func promptWithDefault(reader *bufio.Reader, label, defaultValue string) (string, error) {
fmt.Printf("%s%s%s", headerColor, label, remoteColorReset)
func promptWithDefault(label, defaultValue string) (string, error) {
var suffix string
if defaultValue != "" {
fmt.Printf(" [%s]: ", defaultValue)
suffix = fmt.Sprintf(" [%s]: ", defaultValue)
} else {
fmt.Print(": ")
suffix = ": "
}
input, err := reader.ReadString('\n')
prompt := fmt.Sprintf("%s%s%s%s", headerColor, label, remoteColorReset, suffix)
input, err := readEditableLine(prompt)
if err != nil {
return "", err
}
@@ -427,14 +427,14 @@ func promptWithDefault(reader *bufio.Reader, label, defaultValue string) (string
return input, nil
}
func promptPort(reader *bufio.Reader, label string, defaultValue int) (int, error) {
func promptPort(label string, defaultValue int) (int, error) {
if defaultValue <= 0 {
defaultValue = 8090
}
for {
fmt.Printf("%s%s%s [%d]: ", headerColor, label, remoteColorReset, defaultValue)
input, err := reader.ReadString('\n')
prompt := fmt.Sprintf("%s%s%s [%d]: ", headerColor, label, remoteColorReset, defaultValue)
input, err := readEditableLine(prompt)
if err != nil {
return 0, err
}
@@ -463,31 +463,20 @@ func normalizePromptInput(raw string) string {
break
}
if r == 0x1b {
nextIndex := i + size
if nextIndex >= len(raw) {
break
nextIndex, final := skipEscapeSequence(raw, i)
if nextIndex <= i {
i++
continue
}
next, nextSize := utf8.DecodeRuneInString(raw[nextIndex:])
if next == 0x7f || next == 0x08 {
i = nextIndex
if final == 0x7f || final == 0x08 {
buf = deletePreviousWord(buf)
i = nextIndex + nextSize
continue
}
if next == '[' || next == 'O' {
i = nextIndex + nextSize
for i < len(raw) {
code, codeSize := utf8.DecodeRuneInString(raw[i:])
if codeSize == 0 {
break
}
i += codeSize
if ('A' <= code && code <= 'Z') || ('a' <= code && code <= 'z') {
break
}
}
continue
}
i = nextIndex + nextSize
continue
}
if r == 0x17 {
buf = deletePreviousWord(buf)
i += size
continue
}
if r == 0x7f || r == 0x08 {
@@ -503,6 +492,132 @@ func normalizePromptInput(raw string) string {
return string(buf)
}
func readEditableLine(prompt string) (string, error) {
fd := int(os.Stdin.Fd())
reader := bufio.NewReader(os.Stdin)
if !term.IsTerminal(fd) {
line, err := reader.ReadString('\n')
if err != nil {
return "", err
}
line = strings.TrimRight(line, "\r\n")
return line, nil
}
oldState, err := term.MakeRaw(fd)
if err != nil {
return "", err
}
defer term.Restore(fd, oldState)
var buf []rune
var lastWidth int
fmt.Print(prompt)
for {
r, _, err := reader.ReadRune()
if err != nil {
return "", err
}
switch {
case r == '\r' || r == '\n':
fmt.Println()
return string(buf), nil
case r == 0x03:
fmt.Println()
return "", errors.New("input interrupted")
case r == 0x04:
fmt.Println()
return "", io.EOF
case r == 0x7f || r == 0x08:
if len(buf) > 0 {
buf = buf[:len(buf)-1]
redrawEditableLine(prompt, buf, &lastWidth)
}
case r == 0x17:
buf = deletePreviousWord(buf)
redrawEditableLine(prompt, buf, &lastWidth)
case r == 0x1b:
final, err := readEscapeFinal(reader)
if err != nil {
return "", err
}
if final == 0x7f || final == 0x08 {
buf = deletePreviousWord(buf)
redrawEditableLine(prompt, buf, &lastWidth)
}
default:
if r < 0x20 {
continue
}
buf = append(buf, r)
redrawEditableLine(prompt, buf, &lastWidth)
}
}
}
func readEscapeFinal(reader *bufio.Reader) (rune, error) {
b, err := reader.ReadByte()
if err != nil {
return 0, err
}
if b == '[' || b == 'O' {
for {
c, err := reader.ReadByte()
if err != nil {
return 0, err
}
if isCSIFinalByte(rune(c)) {
return rune(c), nil
}
}
}
return rune(b), nil
}
func redrawEditableLine(prompt string, buf []rune, lastWidth *int) {
fmt.Print("\r")
fmt.Print(prompt)
text := string(buf)
fmt.Print(text)
width := runewidth.StringWidth(text)
if *lastWidth > width {
fmt.Print(strings.Repeat(" ", *lastWidth-width))
fmt.Print("\r")
fmt.Print(prompt)
fmt.Print(text)
}
*lastWidth = width
}
func skipEscapeSequence(raw string, escIndex int) (int, rune) {
i := escIndex + 1
if i >= len(raw) {
return i, 0
}
next, size := utf8.DecodeRuneInString(raw[i:])
i += size
if next == '[' || next == 'O' {
for i < len(raw) {
code, codeSize := utf8.DecodeRuneInString(raw[i:])
if codeSize == 0 {
break
}
i += codeSize
if isCSIFinalByte(code) {
return i, code
}
}
return i, 0
}
return i, next
}
func isCSIFinalByte(r rune) bool {
return 0x40 <= r && r <= 0x7e
}
func deletePreviousWord(buf []rune) []rune {
i := len(buf)
for i > 0 && unicode.IsSpace(buf[i-1]) {