forked from lix-project/lix
refactor: move readline stuff into its own file
This is in direct preparation for an automation mode of nix repl.
Change-Id: I26e6ca88ef1c48aab11a2d1e939ff769f1770caa
This commit is contained in:
parent
45f6e3521a
commit
95a87f2c2a
175
src/libcmd/repl-interacter.cc
Normal file
175
src/libcmd/repl-interacter.cc
Normal file
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
48
src/libcmd/repl-interacter.hh
Normal file
48
src/libcmd/repl-interacter.hh
Normal file
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
|
@ -3,22 +3,8 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
|
||||||
#include <setjmp.h>
|
#include "box_ptr.hh"
|
||||||
|
#include "repl-interacter.hh"
|
||||||
#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 "repl.hh"
|
#include "repl.hh"
|
||||||
|
|
||||||
#include "ansicolor.hh"
|
#include "ansicolor.hh"
|
||||||
|
@ -28,6 +14,7 @@ extern "C" {
|
||||||
#include "eval-inline.hh"
|
#include "eval-inline.hh"
|
||||||
#include "eval-settings.hh"
|
#include "eval-settings.hh"
|
||||||
#include "attr-path.hh"
|
#include "attr-path.hh"
|
||||||
|
#include "signals.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "log-store.hh"
|
#include "log-store.hh"
|
||||||
#include "common-eval-args.hh"
|
#include "common-eval-args.hh"
|
||||||
|
@ -73,6 +60,7 @@ enum class ProcessLineResult {
|
||||||
|
|
||||||
struct NixRepl
|
struct NixRepl
|
||||||
: AbstractNixRepl
|
: AbstractNixRepl
|
||||||
|
, detail::ReplCompleterMixin
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
, gc
|
, gc
|
||||||
#endif
|
#endif
|
||||||
|
@ -88,17 +76,16 @@ struct NixRepl
|
||||||
int displ;
|
int displ;
|
||||||
StringSet varNames;
|
StringSet varNames;
|
||||||
|
|
||||||
const Path historyFile;
|
box_ptr<ReplInteracter> interacter;
|
||||||
|
|
||||||
NixRepl(const SearchPath & searchPath, nix::ref<Store> store,ref<EvalState> state,
|
NixRepl(const SearchPath & searchPath, nix::ref<Store> store,ref<EvalState> state,
|
||||||
std::function<AnnotatedValues()> getValues);
|
std::function<AnnotatedValues()> getValues);
|
||||||
virtual ~NixRepl();
|
virtual ~NixRepl() = default;
|
||||||
|
|
||||||
ReplExitStatus mainLoop() override;
|
ReplExitStatus mainLoop() override;
|
||||||
void initEnv() override;
|
void initEnv() override;
|
||||||
|
|
||||||
StringSet completePrefix(const std::string & prefix);
|
virtual StringSet completePrefix(const std::string & prefix) override;
|
||||||
bool getLine(std::string & input, const std::string & prompt);
|
|
||||||
StorePath getDerivationPath(Value & v);
|
StorePath getDerivationPath(Value & v);
|
||||||
ProcessLineResult processLine(std::string line);
|
ProcessLineResult processLine(std::string line);
|
||||||
|
|
||||||
|
@ -141,16 +128,10 @@ NixRepl::NixRepl(const SearchPath & searchPath, nix::ref<Store> store, ref<EvalS
|
||||||
, debugTraceIndex(0)
|
, debugTraceIndex(0)
|
||||||
, getValues(getValues)
|
, getValues(getValues)
|
||||||
, staticEnv(new StaticEnv(nullptr, state->staticBaseEnv.get()))
|
, 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,
|
void runNix(Path program, const Strings & args,
|
||||||
const std::optional<std::string> & input = {})
|
const std::optional<std::string> & input = {})
|
||||||
{
|
{
|
||||||
|
@ -167,79 +148,6 @@ void runNix(Path program, const Strings & args,
|
||||||
return;
|
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)
|
static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positions, const DebugTrace & dt)
|
||||||
{
|
{
|
||||||
if (dt.isError)
|
if (dt.isError)
|
||||||
|
@ -279,24 +187,7 @@ ReplExitStatus NixRepl::mainLoop()
|
||||||
|
|
||||||
loadFiles();
|
loadFiles();
|
||||||
|
|
||||||
// Allow nix-repl specific settings in .inputrc
|
auto _guard = interacter->init(static_cast<detail::ReplCompleterMixin *>(this));
|
||||||
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
|
|
||||||
|
|
||||||
std::string input;
|
std::string input;
|
||||||
|
|
||||||
|
@ -305,7 +196,7 @@ ReplExitStatus NixRepl::mainLoop()
|
||||||
logger->pause();
|
logger->pause();
|
||||||
// When continuing input from previous lines, don't print a prompt, just align to the same
|
// When continuing input from previous lines, don't print a prompt, just align to the same
|
||||||
// number of chars as the prompt.
|
// 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.
|
// Ctrl-D should exit the debugger.
|
||||||
state->debugStop = false;
|
state->debugStop = false;
|
||||||
logger->cout("");
|
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 NixRepl::completePrefix(const std::string & prefix)
|
||||||
{
|
{
|
||||||
StringSet completions;
|
StringSet completions;
|
||||||
|
|
|
@ -3,11 +3,6 @@
|
||||||
|
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
|
||||||
#define GC_INCLUDE_NEW
|
|
||||||
#include <gc/gc_cpp.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
struct AbstractNixRepl
|
struct AbstractNixRepl
|
||||||
|
|
Loading…
Reference in a new issue