From c5f23f10a84f568874321c04984b1a14d2dce978 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 10 May 2017 18:34:18 +0200 Subject: Replace readline by linenoise Using linenoise avoids a license compatibility issue (#1356), is a lot smaller and doesn't pull in ncurses. --- src/nix/local.mk | 6 +- src/nix/repl.cc | 167 +++++++++++++++++++++---------------------------------- 2 files changed, 63 insertions(+), 110 deletions(-) (limited to 'src/nix') diff --git a/src/nix/local.mk b/src/nix/local.mk index e71cf16fabf6..c7d2d328aab5 100644 --- a/src/nix/local.mk +++ b/src/nix/local.mk @@ -2,12 +2,8 @@ programs += nix nix_DIR := $(d) -nix_SOURCES := $(wildcard $(d)/*.cc) +nix_SOURCES := $(wildcard $(d)/*.cc) src/linenoise/linenoise.c nix_LIBS = libexpr libmain libstore libutil libformat -ifeq ($(HAVE_READLINE), 1) - nix_LDFLAGS += -lreadline -endif - $(eval $(call install-symlink, nix, $(bindir)/nix-hash)) diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 13488bf1dbd4..437c7903ed40 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -1,13 +1,8 @@ -#if HAVE_LIBREADLINE - #include #include #include -#include -#include - #include "shared.hh" #include "eval.hh" #include "eval-inline.hh" @@ -18,6 +13,9 @@ #include "affinity.hh" #include "globals.hh" #include "command.hh" +#include "finally.hh" + +#include "src/linenoise/linenoise.h" namespace nix { @@ -44,14 +42,11 @@ struct NixRepl const Path historyFile; - StringSet completions; - StringSet::iterator curCompletion; - NixRepl(const Strings & searchPath, nix::ref store); ~NixRepl(); void mainLoop(const Strings & files); - void completePrefix(string prefix); - bool getLine(string & input, const char * prompt); + StringSet completePrefix(string prefix); + bool getLine(string & input, const std::string &prompt); Path getDerivationPath(Value & v); bool processLine(string line); void loadFile(const Path & path); @@ -122,7 +117,17 @@ NixRepl::NixRepl(const Strings & searchPath, nix::ref store) NixRepl::~NixRepl() { - write_history(historyFile.c_str()); + linenoiseHistorySave(historyFile.c_str()); +} + + +static NixRepl * curRepl; // ugly + +static void completionCallback(const char * s, linenoiseCompletions *lc) +{ + /* Otherwise, return all symbols that start with the prefix. */ + for (auto & c : curRepl->completePrefix(s)) + linenoiseAddCompletion(lc, c.c_str()); } @@ -137,22 +142,20 @@ void NixRepl::mainLoop(const Strings & files) reloadFiles(); if (!loadedFiles.empty()) std::cout << std::endl; - // Allow nix-repl specific settings in .inputrc - rl_readline_name = "nix-repl"; - using_history(); createDirs(dirOf(historyFile)); - read_history(historyFile.c_str()); + linenoiseHistorySetMaxLen(1000); + linenoiseHistoryLoad(historyFile.c_str()); - string input; + curRepl = this; + linenoiseSetCompletionCallback(completionCallback); + + std::string input; while (true) { // When continuing input from previous lines, don't print a prompt, just align to the same // number of chars as the prompt. - const char * prompt = input.empty() ? "nix-repl> " : " "; - if (!getLine(input, prompt)) { - std::cout << std::endl; + if (!getLine(input, input.empty() ? "nix-repl> " : " ")) break; - } try { if (!removeWhitespace(input).empty() && !processLine(input)) return; @@ -170,103 +173,57 @@ void NixRepl::mainLoop(const Strings & files) printMsg(lvlError, format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg()); } - // We handled the current input fully, so we should clear it and read brand new input. + // We handled the current input fully, so we should clear it + // and read brand new input. + linenoiseHistoryAdd(input.c_str()); input.clear(); std::cout << std::endl; } } -/* Apparently, the only way to get readline() to return on Ctrl-C - (SIGINT) is to use siglongjmp(). That's fucked up... */ -static sigjmp_buf sigintJmpBuf; - - -static void sigintHandler(int signo) +bool NixRepl::getLine(string & input, const std::string &prompt) { - siglongjmp(sigintJmpBuf, 1); -} - - -/* Oh, if only g++ had nested functions... */ -NixRepl * curRepl; - -char * completerThunk(const char * s, int state) -{ - string prefix(s); - - /* If the prefix has a slash in it, use readline's builtin filename - completer. */ - if (prefix.find('/') != string::npos) - return rl_filename_completion_function(s, state); - - /* Otherwise, return all symbols that start with the prefix. */ - if (state == 0) { - curRepl->completePrefix(s); - curRepl->curCompletion = curRepl->completions.begin(); - } - if (curRepl->curCompletion == curRepl->completions.end()) return 0; - return strdup((curRepl->curCompletion++)->c_str()); + char * s = linenoise(prompt.c_str()); + Finally doFree([&]() { linenoiseFree(s); }); + if (!s) return false; + input += s; + return true; } -bool NixRepl::getLine(string & input, const char * prompt) +StringSet NixRepl::completePrefix(string prefix) { - struct sigaction act, old; - act.sa_handler = sigintHandler; - sigfillset(&act.sa_mask); - act.sa_flags = 0; - if (sigaction(SIGINT, &act, &old)) - throw SysError("installing handler for SIGINT"); - - static sigset_t savedSignalMask, set; - sigemptyset(&set); - sigaddset(&set, SIGINT); - - if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask)) - throw SysError("unblocking SIGINT"); + StringSet completions; - if (sigsetjmp(sigintJmpBuf, 1)) { - input.clear(); + size_t start = prefix.find_last_of(" \n\r\t(){}[]"); + std::string prev, cur; + if (start == std::string::npos) { + prev = ""; + cur = prefix; } else { - curRepl = this; - rl_completion_entry_function = completerThunk; - - char * s = readline(prompt); - if (!s) return false; - input.append(s); - input.push_back('\n'); - if (!removeWhitespace(s).empty()) { - add_history(s); - append_history(1, 0); - } - free(s); + prev = std::string(prefix, 0, start + 1); + cur = std::string(prefix, start + 1); } - _isInterrupted = 0; - - if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr)) - throw SysError("restoring signals"); - - if (sigaction(SIGINT, &old, 0)) - throw SysError("restoring handler for SIGINT"); - - return true; -} - - -void NixRepl::completePrefix(string prefix) -{ - completions.clear(); - - size_t dot = prefix.rfind('.'); + size_t slash, dot; - if (dot == string::npos) { + if ((slash = cur.rfind('/')) != string::npos) { + try { + auto dir = std::string(cur, 0, slash); + auto prefix2 = std::string(cur, slash + 1); + for (auto & entry : readDirectory(dir == "" ? "/" : dir)) { + if (entry.name[0] != '.' && hasPrefix(entry.name, prefix2)) + completions.insert(prev + dir + "/" + entry.name); + } + } catch (Error &) { + } + } else if ((dot = cur.rfind('.')) == string::npos) { /* This is a variable name; look it up in the current scope. */ - StringSet::iterator i = varNames.lower_bound(prefix); + StringSet::iterator i = varNames.lower_bound(cur); while (i != varNames.end()) { - if (string(*i, 0, prefix.size()) != prefix) break; - completions.insert(*i); + if (string(*i, 0, cur.size()) != cur) break; + completions.insert(prev + *i); i++; } } else { @@ -274,8 +231,8 @@ void NixRepl::completePrefix(string prefix) /* This is an expression that should evaluate to an attribute set. Evaluate it to get the names of the attributes. */ - string expr(prefix, 0, dot); - string prefix2 = string(prefix, dot + 1); + string expr(cur, 0, dot); + string cur2 = string(cur, dot + 1); Expr * e = parseString(expr); Value v; @@ -284,8 +241,8 @@ void NixRepl::completePrefix(string prefix) for (auto & i : *v.attrs) { string name = i.name; - if (string(name, 0, prefix2.size()) != prefix2) continue; - completions.insert(expr + "." + name); + if (string(name, 0, cur2.size()) != cur2) continue; + completions.insert(prev + expr + "." + name); } } catch (ParseError & e) { @@ -296,6 +253,8 @@ void NixRepl::completePrefix(string prefix) // Quietly ignore undefined variable errors. } } + + return completions; } @@ -728,5 +687,3 @@ struct CmdRepl : StoreCommand static RegisterCommand r1(make_ref()); } - -#endif -- cgit 1.4.1