aboutsummaryrefslogtreecommitdiff
path: root/src/libcmd/repl-interacter.cc
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 /src/libcmd/repl-interacter.cc
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
Diffstat (limited to 'src/libcmd/repl-interacter.cc')
-rw-r--r--src/libcmd/repl-interacter.cc175
1 files changed, 175 insertions, 0 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());
+}
+
+};