From 868420cf077bc542f42db3e51b3bd87f3397d5d7 Mon Sep 17 00:00:00 2001 From: diamondburned Date: Thu, 31 Oct 2024 14:35:07 -0700 Subject: [PATCH] Display exact matches separately --- cmd/nix-search/main.go | 99 +++++++++++++++------- cmd/nix-search/styler.go | 10 +++ flake.lock | 6 +- flake.nix | 2 +- go.mod | 2 +- search/search.go | 7 +- search/searchers/blugesearcher/searcher.go | 20 ++--- 7 files changed, 98 insertions(+), 48 deletions(-) diff --git a/cmd/nix-search/main.go b/cmd/nix-search/main.go index a72da8d..50106b6 100644 --- a/cmd/nix-search/main.go +++ b/cmd/nix-search/main.go @@ -11,6 +11,7 @@ import ( "os/exec" "os/signal" "regexp" + "slices" "strings" "sync" @@ -208,54 +209,94 @@ func mainAction(c *cli.Context) error { searchOpts.Highlight = search.HighlightStyleANSI{} } - pkgsCh, err := searcher.SearchPackages(ctx, query, searchOpts) + pkgsIter, err := searcher.SearchPackages(ctx, query, searchOpts) if err != nil { return errors.Wrap(err, "failed to search packages") } - if c.Bool("json") { - var pkgs []search.SearchedPackage - for pkg := range pkgsCh { - pkgs = append(pkgs, pkg) - } + pkgs := slices.Collect(pkgsIter) + if c.Bool("json") { enc := json.NewEncoder(out) enc.SetIndent("", " ") return enc.Encode(pkgs) } - for pkg := range pkgsCh { - path := pkg.Path - // Fix red coloring when used with other attributes by replacing all - // resets with the default color. - path = strings.ReplaceAll(path, "\x1b[0m", "\x1b[39m") - if pkg.Broken || pkg.UnsupportedPlatform { - path = styler.strikethrough(path) - } + absoluteMatches := make([]search.SearchedPackage, 0, 1) + pkgs = slices.DeleteFunc(pkgs, func(p search.SearchedPackage) bool { + dotq := "." + query + if strings.HasSuffix(p.Path, dotq) { + // Rehighlight the package with the query in the path. + if p.Highlighted != nil { + dotqIx := strings.LastIndex(p.Path, dotq) + + p.Highlighted.Path = "" + + p.Path[:dotqIx] + + "." + styler.style(query, search.DefaultANSIEscapeColor, "\x1b[0m") + + p.Path[dotqIx+len(dotq):] + } - fmt.Fprint(out, "- ", path) - fmt.Fprint(out, " ", styler.dim("("+pkg.Version+")")) - if pkg.Unfree { - fmt.Fprint(out, styler.dim(" (unfree)")) - } - if pkg.Broken { - fmt.Fprint(out, styler.dim(" (broken)")) - } - if pkg.UnsupportedPlatform { - fmt.Fprint(out, styler.dim(" (unsupported)")) + absoluteMatches = append(absoluteMatches, p) + return true } - fmt.Fprint(out, "\n") - fmt.Fprint(out, wrap(pkg.Description, " "), "\n") + return false + }) - if pkg.LongDescription != "" && pkg.Description != pkg.LongDescription { - fmt.Fprint(out, styleLongDescription(styler, pkg.LongDescription), "\n") - } + if len(absoluteMatches) > 0 { + fmt.Fprintln(out, styler.bold("* Exact matches:")) + fmt.Fprintln(out) + printPackages(out, styler, absoluteMatches) + + fmt.Fprintln(out, styler.bold("* Other matches:")) + fmt.Fprintln(out) } + printPackages(out, styler, pkgs) + return ctx.Err() } +func printPackages(out io.Writer, styler textStyler, pkgs []search.SearchedPackage) { + for i := range pkgs { + printPackage(out, styler, &pkgs[i]) + } +} + +func printPackage(out io.Writer, styler textStyler, pkg *search.SearchedPackage) { + // Use the highlighted version of the package if available. + if pkg.Highlighted != nil { + pkg = pkg.Highlighted + } + + path := pkg.Path + // Fix red coloring when used with other attributes by replacing all + // resets with the default color. + path = strings.ReplaceAll(path, "\x1b[0m", "\x1b[39m") + if pkg.Broken || pkg.UnsupportedPlatform { + path = styler.strikethrough(path) + } + + fmt.Fprint(out, "- ", path) + fmt.Fprint(out, " ", styler.dim("("+pkg.Version+")")) + if pkg.Unfree { + fmt.Fprint(out, styler.dim(" (unfree)")) + } + if pkg.Broken { + fmt.Fprint(out, styler.dim(" (broken)")) + } + if pkg.UnsupportedPlatform { + fmt.Fprint(out, styler.dim(" (unsupported)")) + } + fmt.Fprint(out, "\n") + + fmt.Fprint(out, wrap(pkg.Description, " "), "\n") + + if pkg.LongDescription != "" && pkg.Description != pkg.LongDescription { + fmt.Fprint(out, styleLongDescription(styler, pkg.LongDescription), "\n") + } +} + var ( reFencedCodeBlock = regexp.MustCompile(`(?ms)\x60\x60\x60+\s*(.*?)\s*\x60\x60\x60+`) reInlineHyperlink = regexp.MustCompile(`(?m)\[(.*?)\]\n*\((http.*?)\)`) diff --git a/cmd/nix-search/styler.go b/cmd/nix-search/styler.go index cb4cbd9..176f9d4 100644 --- a/cmd/nix-search/styler.go +++ b/cmd/nix-search/styler.go @@ -29,6 +29,16 @@ func (s textStyler) with(o textStyler) textStyler { return s | o } +func (s textStyler) style(text, prefix, suffix string) string { + if s&1 == 0 { + return text + } + if s&dontEndStyle != 0 { + suffix = "" + } + return prefix + text + suffix +} + func (s textStyler) styleTextBlock(text string, prefix, suffix string) string { if s&1 == 0 { return text diff --git a/flake.lock b/flake.lock index e731cb4..831dee5 100644 --- a/flake.lock +++ b/flake.lock @@ -34,11 +34,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1713537308, - "narHash": "sha256-XtTSSIB2DA6tOv+l0FhvfDMiyCmhoRbNB+0SeInZkbk=", + "lastModified": 1730200266, + "narHash": "sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5c24cf2f0a12ad855f444c30b2421d044120c66f", + "rev": "807e9154dcb16384b1b765ebe9cd2bba2ac287fd", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bc218ae..e0f47d9 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,7 @@ { devShells.default = pkgs.mkShell { packages = with pkgs; [ - go_1_21 + go_1_23 gopls gotools sqlc diff --git a/go.mod b/go.mod index 77d2a79..5400b6f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module libdb.so/nix-search -go 1.21 +go 1.23 require ( github.com/alecthomas/assert/v2 v2.2.2 diff --git a/search/search.go b/search/search.go index 6a4c247..5be6829 100644 --- a/search/search.go +++ b/search/search.go @@ -3,6 +3,7 @@ package search import ( "context" "html" + "iter" "strings" ) @@ -12,7 +13,7 @@ import ( type PackagesSearcher interface { // SearchPackages returns a channel of packages that match the given query. // The channel is closed when there are no more results or ctx is canceled. - SearchPackages(ctx context.Context, query string, opts Opts) (<-chan SearchedPackage, error) + SearchPackages(ctx context.Context, query string, opts Opts) (iter.Seq[SearchedPackage], error) } // Opts are options for searching. @@ -35,6 +36,10 @@ type SearchedPackage struct { // Path is the path to the derivation. Path string `json:"path"` Package + + // Highlighted is the color-highlighted package, if any. + // This is only used if Highlight is set in Opts. + Highlighted *SearchedPackage `json:"unhighlighted"` } // HighlightStyle is a style of highlighting. diff --git a/search/searchers/blugesearcher/searcher.go b/search/searchers/blugesearcher/searcher.go index c360956..79dc4a3 100644 --- a/search/searchers/blugesearcher/searcher.go +++ b/search/searchers/blugesearcher/searcher.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "iter" "os" "path/filepath" "strings" @@ -76,7 +77,7 @@ func (s *PackagesSearcher) Close() error { // SearchPackages implements search.PackagesSearcher. The searching is done by // fuzzy matching the query. -func (s *PackagesSearcher) SearchPackages(ctx context.Context, query string, opts search.Opts) (<-chan search.SearchedPackage, error) { +func (s *PackagesSearcher) SearchPackages(ctx context.Context, query string, opts search.Opts) (iter.Seq[search.SearchedPackage], error) { var highlighter blugehighlight.Highlighter if opts.Highlight != nil { switch highlight := opts.Highlight.(type) { @@ -127,10 +128,7 @@ func (s *PackagesSearcher) SearchPackages(ctx context.Context, query string, opt return nil, fmt.Errorf("cannot search: %w", err) } - results := make(chan search.SearchedPackage) - go func() { - defer close(results) - + return func(yield func(p search.SearchedPackage) bool) { var locationBuf []blugesearch.Location for { @@ -218,19 +216,15 @@ func (s *PackagesSearcher) SearchPackages(ctx context.Context, query string, opt } if highlighter != nil { - result = highlightPackage(match, highlighter, result) + hresult := highlightPackage(match, highlighter, result) + result.Highlighted = &hresult } - select { - case results <- result: - // ok - case <-ctx.Done(): + if !yield(result) { return } } - }() - - return results, nil + }, nil } func highlightPackage(match *blugesearch.DocumentMatch, highlighter blugehighlight.Highlighter, pkg search.SearchedPackage) search.SearchedPackage {