From e5259a672b65a5d33032becb8899511fac8a1e93 Mon Sep 17 00:00:00 2001
From: Kevin Robert Stravers <kevin@stravers.net>
Date: Sat, 21 Sep 2024 02:58:18 -0400
Subject: [PATCH] nix repl: Add `:ll` to show all recently loaded variables

Invoking `:ll` will start a pager with all variables which have just
been loaded by `:lf`, `:l`, or by a flake provided to `nix repl` as an
argument.

https://github.com/NixOS/nix/issues/11404
---
 src/libcmd/repl.cc       | 21 ++++++++++++++++++++-
 tests/functional/repl.sh |  4 ++--
 2 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index edae48aa7..e9a076b20 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -66,6 +66,7 @@ struct NixRepl
 
     const static int envSize = 32768;
     std::shared_ptr<StaticEnv> staticEnv;
+    Value lastLoaded;
     Env * env;
     int displ;
     StringSet varNames;
@@ -90,6 +91,7 @@ struct NixRepl
     void loadFile(const Path & path);
     void loadFlake(const std::string & flakeRef);
     void loadFiles();
+    void showLastLoaded();
     void reloadFiles();
     void addAttrsToScope(Value & attrs);
     void addVarToScope(const Symbol name, Value & v);
@@ -374,6 +376,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
              << "                               current profile\n"
              << "  :l, :load <path>             Load Nix expression and add it to scope\n"
              << "  :lf, :load-flake <ref>       Load Nix flake and add it to scope\n"
+             << "  :ll, :last-loaded            Show most recently loaded variables added to scope\n"
              << "  :p, :print <expr>            Evaluate and print expression recursively\n"
              << "                               Strings are printed directly, without escaping.\n"
              << "  :q, :quit                    Exit nix-repl\n"
@@ -464,6 +467,10 @@ ProcessLineResult NixRepl::processLine(std::string line)
         loadFlake(arg);
     }
 
+    else if (command == ":ll" || command == ":last-loaded") {
+        showLastLoaded();
+    }
+
     else if (command == ":r" || command == ":reload") {
         state->resetFileCache();
         reloadFiles();
@@ -751,6 +758,16 @@ void NixRepl::initEnv()
         varNames.emplace(state->symbols[i.first]);
 }
 
+void NixRepl::showLastLoaded()
+{
+    RunPager pager;
+
+    for (auto & i : *lastLoaded.attrs()) {
+        std::string_view name = state->symbols[i.name];
+        logger->cout(name);
+    }
+}
+
 
 void NixRepl::reloadFiles()
 {
@@ -792,6 +809,8 @@ void NixRepl::addAttrsToScope(Value & attrs)
     staticEnv->deduplicate();
     notice("Added %1% variables.", attrs.attrs()->size());
 
+    lastLoaded = attrs;
+
     const int max_print = 10;
     int counter = 0;
     std::ostringstream loaded;
@@ -809,7 +828,7 @@ void NixRepl::addAttrsToScope(Value & attrs)
     notice("%1%", loaded.str());
 
     if (attrs.attrs()->size() > max_print)
-        notice("... and %1% more", attrs.attrs()->size() - max_print);
+        notice("... and %1% more; view with :ll", attrs.attrs()->size() - max_print);
 }
 
 
diff --git a/tests/functional/repl.sh b/tests/functional/repl.sh
index bcd331398..ff4a74956 100755
--- a/tests/functional/repl.sh
+++ b/tests/functional/repl.sh
@@ -177,7 +177,7 @@ testReplResponseNoRegex $'
 :a builtins.foldl\' (x: y: x // y) {} (map (x: { ${builtins.toString x} = x; }) (builtins.genList (x: x) 13))
 ' 'Added 13 variables.
 "0", "1", "10", "11", "12", "2", "3", "4", "5", "6"
-... and 3 more'
+... and 3 more; view with :ll'
 
 # Test the `:reload` mechansim with flakes:
 # - Eval `./flake#changingThing`
@@ -328,7 +328,7 @@ runRepl () {
       -e "s@$testDirNoUnderscores@/path/to/tests/functional@g" \
       -e "s@$nixVersion@<nix version>@g" \
       -e "/Added [0-9]* variables/{s@ [0-9]* @ <number omitted> @;n;d}" \
-      -e '/\.\.\. and [0-9]* more/d' \
+      -e '/\.\.\. and [0-9]* more; view with :ll/d' \
     | grep -vF $'warning: you don\'t have Internet access; disabling some network-dependent features' \
     ;
 }