236 lines
4.0 KiB
Go
236 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"tildegit.org/sloum/qline/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() {
|
|
// This main func is just for testing and will
|
|
// be removed with the intention that this just
|
|
// gets used as a lib
|
|
termios.SetCharMode()
|
|
defer termios.Restore()
|
|
str := ""
|
|
|
|
for {
|
|
fmt.Print("\n")
|
|
str = GetText("> ", str, '\n', false)
|
|
}
|
|
}
|