Initial version
This commit is contained in:
parent
380cb01dc1
commit
d664931113
|
@ -0,0 +1 @@
|
||||||
|
/missingnewline
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) <year> <copyright holders>
|
Copyright (c) 2023 Laurence Withers.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
16
README.md
16
README.md
|
@ -1,3 +1,17 @@
|
||||||
# missingnewline
|
# missingnewline
|
||||||
|
|
||||||
Prints a "🚫" character and adds a newline if the cursor is not in column 1. You can use it in your shell prompt to tidy up after commands that output some text without a trailing newline.
|
Prints a "🚫" character and adds a newline if the cursor is not in column 1.
|
||||||
|
You can use it in your shell prompt to tidy up after commands that output some
|
||||||
|
text without a trailing newline.
|
||||||
|
|
||||||
|
![Example terminal session showing the problem of an unterminated command mixing with the shell prompt, and then the program correctly detecting the presence or absence of a non-newline-terminated output and using the "🚫" character appropriately.](example.gif)
|
||||||
|
|
||||||
|
You can install this into your `~/.bashrc`, for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
declare -a PROMPT_COMMAND
|
||||||
|
if [ -x "${HOME}/go/bin/missingnewline" ]
|
||||||
|
then
|
||||||
|
PROMPT_COMMAND+=("${HOME}/go/bin/missingnewline")
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1,8 @@
|
||||||
|
module src.lwithers.me.uk/go/missingnewline
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/sys v0.7.0
|
||||||
|
golang.org/x/term v0.7.0
|
||||||
|
)
|
|
@ -0,0 +1,4 @@
|
||||||
|
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||||
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
|
@ -0,0 +1,106 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
st, err := term.MakeRaw(0)
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var column int
|
||||||
|
select {
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
case column = <-awaitColumn():
|
||||||
|
}
|
||||||
|
|
||||||
|
term.Restore(0, st)
|
||||||
|
if column > 1 {
|
||||||
|
unix.Write(1, []byte("🚫\n")) // U+1F6AB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func awaitColumn() <-chan int {
|
||||||
|
ch := make(chan int)
|
||||||
|
go func() {
|
||||||
|
row, col := readDSR()
|
||||||
|
_ = row
|
||||||
|
ch <- col
|
||||||
|
}()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func readDSR() (row, col int) {
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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, ';')
|
||||||
|
if pos == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x, err := strconv.Atoi(s[pos+1:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if x != 1 {
|
||||||
|
fmt.Println("🚫") // U+1F6AB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readit() chan []byte {
|
||||||
|
q := make(chan []byte)
|
||||||
|
go func() {
|
||||||
|
b := make([]byte, 1024)
|
||||||
|
n, _ := os.Stdin.Read(b)
|
||||||
|
q <- b[:n]
|
||||||
|
}()
|
||||||
|
return q
|
||||||
|
}
|
Binary file not shown.
Loading…
Reference in New Issue