From d137ceccefe08250106dcede1f30c270b0f9cf19 Mon Sep 17 00:00:00 2001
From: Fishhh <hubert.gluchowski19@gmail.com>
Date: Sun, 5 Jun 2022 18:44:37 +0200
Subject: [PATCH 1/5] Fix incorrect comment in `hiliteMatches`

---
 src/libutil/hilite.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/libutil/hilite.cc b/src/libutil/hilite.cc
index a5991ca39..e5088230d 100644
--- a/src/libutil/hilite.cc
+++ b/src/libutil/hilite.cc
@@ -8,9 +8,9 @@ std::string hiliteMatches(
     std::string_view prefix,
     std::string_view postfix)
 {
-    // Avoid copy on zero matches
+    // Avoid extra work on zero matches
     if (matches.size() == 0)
-        return (std::string) s;
+        return std::string(s);
 
     std::sort(matches.begin(), matches.end(), [](const auto & a, const auto & b) {
         return a.position() < b.position();

From b42358b9bec12dfdc419136f32ded2a4f7d7dea7 Mon Sep 17 00:00:00 2001
From: Fishhh <hubert.gluchowski19@gmail.com>
Date: Sun, 5 Jun 2022 18:45:58 +0200
Subject: [PATCH 2/5] Add `--exclude` flag to `nix search`

If a package's attribute path, description or name contains matches for any of the
regexes specified via `-e` or `--exclude` that package is excluded from
the final output.
---
 src/nix/search.cc | 23 ++++++++++++++++++++++-
 src/nix/search.md | 13 ++++++++++++-
 2 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/src/nix/search.cc b/src/nix/search.cc
index 87dc1c0de..62ad98999 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -18,16 +18,24 @@ using namespace nix;
 
 std::string wrap(std::string prefix, std::string s)
 {
-    return prefix + s + ANSI_NORMAL;
+    return concatStrings(prefix, s, ANSI_NORMAL);
 }
 
 struct CmdSearch : InstallableCommand, MixJSON
 {
     std::vector<std::string> res;
+    std::vector<std::string> excludeRes;
 
     CmdSearch()
     {
         expectArgs("regex", &res);
+        addFlag(Flag {
+            .longName = "exclude",
+            .shortName = 'e',
+            .description = "Hide packages whose attribute path, name or description contain *regex*.",
+            .labels = {"regex"},
+            .handler = Handler(&excludeRes),
+        });
     }
 
     std::string description() override
@@ -62,11 +70,16 @@ struct CmdSearch : InstallableCommand, MixJSON
             res.push_back("^");
 
         std::vector<std::regex> regexes;
+        std::vector<std::regex> excludeRegexes;
         regexes.reserve(res.size());
+        excludeRegexes.reserve(excludeRes.size());
 
         for (auto & re : res)
             regexes.push_back(std::regex(re, std::regex::extended | std::regex::icase));
 
+        for (auto & re : excludeRes)
+            excludeRegexes.emplace_back(re, std::regex::extended | std::regex::icase);
+
         auto state = getEvalState();
 
         auto jsonOut = json ? std::make_unique<JSONObject>(std::cout) : nullptr;
@@ -106,6 +119,14 @@ struct CmdSearch : InstallableCommand, MixJSON
                     std::vector<std::smatch> nameMatches;
                     bool found = false;
 
+                    for (auto & regex : excludeRegexes) {
+                        if (
+                            std::regex_search(attrPath2, regex)
+                            || std::regex_search(name.name, regex)
+                            || std::regex_search(description, regex))
+                            return;
+                    }
+
                     for (auto & regex : regexes) {
                         found = false;
                         auto addAll = [&found](std::sregex_iterator it, std::vector<std::smatch> & vec) {
diff --git a/src/nix/search.md b/src/nix/search.md
index d182788a6..5a5b5ae05 100644
--- a/src/nix/search.md
+++ b/src/nix/search.md
@@ -43,12 +43,23 @@ R""(
   # nix search nixpkgs 'firefox|chromium'
   ```
 
-* Search for packages containing `git'`and either `frontend` or `gui`:
+* Search for packages containing `git` and either `frontend` or `gui`:
 
   ```console
   # nix search nixpkgs git 'frontend|gui'
   ```
 
+* Search for packages containing `neovim` but hide ones containing either `gui` or `python`:
+
+  ```console
+  # nix search nixpkgs neovim -e 'python|gui'
+  ```
+  or
+
+  ```console
+  # nix search nixpkgs neovim -e 'python' -e 'gui'
+  ```
+
 # Description
 
 `nix search` searches *installable* (which must be evaluatable, e.g. a

From e009367c8d4523bfe3a1bc20583b27d06948a390 Mon Sep 17 00:00:00 2001
From: Fishhh <hubert.gluchowski19@gmail.com>
Date: Sun, 5 Jun 2022 18:48:48 +0200
Subject: [PATCH 3/5] Remove redundant `std::move`s in calls to `hiliteMatches`

---
 src/nix/search.cc | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/nix/search.cc b/src/nix/search.cc
index 62ad98999..f1f5f9641 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -154,15 +154,15 @@ struct CmdSearch : InstallableCommand, MixJSON
                             jsonElem.attr("version", name.version);
                             jsonElem.attr("description", description);
                         } else {
-                            auto name2 = hiliteMatches(name.name, std::move(nameMatches), ANSI_GREEN, "\e[0;2m");
+                            auto name2 = hiliteMatches(name.name, nameMatches, ANSI_GREEN, "\e[0;2m");
                             if (results > 1) logger->cout("");
                             logger->cout(
                                 "* %s%s",
-                                wrap("\e[0;1m", hiliteMatches(attrPath2, std::move(attrPathMatches), ANSI_GREEN, "\e[0;1m")),
+                                wrap("\e[0;1m", hiliteMatches(attrPath2, attrPathMatches, ANSI_GREEN, "\e[0;1m")),
                                 name.version != "" ? " (" + name.version + ")" : "");
                             if (description != "")
                                 logger->cout(
-                                    "  %s", hiliteMatches(description, std::move(descriptionMatches), ANSI_GREEN, ANSI_NORMAL));
+                                    "  %s", hiliteMatches(description, descriptionMatches, ANSI_GREEN, ANSI_NORMAL));
                         }
                     }
                 }

From 0338cf55395feb3aedabc535858263d95d235f72 Mon Sep 17 00:00:00 2001
From: Fishhh <hubert.gluchowski19@gmail.com>
Date: Sun, 5 Jun 2022 19:44:42 +0200
Subject: [PATCH 4/5] Add tests for `--exclude` flag in `nix search`

---
 tests/search.sh | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/tests/search.sh b/tests/search.sh
index 52e12f381..f320e0ec1 100644
--- a/tests/search.sh
+++ b/tests/search.sh
@@ -36,3 +36,9 @@ e=$'\x1b' # grep doesn't support \e, \033 or even \x1b
 (( $(nix search -f search.nix '' 'o' | grep -Eo "$e\[32;1mo{1,2}$e\[(0|0;1)m" | wc -l) == 3 ))
 # Searching for 'b' should yield the 'b' in bar and the two 'b's in 'broken bar'
 (( $(nix search -f search.nix '' 'b' | grep -Eo "$e\[32;1mb$e\[(0|0;1)m" | wc -l) == 3 ))
+
+## Tests for --exclude
+(( $(nix search -f search.nix -e hello | grep -c hello) == 0 ))
+
+(( $(nix search -f search.nix foo --exclude 'foo|bar' | grep -Ec 'foo|bar') == 0 ))
+(( $(nix search -f search.nix foo -e foo --exclude bar | grep -Ec 'foo|bar') == 0 ))

From 9ae22b1fdeaf6cc7541a66d981ecf7b6038739cc Mon Sep 17 00:00:00 2001
From: Fishhh <hubert.gluchowski19@gmail.com>
Date: Sun, 5 Jun 2022 19:45:21 +0200
Subject: [PATCH 5/5] Use `grep -c` instead of `grep|wc -l` in some `nix
 search` tests

---
 tests/search.sh | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/tests/search.sh b/tests/search.sh
index f320e0ec1..41b706ac6 100644
--- a/tests/search.sh
+++ b/tests/search.sh
@@ -28,13 +28,14 @@ nix search -f search.nix '' |grep -q hello
 
 e=$'\x1b' # grep doesn't support \e, \033 or even \x1b
 # Multiple overlapping regexes
-(( $(nix search -f search.nix '' 'oo' 'foo' 'oo' | grep "$e\[32;1mfoo$e\\[0;1m" | wc -l) == 1 ))
-(( $(nix search -f search.nix '' 'broken b' 'en bar' | grep "$e\[32;1mbroken bar$e\\[0m" | wc -l) == 1 ))
+(( $(nix search -f search.nix '' 'oo' 'foo' 'oo' | grep -c "$e\[32;1mfoo$e\\[0;1m") == 1 ))
+(( $(nix search -f search.nix '' 'broken b' 'en bar' | grep -c "$e\[32;1mbroken bar$e\\[0m") == 1 ))
 
 # Multiple matches
 # Searching for 'o' should yield the 'o' in 'broken bar', the 'oo' in foo and 'o' in hello
-(( $(nix search -f search.nix '' 'o' | grep -Eo "$e\[32;1mo{1,2}$e\[(0|0;1)m" | wc -l) == 3 ))
+(( $(nix search -f search.nix '' 'o' | grep -Eoc "$e\[32;1mo{1,2}$e\[(0|0;1)m") == 3 ))
 # Searching for 'b' should yield the 'b' in bar and the two 'b's in 'broken bar'
+# NOTE: This does not work with `grep -c` because it counts the two 'b's in 'broken bar' as one matched line
 (( $(nix search -f search.nix '' 'b' | grep -Eo "$e\[32;1mb$e\[(0|0;1)m" | wc -l) == 3 ))
 
 ## Tests for --exclude