Replace readline by linenoise

Using linenoise avoids a license compatibility issue (#1356), is a lot
smaller and doesn't pull in ncurses.
This commit is contained in:
Eelco Dolstra 2017-05-10 18:34:18 +02:00
parent 82a9c93c7f
commit c5f23f10a8
No known key found for this signature in database
GPG key ID: 8170B4726D7198DE
10 changed files with 1379 additions and 134 deletions

View file

@ -196,14 +196,6 @@ if test "$gc" = yes; then
fi fi
# Check for readline, needed by "nix repl".
AX_LIB_READLINE
if test "$ax_cv_lib_readline" != "no"; then
have_readline=1
fi
AC_SUBST(HAVE_READLINE, [$have_readline])
AC_ARG_ENABLE(init-state, AC_HELP_STRING([--disable-init-state], AC_ARG_ENABLE(init-state, AC_HELP_STRING([--disable-init-state],
[do not initialise DB etc. in `make install']), [do not initialise DB etc. in `make install']),
init_state=$enableval, init_state=yes) init_state=$enableval, init_state=yes)

View file

@ -261,6 +261,12 @@ xlink:href="http://nixos.org/">NixOS homepage</link>.</para>
xlink:href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">GNU xlink:href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">GNU
LGPLv2.1 or (at your option) any later version</link>.</para> LGPLv2.1 or (at your option) any later version</link>.</para>
<para>Nix uses the <link
xlink:href="https://github.com/antirez/linenoise">linenoise
library</link>, which has the following license:</para>
<programlisting><xi:include href="../../../src/linenoise/LICENSE" parse="text" /></programlisting>
</simplesect> </simplesect>

View file

@ -20,11 +20,9 @@ Requires: curl
Requires: bzip2 Requires: bzip2
Requires: gzip Requires: gzip
Requires: xz Requires: xz
Requires: readline
BuildRequires: bzip2-devel BuildRequires: bzip2-devel
BuildRequires: sqlite-devel BuildRequires: sqlite-devel
BuildRequires: libcurl-devel BuildRequires: libcurl-devel
BuildRequires: readline-devel
# Hack to make that shitty RPM scanning hack shut up. # Hack to make that shitty RPM scanning hack shut up.
Provides: perl(Nix::SSH) Provides: perl(Nix::SSH)

View file

@ -73,7 +73,7 @@ let
buildInputs = buildInputs =
[ curl [ curl
bzip2 xz brotli bzip2 xz brotli
openssl pkgconfig sqlite boehmgc readline openssl pkgconfig sqlite boehmgc
] ]
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
@ -198,15 +198,15 @@ let
rpm_fedora25x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora25x86_64) [ "libsodium-devel" ]; rpm_fedora25x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora25x86_64) [ "libsodium-devel" ];
deb_debian8i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.debian8i386) [ "libsodium-dev" ] [ "libsodium13" "libreadline6" ]; deb_debian8i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.debian8i386) [ "libsodium-dev" ] [ "libsodium13" ];
deb_debian8x86_64 = makeDeb_x86_64 (diskImageFunsFun: diskImageFunsFun.debian8x86_64) [ "libsodium-dev" ] [ "libsodium13" "libreadline6" ]; deb_debian8x86_64 = makeDeb_x86_64 (diskImageFunsFun: diskImageFunsFun.debian8x86_64) [ "libsodium-dev" ] [ "libsodium13" ];
deb_ubuntu1410i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1410i386) [] [ "libreadline6" ]; deb_ubuntu1410i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1410i386) [] [];
deb_ubuntu1410x86_64 = makeDeb_x86_64 (diskImageFuns: diskImageFuns.ubuntu1410x86_64) [] [ "libreadline6" ]; deb_ubuntu1410x86_64 = makeDeb_x86_64 (diskImageFuns: diskImageFuns.ubuntu1410x86_64) [] [];
deb_ubuntu1604i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1604i386) [ "libsodium-dev" ] [ "libsodium18" "libreadline6" ]; deb_ubuntu1604i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1604i386) [ "libsodium-dev" ] [ "libsodium18" ];
deb_ubuntu1604x86_64 = makeDeb_x86_64 (diskImageFuns: diskImageFuns.ubuntu1604x86_64) [ "libsodium-dev" ] [ "libsodium18" "libreadline6" ]; deb_ubuntu1604x86_64 = makeDeb_x86_64 (diskImageFuns: diskImageFuns.ubuntu1604x86_64) [ "libsodium-dev" ] [ "libsodium18" ];
deb_ubuntu1610i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1610i386) [ "libsodium-dev" ] [ "libsodium18" "libreadline7" ]; deb_ubuntu1610i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1610i386) [ "libsodium-dev" ] [ "libsodium18" ];
deb_ubuntu1610x86_64 = makeDeb_x86_64 (diskImageFuns: diskImageFuns.ubuntu1610x86_64) [ "libsodium-dev" ] [ "libsodium18" "libreadline7" ]; deb_ubuntu1610x86_64 = makeDeb_x86_64 (diskImageFuns: diskImageFuns.ubuntu1610x86_64) [ "libsodium-dev" ] [ "libsodium18" ];
# System tests. # System tests.
@ -299,7 +299,7 @@ let
src = jobs.tarball; src = jobs.tarball;
diskImage = (diskImageFun vmTools.diskImageFuns) diskImage = (diskImageFun vmTools.diskImageFuns)
{ extraPackages = { extraPackages =
[ "sqlite" "sqlite-devel" "bzip2-devel" "emacs" "libcurl-devel" "openssl-devel" "xz-devel" "readline-devel" ] [ "sqlite" "sqlite-devel" "bzip2-devel" "emacs" "libcurl-devel" "openssl-devel" "xz-devel" ]
++ extraPackages; }; ++ extraPackages; };
memSize = 1024; memSize = 1024;
meta.schedulingPriority = 50; meta.schedulingPriority = 50;
@ -321,7 +321,7 @@ let
src = jobs.tarball; src = jobs.tarball;
diskImage = (diskImageFun vmTools.diskImageFuns) diskImage = (diskImageFun vmTools.diskImageFuns)
{ extraPackages = { extraPackages =
[ "libsqlite3-dev" "libbz2-dev" "libcurl-dev" "libcurl3-nss" "libssl-dev" "liblzma-dev" "libreadline-dev" ] [ "libsqlite3-dev" "libbz2-dev" "libcurl-dev" "libcurl3-nss" "libssl-dev" "liblzma-dev" ]
++ extraPackages; }; ++ extraPackages; };
memSize = 1024; memSize = 1024;
meta.schedulingPriority = 50; meta.schedulingPriority = 50;

