-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: move the repl input code to its own file #10083
Changes from all commits
f6158ea
76aced6
70a6ce1
ea31b8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
#include <cstdio> | ||
|
||
#ifdef USE_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 "file-system.hh" | ||
#include "libcmd/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 (SystemError & e) { | ||
logWarning(e.info()); | ||
} | ||
#ifndef USE_READLINE | ||
el_hist_size = 1000; | ||
#endif | ||
read_history(historyFile.c_str()); | ||
auto oldRepl = curRepl; | ||
curRepl = repl; | ||
Guard restoreRepl([oldRepl] { curRepl = oldRepl; }); | ||
#ifndef USE_READLINE | ||
rl_set_complete_func(completionCallback); | ||
rl_set_list_possib_func(listPossibleCallback); | ||
#endif | ||
return restoreRepl; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This used to be a Finally in caller scope so we have to hoist it to the caller by returning it now. Nothing else is changed in this function. |
||
} | ||
|
||
static constexpr const char * promptForType(ReplPromptType promptType) | ||
{ | ||
switch (promptType) { | ||
case ReplPromptType::ReplPrompt: | ||
return "nix-repl> "; | ||
case ReplPromptType::ContinuationPrompt: | ||
return " "; | ||
} | ||
assert(false); | ||
} | ||
Comment on lines
+127
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the only actual new code in here |
||
|
||
bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptType) | ||
{ | ||
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(promptForType(promptType)); | ||
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()); | ||
} | ||
|
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, ReplPromptType promptType) = 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, ReplPromptType promptType) override; | ||
virtual ~ReadlineLikeInteracter() override; | ||
}; | ||
|
||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is probably a bad idea/unsound to unwind through libreadline, resulting in the terminal not being reset! But I'm not fixing it in this PR as this PR is just moving code around.