aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJade Lovelace <lix@jade.fyi>2024-03-09 22:05:50 -0800
committerJade Lovelace <lix@jade.fyi>2024-03-11 01:04:52 -0700
commit95a87f2c2ace3875f4c0bd4709bf062fb35ac81b (patch)
tree84b65d5e236c8caa7e1bd23929f2409c95d472e7
parent45f6e3521a2b4f535e29d03dd077978a691bdb58 (diff)
refactor: move readline stuff into its own file
This is in direct preparation for an automation mode of nix repl. Change-Id: I26e6ca88ef1c48aab11a2d1e939ff769f1770caa
-rw-r--r--src/libcmd/repl-interacter.cc175
-rw-r--r--src/libcmd/repl-interacter.hh48
-rw-r--r--src/libcmd/repl.cc174
-rw-r--r--src/libcmd/repl.hh5
4 files changed, 233 insertions, 169 deletions
diff --git a/src/libcmd/repl-interacter.cc b/src/libcmd/repl-interacter.cc
new file mode 100644
index 000000000..93d060e30
--- /dev/null
+++ b/src/libcmd/repl-interacter.cc
@@ -0,0 +1,175 @@
+#include <cstdio>
+
+#ifdef READLINE
+#include <readline/history.h>
+#include <readline/readline.h>
+#else
+// editline < 1.15.2 don't wrap their API for C++ usage
+// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
+// This results in linker errors due to to name-mangling of editline C symbols.
+// For compatibility with these versions, we wrap the API here
+// (wrapping multiple times on newer versions is no problem).
+extern "C" {
+#include <editline.h>
+}
+#endif
+
+#include "signals.hh"
+#include "finally.hh"
+#include "repl-interacter.hh"
+#include "util.hh"
+#include "repl.hh"
+
+namespace nix {
+
+namespace {
+// Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
+volatile sig_atomic_t g_signal_received = 0;
+
+void sigintHandler(int signo)
+{
+ g_signal_received = signo;
+}
+};
+
+static detail::ReplCompleterMixin * curRepl; // ugly
+
+static char * completionCallback(char * s, int * match)
+{
+ auto possible = curRepl->completePrefix(s);
+ if (possible.size() == 1) {
+ *match = 1;
+ auto * res = strdup(possible.begin()->c_str() + strlen(s));
+ if (!res)
+ throw Error("allocation failure");
+ return res;
+ } else if (possible.size() > 1) {
+ auto checkAllHaveSameAt = [&](size_t pos) {
+ auto & first = *possible.begin();
+ for (auto & p : possible) {
+ if (p.size() <= pos || p[pos] != first[pos])
+ return false;
+ }
+ return true;
+ };
+ size_t start = strlen(s);
+ size_t len = 0;
+ while (checkAllHaveSameAt(start + len))
+ ++len;
+ if (len > 0) {
+ *match = 1;
+ auto * res = strdup(std::string(*possible.begin(), start, len).c_str());
+ if (!res)
+ throw Error("allocation failure");
+ return res;
+ }
+ }
+
+ *match = 0;
+ return nullptr;
+}
+
+static int listPossibleCallback(char * s, char *** avp)
+{
+ auto possible = curRepl->completePrefix(s);
+
+ if (possible.size() > (INT_MAX / sizeof(char *)))
+ throw Error("too many completions");
+
+ int ac = 0;
+ char ** vp = nullptr;
+
+ auto check = [&](auto * p) {
+ if (!p) {
+ if (vp) {
+ while (--ac >= 0)
+ free(vp[ac]);
+ free(vp);
+ }
+ throw Error("allocation failure");
+ }
+ return p;
+ };
+
+ vp = check((char **) malloc(possible.size() * sizeof(char *)));
+
+ for (auto & p : possible)
+ vp[ac++] = check(strdup(p.c_str()));
+
+ *avp = vp;
+
+ return ac;
+}
+
+ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleterMixin * repl)
+{
+ // Allow nix-repl specific settings in .inputrc
+ rl_readline_name = "nix-repl";
+ try {
+ createDirs(dirOf(historyFile));
+ } catch (SysError & e) {
+ logWarning(e.info());
+ }
+#ifndef READLINE
+ el_hist_size = 1000;
+#endif
+ read_history(historyFile.c_str());
+ auto oldRepl = curRepl;
+ curRepl = repl;
+ Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
+#ifndef READLINE
+ rl_set_complete_func(completionCallback);
+ rl_set_list_possib_func(listPossibleCallback);
+#endif
+ return restoreRepl;
+}
+
+bool ReadlineLikeInteracter::getLine(std::string & input, const std::string & prompt)
+{
+ struct sigaction act, old;
+ sigset_t savedSignalMask, set;
+
+ auto setupSignals = [&]() {
+ act.sa_handler = sigintHandler;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGINT, &act, &old))
+ throw SysError("installing handler for SIGINT");
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGINT);
+ if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
+ throw SysError("unblocking SIGINT");
+ };
+ auto restoreSignals = [&]() {
+ if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
+ throw SysError("restoring signals");
+
+ if (sigaction(SIGINT, &old, 0))
+ throw SysError("restoring handler for SIGINT");
+ };
+
+ setupSignals();
+ char * s = readline(prompt.c_str());
+ Finally doFree([&]() { free(s); });
+ restoreSignals();
+
+ if (g_signal_received) {
+ g_signal_received = 0;
+ input.clear();
+ return true;
+ }
+
+ if (!s)
+ return false;
+ input += s;
+ input += '\n';
+ return true;
+}
+
+ReadlineLikeInteracter::~ReadlineLikeInteracter()
+{
+ write_history(historyFile.c_str());
+}
+
+};
diff --git a/src/libcmd/repl-interacter.hh b/src/libcmd/repl-interacter.hh
new file mode 100644
index 000000000..e549bab36
--- /dev/null
+++ b/src/libcmd/repl-interacter.hh
@@ -0,0 +1,48 @@
+#pragma once
+/// @file
+
+#include "finally.hh"
+#include "types.hh"
+#include <functional>
+#include <string>
+
+namespace nix {
+
+namespace detail {
+/** Provides the completion hooks for the repl, without exposing its complete
+ * internals. */
+struct ReplCompleterMixin {
+ virtual StringSet completePrefix(const std::string & prefix) = 0;
+};
+};
+
+enum class ReplPromptType {
+ ReplPrompt,
+ ContinuationPrompt,
+};
+
+class ReplInteracter
+{
+public:
+ using Guard = Finally<std::function<void()>>;
+
+ virtual Guard init(detail::ReplCompleterMixin * repl) = 0;
+ /** Returns a boolean of whether the interacter got EOF */
+ virtual bool getLine(std::string & input, const std::string & prompt) = 0;
+ virtual ~ReplInteracter(){};
+};
+
+class ReadlineLikeInteracter : public virtual ReplInteracter
+{
+ std::string historyFile;
+public:
+ ReadlineLikeInteracter(std::string historyFile)
+ : historyFile(historyFile)
+ {
+ }
+ virtual Guard init(detail::ReplCompleterMixin * repl) override;
+ virtual bool getLine(std::string & input, const std::string & prompt) override;
+ virtual ~ReadlineLikeInteracter() override;
+};
+
+};
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 5268ce44b..243324c61 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -3,22 +3,8 @@
#include <cstring>
#include <climits>
-#include <setjmp.h>
-
-#ifdef READLINE
-#include <readline/history.h>
-#include <readline/readline.h>
-#else
-// editline < 1.15.2 don't wrap their API for C++ usage
-// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
-// This results in linker errors due to to name-mangling of editline C symbols.
-// For compatibility with these versions, we wrap the API here
-// (wrapping multiple times on newer versions is no problem).
-extern "C" {
-#include <editline.h>
-}
-#endif
-
+#include "box_ptr.hh"
+#include "repl-interacter.hh"
#include "repl.hh"
#include "ansicolor.hh"
@@ -28,6 +14,7 @@ extern "C" {
#include "eval-inline.hh"
#include "eval-settings.hh"
#include "attr-path.hh"
+#include "signals.hh"
#include "store-api.hh"
#include "log-store.hh"
#include "common-eval-args.hh"
@@ -73,6 +60,7 @@ enum class ProcessLineResult {
struct NixRepl
: AbstractNixRepl
+ , detail::ReplCompleterMixin
#if HAVE_BOEHMGC
, gc
#endif
@@ -88,17 +76,16 @@ struct NixRepl
int displ;
StringSet varNames;
- const Path historyFile;
+ box_ptr<ReplInteracter> interacter;
NixRepl(const SearchPath & searchPath, nix::ref<Store> store,ref<EvalState> state,
std::function<AnnotatedValues()> getValues);
- virtual ~NixRepl();
+ virtual ~NixRepl() = default;
ReplExitStatus mainLoop() override;
void initEnv() override;
- StringSet completePrefix(const std::string & prefix);
- bool getLine(std::string & input, const std::string & prompt);
+ virtual StringSet completePrefix(const std::string & prefix) override;
StorePath getDerivationPath(Value & v);
ProcessLineResult processLine(std::string line);
@@ -141,16 +128,10 @@ NixRepl::NixRepl(const SearchPath & searchPath, nix::ref<Store> store, ref<EvalS
, debugTraceIndex(0)
, getValues(getValues)
, staticEnv(new StaticEnv(nullptr, state->staticBaseEnv.get()))
- , historyFile(getDataDir() + "/nix/repl-history")
+ , interacter(make_box_ptr<ReadlineLikeInteracter>(getDataDir() + "/nix/repl-history"))
{
}
-
-NixRepl::~NixRepl()
-{
- write_history(historyFile.c_str());
-}
-
void runNix(Path program, const Strings & args,
const std::optional<std::string> & input = {})
{
@@ -167,79 +148,6 @@ void runNix(Path program, const Strings & args,
return;
}
-static NixRepl * curRepl; // ugly
-
-static char * completionCallback(char * s, int *match) {
- auto possible = curRepl->completePrefix(s);
- if (possible.size() == 1) {
- *match = 1;
- auto *res = strdup(possible.begin()->c_str() + strlen(s));
- if (!res) throw Error("allocation failure");
- return res;
- } else if (possible.size() > 1) {
- auto checkAllHaveSameAt = [&](size_t pos) {
- auto &first = *possible.begin();
- for (auto &p : possible) {
- if (p.size() <= pos || p[pos] != first[pos])
- return false;
- }
- return true;
- };
- size_t start = strlen(s);
- size_t len = 0;
- while (checkAllHaveSameAt(start + len)) ++len;
- if (len > 0) {
- *match = 1;
- auto *res = strdup(std::string(*possible.begin(), start, len).c_str());
- if (!res) throw Error("allocation failure");
- return res;
- }
- }
-
- *match = 0;
- return nullptr;
-}
-
-static int listPossibleCallback(char *s, char ***avp) {
- auto possible = curRepl->completePrefix(s);
-
- if (possible.size() > (INT_MAX / sizeof(char*)))
- throw Error("too many completions");
-
- int ac = 0;
- char **vp = nullptr;
-
- auto check = [&](auto *p) {
- if (!p) {
- if (vp) {
- while (--ac >= 0)
- free(vp[ac]);
- free(vp);
- }
- throw Error("allocation failure");
- }
- return p;
- };
-
- vp = check((char **)malloc(possible.size() * sizeof(char*)));
-
- for (auto & p : possible)
- vp[ac++] = check(strdup(p.c_str()));
-
- *avp = vp;
-
- return ac;
-}
-
-namespace {
- // Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
- volatile sig_atomic_t g_signal_received = 0;
-
- void sigintHandler(int signo) {
- g_signal_received = signo;
- }
-}
-
static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positions, const DebugTrace & dt)
{
if (dt.isError)
@@ -279,24 +187,7 @@ ReplExitStatus NixRepl::mainLoop()
loadFiles();
- // Allow nix-repl specific settings in .inputrc
- rl_readline_name = "nix-repl";
- try {
- createDirs(dirOf(historyFile));
- } catch (SysError & e) {
- logWarning(e.info());
- }
-#ifndef READLINE
- el_hist_size = 1000;
-#endif
- read_history(historyFile.c_str());
- auto oldRepl = curRepl;
- curRepl = this;
- Finally restoreRepl([&] { curRepl = oldRepl; });
-#ifndef READLINE
- rl_set_complete_func(completionCallback);
- rl_set_list_possib_func(listPossibleCallback);
-#endif
+ auto _guard = interacter->init(static_cast<detail::ReplCompleterMixin *>(this));
std::string input;
@@ -305,7 +196,7 @@ ReplExitStatus NixRepl::mainLoop()
logger->pause();
// When continuing input from previous lines, don't print a prompt, just align to the same
// number of chars as the prompt.
- if (!getLine(input, input.empty() ? "nix-repl> " : " ")) {
+ if (!interacter->getLine(input, input.empty() ? "nix-repl> " : " ")) {
// Ctrl-D should exit the debugger.
state->debugStop = false;
logger->cout("");
@@ -354,51 +245,6 @@ ReplExitStatus NixRepl::mainLoop()
}
}
-
-bool NixRepl::getLine(std::string & input, const std::string & prompt)
-{
- struct sigaction act, old;
- sigset_t savedSignalMask, set;
-
- auto setupSignals = [&]() {
- act.sa_handler = sigintHandler;
- sigfillset(&act.sa_mask);
- act.sa_flags = 0;
- if (sigaction(SIGINT, &act, &old))
- throw SysError("installing handler for SIGINT");
-
- sigemptyset(&set);
- sigaddset(&set, SIGINT);
- if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
- throw SysError("unblocking SIGINT");
- };
- auto restoreSignals = [&]() {
- if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
- throw SysError("restoring signals");
-
- if (sigaction(SIGINT, &old, 0))
- throw SysError("restoring handler for SIGINT");
- };
-
- setupSignals();
- char * s = readline(prompt.c_str());
- Finally doFree([&]() { free(s); });
- restoreSignals();
-
- if (g_signal_received) {
- g_signal_received = 0;
- input.clear();
- return true;
- }
-
- if (!s)
- return false;
- input += s;
- input += '\n';
- return true;
-}
-
-
StringSet NixRepl::completePrefix(const std::string & prefix)
{
StringSet completions;
diff --git a/src/libcmd/repl.hh b/src/libcmd/repl.hh
index 21aa8bfc7..aac79ec74 100644
--- a/src/libcmd/repl.hh
+++ b/src/libcmd/repl.hh
@@ -3,11 +3,6 @@
#include "eval.hh"
-#if HAVE_BOEHMGC
-#define GC_INCLUDE_NEW
-#include <gc/gc_cpp.h>
-#endif
-
namespace nix {
struct AbstractNixRepl