Skip to content

Commit

Permalink
Print filtered gists with highlights conditional on included content
Browse files Browse the repository at this point in the history
When `--filter` is passed, matches will be highlighted in the existing table. If file names match, the "n file(s)" cell will be highlighted.

When `--include-content` is additionally passed, the file name, description, and/or content will be printed like `search code` with matches highlighted.
  • Loading branch information
heaths authored Oct 13, 2024
2 parents dd25eab + be86047 commit 63b218f
Show file tree
Hide file tree
Showing 4 changed files with 394 additions and 20 deletions.
131 changes: 126 additions & 5 deletions pkg/cmd/gist/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman
or even the content of files in the gist using %[1]s--filter%[1]s.
Use %[1]s--include-content%[1]s to include content of files, noting that
this will be slower and increase the rate limit used.
this will be slower and increase the rate limit used. Instead of printing a table,
code will be printed with highlights.
For supported regular expression syntax, see https://pkg.go.dev/regexp/syntax
`, "`"),
Expand Down Expand Up @@ -136,8 +137,38 @@ func listRun(opts *ListOptions) error {
fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err)
}

cs := opts.IO.ColorScheme()
tp := tableprinter.New(opts.IO, tableprinter.WithHeader("ID", "DESCRIPTION", "FILES", "VISIBILITY", "UPDATED"))
if opts.Filter != nil && opts.IncludeContent {
return printContent(opts.IO, gists, opts.Filter)
}

return printTable(opts.IO, gists, opts.Filter)
}

func printTable(io *iostreams.IOStreams, gists []shared.Gist, filter *regexp.Regexp) error {
cs := io.ColorScheme()
tp := tableprinter.New(io, tableprinter.WithHeader("ID", "DESCRIPTION", "FILES", "VISIBILITY", "UPDATED"))

// Highlight filter matches in the description when printing the table.
highlightDescription := func(s string) string {
if filter != nil {
if str, err := highlightMatch(s, filter, nil, cs.Bold, cs.Highlight); err == nil {
return str
}
}
return cs.Bold(s)
}

// Highlight the files column when any file name matches the filter.
highlightFilesFunc := func(gist *shared.Gist) func(string) string {
if filter != nil {
for _, file := range gist.Files {
if filter.MatchString(file.Filename) {
return cs.Highlight
}
}
}
return normal
}

for _, gist := range gists {
fileCount := len(gist.Files)
Expand All @@ -162,13 +193,103 @@ func listRun(opts *ListOptions) error {
tp.AddField(gist.ID)
tp.AddField(
text.RemoveExcessiveWhitespace(description),
tableprinter.WithColor(cs.Bold),
tableprinter.WithColor(highlightDescription),
)
tp.AddField(
text.Pluralize(fileCount, "file"),
tableprinter.WithColor(highlightFilesFunc(&gist)),
)
tp.AddField(text.Pluralize(fileCount, "file"))
tp.AddField(visibility, tableprinter.WithColor(visColor))
tp.AddTimeField(time.Now(), gist.UpdatedAt, cs.Gray)
tp.EndRow()
}

return tp.Render()
}

func printContent(io *iostreams.IOStreams, gists []shared.Gist, filter *regexp.Regexp) error {
const tab string = " "
cs := io.ColorScheme()

out := &strings.Builder{}
var filename, description string
var err error
split := func(r rune) bool {
return r == '\n' || r == '\r'
}

for _, gist := range gists {
for _, file := range gist.Files {
matched := false
out.Reset()

if filename, err = highlightMatch(file.Filename, filter, &matched, cs.Green, cs.Highlight); err != nil {
return err
}
fmt.Fprintln(out, cs.Blue(gist.ID), filename)

if gist.Description != "" {
if description, err = highlightMatch(gist.Description, filter, &matched, cs.Bold, cs.Highlight); err != nil {
return err
}
fmt.Fprintf(out, "%s%s\n", tab, description)
}

if file.Content != "" {
for _, line := range strings.FieldsFunc(file.Content, split) {
if filter.MatchString(line) {
if line, err = highlightMatch(line, filter, &matched, normal, cs.Highlight); err != nil {
return err
}
fmt.Fprintf(out, "%[1]s%[1]s%[2]s\n", tab, line)
}
}
}

if matched {
fmt.Fprintln(io.Out, out.String())
}
}
}

return nil
}

func highlightMatch(s string, filter *regexp.Regexp, matched *bool, color, highlight func(string) string) (string, error) {
matches := filter.FindAllStringIndex(s, -1)
if matches == nil {
return color(s), nil
}

out := strings.Builder{}

// Color up to the first match. If an empty string, no ANSI color sequence is added.
if _, err := out.WriteString(color(s[:matches[0][0]])); err != nil {
return "", err
}

// Highlight each match, then color the remaining text which, if an empty string, no ANSI color sequence is added.
for i, match := range matches {
if _, err := out.WriteString(highlight(s[match[0]:match[1]])); err != nil {
return "", err
}

text := s[match[1]:]
if i+1 < len(matches) {
text = s[match[1]:matches[i+1][0]]
}
if _, err := out.WriteString(color(text)); err != nil {
return "", nil
}
}

if matched != nil {
*matched = *matched || true
}

return out.String(), nil
}

func normal(s string) string {
return s
}
Loading

0 comments on commit 63b218f

Please sign in to comment.