package main import ( "bufio" "fmt" "os" "strings" "tildegit.org/sloum/lines/termios" ) const ( UpArrow rune = iota - 20 DownArrow LeftArrow RightArrow Delete Home End PrintScreen PageUp PageDown Escape rune = 27 NewLine rune = 10 CarriageReturn rune = 13 BackSpace rune = 127 ) type Buffer struct { buf []rune cursor int } func (lb Buffer) String() string { return string(lb.buf) } func (lb *Buffer) AddChar(c rune, echo bool) { if lb.cursor == len(lb.buf) { lb.buf = append(lb.buf, c) lb.cursor++ if echo { fmt.Printf("%c", c) } } else { lb.buf = append(lb.buf[:lb.cursor+1], lb.buf[lb.cursor:]...) lb.buf[lb.cursor] = c lb.cursor++ if echo { s := string(lb.buf[lb.cursor-1:]) fmt.Printf("%s\033[%dD", s, len(s)-1) } } } func (lb *Buffer) controlInput(c rune) { switch c { case BackSpace: lb.cursor-- fmt.Printf("%c", BackSpace) case LeftArrow: if lb.cursor > 0 { lb.cursor-- fmt.Print("\033[1D") } case RightArrow: if lb.cursor < len(lb.buf) { lb.cursor++ fmt.Print("\033[1C") } } } func (lb *Buffer) seedContent(s string) { for _, r := range s { lb.AddChar(r, false) } } func GetText(prompt string, content string, endChar rune, includeEndChar bool) string { b := Buffer{make([]rune, 0, (len(content)+1)*2), 0} b.seedContent(content) fmt.Print(prompt) fmt.Print(b.String()) var ch rune var err error reader := bufio.NewReader(os.Stdin) for { ch, err = readKey(reader) if err != nil { continue } if ch == endChar { if includeEndChar { b.AddChar(ch, true) } break } if isControl(ch) { b.controlInput(ch) } else { b.AddChar(ch, true) } } return b.String() } func readKey(reader *bufio.Reader) (rune, error) { char, _, err := reader.ReadRune() if err != nil { return 0, err } avail := reader.Buffered() if char == Escape && avail > 0 { var b strings.Builder b.WriteRune(27) for ; avail > 0; avail-- { c, _, e := reader.ReadRune() if e != nil { break } b.WriteRune(c) } escSeq := b.String() switch true { case escSeq == "\033[A": char = UpArrow case escSeq == "\033[B": char = DownArrow case escSeq == "\033[C": char = RightArrow case escSeq == "\033[D": char = LeftArrow case escSeq == "\033[5~": char = PageUp case escSeq == "\033[6~": char = PageDown case isHomeKey(escSeq): char = Home case isEndKey(escSeq): char = End case isDeleteKey(escSeq): char = Delete default: fmt.Printf("Odd sequence: %s\n", escSeq[1:]) } } return char, nil } func isControl(c rune) bool { switch c { case UpArrow, DownArrow, RightArrow, LeftArrow, PageUp, PageDown, Home, End, Delete, BackSpace, CarriageReturn, NewLine, Escape: return true default: return false } } func isDeleteKey(seq string) bool { switch seq { case "\033[3~", "\033[P": return true default: return false } } func isHomeKey(seq string) bool { switch seq { case "\033[1~", "\033[7~", "\033[H", "\033OH": return true default: return false } } func isEndKey(seq string) bool { switch seq { case "\033[4~", "\033[8~", "\033[F", "\033OF": return true default: return false } } func PrintKey(k rune) { switch true { case k == UpArrow: fmt.Println("UpArrow") case k == DownArrow: fmt.Println("DownArrow") case k == LeftArrow: fmt.Println("LeftArrow") case k == RightArrow: fmt.Println("RightArrow") case k == Escape: fmt.Println("Escape") case k == NewLine: fmt.Println("NewLine") case k == CarriageReturn: fmt.Println("CarriageReturn") case k == Delete: fmt.Println("Delete") case k == Home: fmt.Println("Home") case k == End: fmt.Println("End") case k == BackSpace: fmt.Println("BackSpace") default: fmt.Printf("%c (%d)\n", k, k) } } func main() { termios.SetCharMode() defer termios.Restore() str := "Hi" for { fmt.Print("\n") str = GetText("> ", str, '\n', false) } }