View file

@ -16,7 +16,6 @@ with import <nixpkgs> {};
customMemoryManagement = false; customMemoryManagement = false;
}) })
autoreconfHook autoreconfHook
readline
# For nix-perl # For nix-perl
perl perl

25
src/linenoise/LICENSE Normal file
View file

@ -0,0 +1,25 @@
Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1199
src/linenoise/linenoise.c Normal file

File diff suppressed because it is too large Load diff

73
src/linenoise/linenoise.h Normal file
View file

@ -0,0 +1,73 @@
/* linenoise.h -- VERSION 1.0
*
* Guerrilla line editing library against the idea that a line editing lib
* needs to be 20,000 lines of C code.
*
* See linenoise.c for more information.
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __LINENOISE_H
#define __LINENOISE_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct linenoiseCompletions {
size_t len;
char **cvec;
} linenoiseCompletions;
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
typedef void(linenoiseFreeHintsCallback)(void *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
char *linenoise(const char *prompt);
void linenoiseFree(void *ptr);
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(const char *filename);
void linenoiseClearScreen(void);
void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void);
#ifdef __cplusplus
}
#endif
#endif /* __LINENOISE_H */

View file

@ -2,12 +2,8 @@ programs += nix
nix_DIR := $(d) nix_DIR := $(d)
nix_SOURCES := $(wildcard $(d)/*.cc) nix_SOURCES := $(wildcard $(d)/*.cc) src/linenoise/linenoise.c
nix_LIBS = libexpr libmain libstore libutil libformat nix_LIBS = libexpr libmain libstore libutil libformat
ifeq ($(HAVE_READLINE), 1)
nix_LDFLAGS += -lreadline
endif
$(eval $(call install-symlink, nix, $(bindir)/nix-hash)) $(eval $(call install-symlink, nix, $(bindir)/nix-hash))

View file

@ -1,13 +1,8 @@
#if HAVE_LIBREADLINE
#include <iostream> #include <iostream>
#include <cstdlib> #include <cstdlib>
#include <setjmp.h> #include <setjmp.h>
#include <readline/readline.h>
#include <readline/history.h>
#include "shared.hh" #include "shared.hh"
#include "eval.hh" #include "eval.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
@ -18,6 +13,9 @@
#include "affinity.hh" #include "affinity.hh"
#include "globals.hh" #include "globals.hh"
#include "command.hh" #include "command.hh"
#include "finally.hh"
#include "src/linenoise/linenoise.h"
namespace nix { namespace nix {
@ -44,14 +42,11 @@ struct NixRepl
const Path historyFile; const Path historyFile;
StringSet completions;
StringSet::iterator curCompletion;
NixRepl(const Strings & searchPath, nix::ref<Store> store); NixRepl(const Strings & searchPath, nix::ref<Store> store);
~NixRepl(); ~NixRepl();
void mainLoop(const Strings & files); void mainLoop(const Strings & files);
void completePrefix(string prefix); StringSet completePrefix(string prefix);
bool getLine(string & input, const char * prompt); bool getLine(string & input, const std::string &prompt);
Path getDerivationPath(Value & v); Path getDerivationPath(Value & v);
bool processLine(string line); bool processLine(string line);
void loadFile(const Path & path); void loadFile(const Path & path);
@ -122,7 +117,17 @@ NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store)
NixRepl::~NixRepl() 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(); reloadFiles();
if (!loadedFiles.empty()) std::cout << std::endl; if (!loadedFiles.empty()) std::cout << std::endl;
// Allow nix-repl specific settings in .inputrc
rl_readline_name = "nix-repl";
using_history();
createDirs(dirOf(historyFile)); 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) { while (true) {
// 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.
const char * prompt = input.empty() ? "nix-repl> " : " "; if (!getLine(input, input.empty() ? "nix-repl> " : " "))
if (!getLine(input, prompt)) {
std::cout << std::endl;
break; break;
}
try { try {
if (!removeWhitespace(input).empty() && !processLine(input)) return; 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()); 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(); input.clear();
std::cout << std::endl; std::cout << std::endl;
} }
} }
/* Apparently, the only way to get readline() to return on Ctrl-C bool NixRepl::getLine(string & input, const std::string &prompt)
(SIGINT) is to use siglongjmp(). That's fucked up... */
static sigjmp_buf sigintJmpBuf;
static void sigintHandler(int signo)
{ {
siglongjmp(sigintJmpBuf, 1); char * s = linenoise(prompt.c_str());
} Finally doFree([&]() { linenoiseFree(s); });
/* 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());
}
bool NixRepl::getLine(string & input, const char * prompt)
{
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");
if (sigsetjmp(sigintJmpBuf, 1)) {
input.clear();
} else {
curRepl = this;
rl_completion_entry_function = completerThunk;
char * s = readline(prompt);
if (!s) return false; if (!s) return false;
input.append(s); input += s;
input.push_back('\n');
if (!removeWhitespace(s).empty()) {
add_history(s);
append_history(1, 0);
}
free(s);
}
_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; return true;
} }
void NixRepl::completePrefix(string prefix) StringSet NixRepl::completePrefix(string prefix)
{ {
completions.clear(); StringSet completions;
size_t dot = prefix.rfind('.'); size_t start = prefix.find_last_of(" \n\r\t(){}[]");
std::string prev, cur;
if (start == std::string::npos) {
prev = "";
cur = prefix;
} else {
prev = std::string(prefix, 0, start + 1);
cur = std::string(prefix, start + 1);
}
if (dot == string::npos) { size_t slash, dot;
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. */ /* 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()) { while (i != varNames.end()) {
if (string(*i, 0, prefix.size()) != prefix) break; if (string(*i, 0, cur.size()) != cur) break;
completions.insert(*i); completions.insert(prev + *i);
i++; i++;
} }
} else { } else {
@ -274,8 +231,8 @@ void NixRepl::completePrefix(string prefix)
/* This is an expression that should evaluate to an /* This is an expression that should evaluate to an
attribute set. Evaluate it to get the names of the attribute set. Evaluate it to get the names of the
attributes. */ attributes. */
string expr(prefix, 0, dot); string expr(cur, 0, dot);
string prefix2 = string(prefix, dot + 1); string cur2 = string(cur, dot + 1);
Expr * e = parseString(expr); Expr * e = parseString(expr);
Value v; Value v;
@ -284,8 +241,8 @@ void NixRepl::completePrefix(string prefix)
for (auto & i : *v.attrs) { for (auto & i : *v.attrs) {
string name = i.name; string name = i.name;
if (string(name, 0, prefix2.size()) != prefix2) continue; if (string(name, 0, cur2.size()) != cur2) continue;
completions.insert(expr + "." + name); completions.insert(prev + expr + "." + name);
} }
} catch (ParseError & e) { } catch (ParseError & e) {
@ -296,6 +253,8 @@ void NixRepl::completePrefix(string prefix)
// Quietly ignore undefined variable errors. // Quietly ignore undefined variable errors.
} }
} }
return completions;
} }
@ -728,5 +687,3 @@ struct CmdRepl : StoreCommand
static RegisterCommand r1(make_ref<CmdRepl>()); static RegisterCommand r1(make_ref<CmdRepl>());
} }
#endif