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

133
main.go
View File

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