Improvements to display code

Now switchable, detects terminal. Don't be weird with tabs. Known issues are
not being themeable and bold match output stopping if colours are used inside.
This commit is contained in:
Laurence Withers 2023-05-13 10:58:06 +01:00
parent b438aef169
commit 4fd3ae4c7e
2 changed files with 81 additions and 17 deletions

54
display.go Normal file
View File

@ -0,0 +1,54 @@
package main
import (
"fmt"
"github.com/logrusorgru/aurora/v4"
"golang.org/x/sys/unix"
)
type Display struct {
a *aurora.Aurora
}
func NewDisplay(noColour bool) *Display {
if _, err := unix.IoctlGetTermios(1, unix.TCGETS); err != nil {
noColour = true
}
a := aurora.New(aurora.WithColors(!noColour))
return &Display{
a: a,
}
}
func (d *Display) Filename(name string) aurora.Value {
return d.a.BgBlue(d.a.White(name))
}
func (d *Display) LineNumber(lineNum int) aurora.Value {
return d.a.Green(lineNum)
}
func (d *Display) Match(text string) aurora.Value {
return d.a.Bold(text)
}
func (d *Display) TruncatedMarker() aurora.Value {
return d.a.Magenta("…")
}
func (d *Display) TruncatedBytes(byteCount int) aurora.Value {
return d.a.Faint(fmt.Sprintf("(%d bytes)", byteCount))
}
func (d *Display) BadUTF8Char() aurora.Value {
return d.a.Magenta("\uFFFD")
}
func (d *Display) CarriageReturn() aurora.Value {
return d.a.Magenta(`\r`)
}
func (d *Display) UnprintableChar() aurora.Value {
return d.a.Magenta(`·`)
}

44
main.go
View File

@ -14,14 +14,12 @@ import (
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"github.com/logrusorgru/aurora/v4"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
// TODO: // TODO:
// - option to squelch aurora output // - bold of escaped output doesn't work
// - detect if terminal
// - binary file detection // - binary file detection
// - long-line / minified-file detection // - long-line / minified-file detection
// - ignore files by extension (or glob?) // - ignore files by extension (or glob?)
@ -65,6 +63,9 @@ var (
ignoreList []string ignoreList []string
ignoreMap map[string]struct{} ignoreMap map[string]struct{}
ignoreCase bool ignoreCase bool
noColour bool
display *Display
matchedAny bool
) )
func init() { func init() {
@ -72,9 +73,12 @@ func init() {
rootCmd.Flags().StringSliceVarP(&searchFixed, "fixed", "Q", nil, "pattern to match (fixed string)") rootCmd.Flags().StringSliceVarP(&searchFixed, "fixed", "Q", nil, "pattern to match (fixed string)")
rootCmd.Flags().StringSliceVarP(&ignoreList, "exclude", "x", []string{".git"}, "files/directories to exclude") rootCmd.Flags().StringSliceVarP(&ignoreList, "exclude", "x", []string{".git"}, "files/directories to exclude")
rootCmd.Flags().BoolVarP(&ignoreCase, "ignore-case", "i", false, "make all searches case insensitive") rootCmd.Flags().BoolVarP(&ignoreCase, "ignore-case", "i", false, "make all searches case insensitive")
rootCmd.Flags().BoolVarP(&noColour, "no-colour", "C", false, "disable colour output")
} }
func run(c *cobra.Command, args []string) error { func run(c *cobra.Command, args []string) error {
display = NewDisplay(noColour)
if len(searchRegexp) == 0 && len(searchFixed) == 0 { if len(searchRegexp) == 0 && len(searchFixed) == 0 {
if len(args) == 0 { if len(args) == 0 {
return errors.New("no pattern specified") return errors.New("no pattern specified")
@ -196,11 +200,16 @@ func search(path string) error {
if !printedHeader { if !printedHeader {
printedHeader = true printedHeader = true
fmt.Println(aurora.BgBlue(aurora.Bold(path))) if !matchedAny {
matchedAny = true
} else {
fmt.Println("")
}
fmt.Println(display.Filename(path))
} }
b.Reset() b.Reset()
fmt.Fprintf(&b, "%4d: ", aurora.Gray(16, lineNum)) fmt.Fprintf(&b, "%4d: ", display.LineNumber(lineNum))
if loc[0] < 128 { if loc[0] < 128 {
escape(&b, line[0:loc[0]]) escape(&b, line[0:loc[0]])
@ -213,14 +222,15 @@ func search(path string) error {
start++ start++
} }
fmt.Fprintf(&b, "(%d bytes)%s", start, aurora.Magenta("…")) b.WriteString(display.TruncatedBytes(start).String())
b.WriteString(display.TruncatedMarker().String())
escape(&b, line[start:loc[0]]) escape(&b, line[start:loc[0]])
} }
if loc[1]-loc[0] < 128 { if loc[1]-loc[0] < 128 {
b2.Reset() b2.Reset()
escape(&b2, line[loc[0]:loc[1]]) escape(&b2, line[loc[0]:loc[1]])
b.WriteString(aurora.Bold(b2.String()).String()) b.WriteString(display.Match(b2.String()).String())
if loc[1]+128 > len(line) { if loc[1]+128 > len(line) {
escape(&b, line[loc[1]:]) escape(&b, line[loc[1]:])
@ -233,7 +243,8 @@ func search(path string) error {
end-- end--
} }
escape(&b, line[loc[1]:end]) escape(&b, line[loc[1]:end])
fmt.Fprintf(&b, "(%d bytes)%s", len(line)-end, aurora.Magenta("…")) b.WriteString(display.TruncatedBytes(len(line) - end).String())
b.WriteString(display.TruncatedMarker().String())
} }
} else { } else {
@ -247,8 +258,9 @@ func search(path string) error {
b2.Reset() b2.Reset()
escape(&b2, line[loc[0]:end]) escape(&b2, line[loc[0]:end])
b.WriteString(aurora.Bold(b2.String()).String()) b.WriteString(display.Match(b2.String()).String())
fmt.Fprintf(&b, "%s(%d bytes)", aurora.Magenta("…"), len(line)-end) b.WriteString(display.TruncatedMarker().String())
b.WriteString(display.TruncatedBytes(len(line) - end).String())
} }
@ -282,19 +294,17 @@ func escape(b *strings.Builder, s []byte) {
switch { switch {
case r == utf8.RuneError && size == 1: case r == utf8.RuneError && size == 1:
b.WriteString(aurora.Magenta("\uFFFD").String()) b.WriteString(display.BadUTF8Char().String())
case r == '\r': case r == '\r':
b.WriteString(aurora.Magenta(`\r`).String()) b.WriteString(display.CarriageReturn().String())
case r == '\t': case r == '\t',
b.WriteString(aurora.Magenta(`\t`).String()) unicode.IsPrint(r):
case unicode.IsPrint(r):
b.WriteRune(r) b.WriteRune(r)
default: default:
b.WriteString(aurora.Magenta(".").String()) b.WriteString(display.UnprintableChar().String())
} }
} }
} }