diff --git a/display.go b/display.go new file mode 100644 index 0000000..f7b1d9e --- /dev/null +++ b/display.go @@ -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(`·`) +} diff --git a/main.go b/main.go index 12b20dc..48ce67d 100644 --- a/main.go +++ b/main.go @@ -14,14 +14,12 @@ import ( "unicode" "unicode/utf8" - "github.com/logrusorgru/aurora/v4" "github.com/spf13/cobra" "golang.org/x/sys/unix" ) // TODO: -// - option to squelch aurora output -// - detect if terminal +// - bold of escaped output doesn't work // - binary file detection // - long-line / minified-file detection // - ignore files by extension (or glob?) @@ -65,6 +63,9 @@ var ( ignoreList []string ignoreMap map[string]struct{} ignoreCase bool + noColour bool + display *Display + matchedAny bool ) func init() { @@ -72,9 +73,12 @@ func init() { 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().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 { + display = NewDisplay(noColour) + if len(searchRegexp) == 0 && len(searchFixed) == 0 { if len(args) == 0 { return errors.New("no pattern specified") @@ -196,11 +200,16 @@ func search(path string) error { if !printedHeader { printedHeader = true - fmt.Println(aurora.BgBlue(aurora.Bold(path))) + if !matchedAny { + matchedAny = true + } else { + fmt.Println("") + } + fmt.Println(display.Filename(path)) } b.Reset() - fmt.Fprintf(&b, "%4d: ", aurora.Gray(16, lineNum)) + fmt.Fprintf(&b, "%4d: ", display.LineNumber(lineNum)) if loc[0] < 128 { escape(&b, line[0:loc[0]]) @@ -213,14 +222,15 @@ func search(path string) error { 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]]) } if loc[1]-loc[0] < 128 { b2.Reset() 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) { escape(&b, line[loc[1]:]) @@ -233,7 +243,8 @@ func search(path string) error { 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 { @@ -247,8 +258,9 @@ func search(path string) error { b2.Reset() escape(&b2, line[loc[0]:end]) - b.WriteString(aurora.Bold(b2.String()).String()) - fmt.Fprintf(&b, "%s(%d bytes)", aurora.Magenta("…"), len(line)-end) + b.WriteString(display.Match(b2.String()).String()) + 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 { case r == utf8.RuneError && size == 1: - b.WriteString(aurora.Magenta("\uFFFD").String()) + b.WriteString(display.BadUTF8Char().String()) case r == '\r': - b.WriteString(aurora.Magenta(`\r`).String()) + b.WriteString(display.CarriageReturn().String()) - case r == '\t': - b.WriteString(aurora.Magenta(`\t`).String()) - - case unicode.IsPrint(r): + case r == '\t', + unicode.IsPrint(r): b.WriteRune(r) default: - b.WriteString(aurora.Magenta(".").String()) + b.WriteString(display.UnprintableChar().String()) } } }