Improvements: cope with extra input chars and replay

This still suffers a bit if the user does manage to type anything during the invocation, because the
simulated input characters get echoed to the terminal and mess up the nicely-aligned newline we just
made.
This commit is contained in:
Laurence Withers 2023-04-28 12:22:51 +01:00
parent 10da808f86
commit bd0d1aaebe
1 changed files with 78 additions and 61 deletions

139
main.go
View File

@ -2,105 +2,122 @@ package main
import ( import (
"bytes" "bytes"
"fmt"
"os" "os"
"strconv"
"strings"
"time" "time"
"unsafe"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"golang.org/x/term" "golang.org/x/term"
) )
type dsrResult struct {
row int
column int
extraBytes []byte
}
func main() { func main() {
st, err := term.MakeRaw(0) st, err := term.MakeRaw(0)
if err != nil { if err != nil {
os.Exit(1) os.Exit(1)
} }
var column int var res dsrResult
select { select {
case <-time.After(100 * time.Millisecond): case <-time.After(100 * time.Millisecond):
case column = <-awaitColumn(): case res = <-awaitDSR():
} }
term.Restore(0, st) term.Restore(0, st)
if column > 1 { if res.column > 1 {
unix.Write(1, []byte("🚫\n")) // U+1F6AB unix.Write(1, []byte("🚫\n")) // U+1F6AB
} }
for _, b := range res.extraBytes {
unix.Syscall(unix.SYS_IOCTL, 0, unix.TIOCSTI, uintptr(unsafe.Pointer(&b)))
}
} }
func awaitColumn() <-chan int { func awaitDSR() <-chan dsrResult {
ch := make(chan int) ch := make(chan dsrResult)
go func() { go func() {
row, col := readDSR() row, col, extra := readDSR()
_ = row ch <- dsrResult{
ch <- col row: row,
column: col,
extraBytes: extra,
}
}() }()
return ch return ch
} }
func readDSR() (row, col int) { func readDSR() (row, col int, extra []byte) {
unix.Write(1, []byte{ unix.Write(1, []byte{
'\033', '[', // ANSI escape code: CSI '\033', '[', // ANSI escape code: CSI
'6', 'n', // DSR / device status report '6', 'n', // DSR / device status report
}) })
buf := make([]byte, 64) inputBuf := bytes.NewBuffer(make([]byte, 0, 128))
n, err := unix.Read(0, buf) termIn := make([]byte, 64)
if err != nil { for {
return 0, 0 // read some bytes from stdin and append to our input buffer
} termIn = termIn[:64]
buf = buf[:n] n, err := unix.Read(0, termIn)
inputBuf.Write(termIn[:n])
if err != nil {
return 0, 0, inputBuf.Bytes()
}
if len(buf) < 6 || buf[0] != '\033' || buf[1] != '[' || buf[len(buf)-1] != 'R' { // does the input buffer contain a valid DSR result sequence?
return 0, 0 b := inputBuf.Bytes()
row, col, pos, count, ok := parseDSR(b)
if ok {
// replay the non-DSR sequence bytes
return row, col, append(b[:pos], b[pos+count:]...)
}
} }
s := string(buf[2 : len(buf)-1])
pos := strings.IndexByte(s, ';')
if pos == -1 {
return 0, 0
}
row, err = strconv.Atoi(s[:pos])
if err != nil {
return 0, 0
}
col, err = strconv.Atoi(s[pos+1:])
if err != nil {
return 0, 0
}
return row, col
} }
func processit(b []byte) { func parseDSR(input []byte) (row, col, pos, count int, ok bool) {
if !bytes.HasPrefix(b, []byte{'\033', '['}) { // locate the start of the result
return pos = bytes.Index(input, []byte{'\033', '['})
}
if !bytes.HasSuffix(b, []byte{'R'}) {
return
}
s := string(b[2 : len(b)-1])
pos := strings.IndexByte(s, ';')
if pos == -1 { if pos == -1 {
return return
} }
x, err := strconv.Atoi(s[pos+1:])
if err != nil {
return
}
if x != 1 {
fmt.Println("🚫") // U+1F6AB
}
}
func readit() chan []byte { // parse the row and column numbers, separated by ';'
q := make(chan []byte) p := pos + 2
go func() { rowLoop:
b := make([]byte, 1024) for {
n, _ := os.Stdin.Read(b) if p == len(input) {
q <- b[:n] return
}() }
return q switch input[p] {
case ';':
break rowLoop
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
row *= 10
row += int(input[p] - '0')
default:
return
}
p++
}
p++
for {
if p == len(input) {
return
}
switch input[p] {
case 'R':
count = p - pos + 1
ok = true
return
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
col *= 10
col += int(input[p] - '0')
default:
return
}
p++
}
} }