diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61b11b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/missingnewline diff --git a/LICENSE b/LICENSE index d449d3e..1d4903c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2023 Laurence Withers. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e8641c8..a8948f7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ # 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. \ No newline at end of file +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 +``` diff --git a/example.gif b/example.gif new file mode 100644 index 0000000..48688ba Binary files /dev/null and b/example.gif differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..28a2385 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..da5338a --- /dev/null +++ b/go.sum @@ -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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..0661720 --- /dev/null +++ b/main.go @@ -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 +} diff --git a/my-recording b/my-recording new file mode 100644 index 0000000..5d5f0f9 Binary files /dev/null and b/my-recording differ