libcmd|rs: use rustyline instead of editline for the repl

Change-Id: Ic414de3cc2435d9615305f05a85aa9c923e82a69
This commit is contained in:
eldritch horrors
2026-06-28 16:37:46 +00:00
parent 567348a90c
commit 2605d3eab1
19 changed files with 374 additions and 268 deletions
Generated
+126 -1
View File
@@ -82,7 +82,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd"
dependencies = [
"unicode-width",
"unicode-width 0.1.14",
"yansi",
]
@@ -145,6 +145,12 @@ dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -170,6 +176,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chumsky"
version = "1.0.0-alpha.8"
@@ -224,6 +236,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "clipboard-win"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
dependencies = [
"error-code",
]
[[package]]
name = "colorchoice"
version = "1.0.5"
@@ -277,12 +298,24 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
[[package]]
name = "endian-type"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "error-code"
version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
[[package]]
name = "expect-test"
version = "1.5.0"
@@ -350,6 +383,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "home"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
dependencies = [
"windows-sys",
]
[[package]]
name = "humansize"
version = "2.1.3"
@@ -409,6 +451,8 @@ name = "lix"
version = "0.0.0"
dependencies = [
"lix-doc",
"rustyline",
"rustyline-derive",
"zngur",
]
@@ -421,6 +465,12 @@ dependencies = [
"rowan",
]
[[package]]
name = "log"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "memchr"
version = "2.8.2"
@@ -449,6 +499,27 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
@@ -523,6 +594,16 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radix_trie"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "regex-automata"
version = "0.3.9"
@@ -567,6 +648,38 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustyline"
version = "18.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53f6a737db68eb1a8ccff86b584b2fc13eca6a7bb6f78ebc7c529547e3ab9684"
dependencies = [
"bitflags",
"cfg-if",
"clipboard-win",
"home",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"unicode-segmentation",
"unicode-width 0.2.2",
"utf8parse",
"windows-sys",
]
[[package]]
name = "rustyline-derive"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e5587417a3c4e16a4415e8d7d07f80998ed835ade621d19dfbe9fbe3205b0f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde"
version = "1.0.228"
@@ -614,6 +727,12 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "stacker"
version = "0.1.24"
@@ -680,6 +799,12 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "utf8parse"
version = "0.2.2"
+2
View File
@@ -11,6 +11,8 @@ edition = "2021"
[workspace.dependencies]
clap = "4"
rustyline = "18"
rustyline-derive = "0.12"
syn = "2.0"
zngur = "0.10"
+12
View File
@@ -0,0 +1,12 @@
---
synopsis: "REPL now uses rustyline"
cls: [5703]
category: "Improvements"
credits: [horrors]
issues: []
---
The REPL now uses [rustyline](https://github.com/kkawakam/rustyline) for input processing instead
of editline. This comes with some improvements to REPL behavior: wrapping lines no longer confuse
the line editor, unicode is fully supported, pasting multiline expressions is noew possible, even
undo commands are now available! We plan to improve the REPL further using these newfound powers.
@@ -50,10 +50,6 @@ The most current alternative to this section is to read `package.nix` and see wh
- The `boost` library of version 1.66.0 or higher. It can be obtained
from the official web site <https://www.boost.org/>.
- The `editline` library of version 1.14.0 or higher. It can be
obtained from the its repository
<https://github.com/troglobit/editline>.
- Recent versions of Bison and Flex to build the parser. (This is
because Nix needs GLR support in Bison and reentrancy support in
Flex.) For Bison, you need version 2.6, which can be obtained from
+57 -216
View File
@@ -1,251 +1,97 @@
#include "lix/libutil/c-calls.hh"
#include "libutil/fmt.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libutil/logging.hh"
#include <csignal>
#include <cstdio>
#include "lix/lix-rs/main.gen.hh"
#include "lix/lix-rs/utils.hh"
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <cerrno>
// 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>
}
#include "lix/libutil/finally.hh"
#include "lix/libutil/strings.hh"
#include "lix/libcmd/repl-interacter.hh"
namespace rust {
Vec<String> Impl<lix::repl::CxxCompleter, lix::repl::ReplCompleter>::complete(
Ref<lix::repl::CxxCompleter> self, Ref<Str> input
)
try {
auto s = to_std_string(input);
auto result = std::vec::Vec<std::string::String>::new_();
for (auto & possible : self.cpp().completePrefix(s)) {
result.push(rust::to_string(possible));
}
return result;
} catch (...) {
// the completer should have logged anything interesting.
return std::vec::Vec<std::string::String>::new_();
}
}
namespace nix {
namespace {
// Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
volatile sig_atomic_t g_signal_received = 0;
ReadlineLikeInteracter::ReadlineLikeInteracter(std::string historyFile) : historyFile(historyFile) {}
void sigintHandler(int signo)
void ReadlineLikeInteracter::init(detail::ReplCompleterMixin * repl)
{
g_signal_received = signo;
}
static detail::ReplCompleterMixin * curRepl; // ugly
/**
* @return a null-terminated list of completions as expected by `el_print_columns`
*/
char ** copyCompletions(const StringSet& possible)
{
int ac = 0;
char ** vp = nullptr;
auto check = [&](auto * p) {
if (!p) {
if (vp) {
while (--ac >= 0)
free(vp[ac]);
free(static_cast<void *>(vp));
}
throw Error("allocation failure");
}
return p;
};
vp = check(static_cast<char **>(malloc(possible.size() * sizeof(char *))));
for (auto & p : possible)
vp[ac++] = check(strdup(p.c_str()));
return vp;
}
// Instead of using the readline-provided prefix, do our own tokenization
// to avoid the default behavior of treating dots/quotes as word boundaries.
// See the definition of SEPS for what it treats as a boundary:
// https://github.com/troglobit/editline/blob/caf4b3c0ce3b0785791198b11de6f3134e9f05d8/src/editline.c
std::string getLastTokenBeforeCursor()
{
std::string_view line{rl_line_buffer, static_cast<size_t>(rl_point)};
auto tokens = tokenizeString<std::vector<std::string>>(
line,
// Same as editline's SEPS, except for double and single quotes:
"#$&()*:;<=>?[\\]^`{|}~\n\t "
);
if (tokens.empty()) {
return "";
}
return tokens.back();
}
// Sometimes inserting text or listing possible completions has a side effect
// of hiding the text after the cursor (even though it remains in the buffer).
// This helper just refreshes the display while keeping the cursor in place.
//
// Inserting text also sometimes moves the whole buffer down one line, usually
// if the cursor is inside a quoted attr name. I'm not sure why (vs unquoted)
// but it still seems to work pretty well and is just a visual artifact.
el_status_t redisplay()
{
int cursorPos = rl_point;
rl_refresh_line(0, 0);
rl_point = cursorPos;
return (rl_point == rl_end) ? CSstay : CSmove;
}
};
static el_status_t doCompletion() {
auto s = getLastTokenBeforeCursor();
auto possible = curRepl->completePrefix(s);
if (possible.empty()) {
return el_ring_bell();
}
if (possible.size() == 1) {
const auto completion = *possible.cbegin();
if (completion.size() > s.size()) {
rl_insert_text(requireCString(completion.substr(s.size())));
return redisplay();
}
return el_ring_bell();
}
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 = s.size();
size_t len = 0;
while (checkAllHaveSameAt(start + len)) {
++len;
}
if (len > 0) {
auto commonPrefix = possible.begin()->substr(start, len);
rl_insert_text(requireCString(commonPrefix));
el_ring_bell();
return redisplay();
}
char** columns = copyCompletions(possible);
el_print_columns(possible.size(), columns);
return redisplay();
}
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());
}
el_hist_size = 1000;
read_history(requireCString(historyFile));
auto oldRepl = curRepl;
curRepl = repl;
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
// editline does its own escaping of completions, so we rebind tab
// to our own completion function to skip that and do nix escaping
// instead of shell escaping.
el_bind_key(CTL('I'), doCompletion);
return restoreRepl;
auto rl = repl::Rustyline::new_(rust::to_string(historyFile).as_str(), *repl);
match_result(
std::move(rl),
[&](repl::Rustyline ok) { this->rl = std::make_unique<repl::Rustyline>(std::move(ok)); },
[&](rust::Box<rust::Dyn<rust::std::error::Error>> err) {
throw Error("%s", Uncolored(to_std_string(err.to_string())));
}
);
}
static constexpr const char * promptForType(ReplPromptType promptType)
static rust::Ref<rust::Str> promptForType(ReplPromptType promptType)
{
switch (promptType) {
case ReplPromptType::ReplPrompt:
return "nix-repl> ";
return "nix-repl> "_rs;
case ReplPromptType::ContinuationPrompt:
return " ";
return " "_rs;
}
assert(false);
}
bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptType)
{
struct sigaction act, old;
sigset_t savedSignalMask, set;
auto s = rl->ask(promptForType(promptType));
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");
return match_result(
std::move(s),
[&](rust::String ok) {
input += to_std_string(ok);
input += '\n';
return true;
},
[&](rust::rustyline::error::ReadlineError err) {
if (err.matches_Interrupted()) {
input.clear();
return true;
}
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 (err.matches_Eof()) {
return false;
}
if (sigaction(SIGINT, &old, 0))
throw SysError("restoring handler for SIGINT");
};
setupSignals();
char * s = readline(promptForType(promptType)); // NOLINT(lix-unsafe-c-calls)
Finally doFree([&]() { free(s); });
restoreSignals();
if (g_signal_received) {
g_signal_received = 0;
input.clear();
return true;
}
if (!s)
return false;
this->writeHistory();
input += s;
input += '\n';
return true;
throw Error("%s", Uncolored(to_std_string(err.into().to_string())));
}
);
}
void ReadlineLikeInteracter::writeHistory()
{
int ret = write_history(requireCString(historyFile));
int writeHistErr = errno;
if (ret == 0) {
return;
if (rl) {
rl->write_history();
}
// If the open fails, editline returns EOF. If the close fails, editline
// forwards the return value of fclose(), which is EOF on error.
// readline however, returns the errno.
// So if we didn't get exactly EOF, then consider the return value the error
// code; otherwise use the errno we saved above.
// https://github.com/troglobit/editline/issues/66
if (ret != EOF) {
writeHistErr = ret;
}
// In any of these cases, we should explicitly ignore the error, but log
// them so the user isn't confused why their history is getting eaten.
std::string_view const errMsg(std::strerror(writeHistErr));
printTaggedWarning("ignoring error writing repl history to %s: %s", this->historyFile, errMsg);
}
ReadlineLikeInteracter::~ReadlineLikeInteracter()
@@ -253,11 +99,6 @@ ReadlineLikeInteracter::~ReadlineLikeInteracter()
this->writeHistory();
}
AutomationInteracter::Guard AutomationInteracter::init(detail::ReplCompleterMixin *)
{
return Guard([] {});
}
// ASCII ENQ character
constexpr const char * automationPrompt = "\x05";
+11 -11
View File
@@ -1,11 +1,14 @@
#pragma once
/// @file
#include "lix/libutil/finally.hh"
#include "lix/libutil/types.hh"
#include <functional>
#include <memory>
#include <string>
namespace rust::lix::repl {
struct Rustyline;
}
namespace nix {
namespace detail {
@@ -24,9 +27,7 @@ enum class ReplPromptType {
class ReplInteracter
{
public:
using Guard = Finally<std::function<void()>>;
virtual Guard init(detail::ReplCompleterMixin * repl) = 0;
virtual void init(detail::ReplCompleterMixin * repl) {}
/** Returns a boolean of whether the interacter got EOF */
virtual bool getLine(std::string & input, ReplPromptType promptType) = 0;
virtual ~ReplInteracter(){};
@@ -35,12 +36,12 @@ public:
class ReadlineLikeInteracter final : public ReplInteracter
{
std::string historyFile;
std::unique_ptr<rust::lix::repl::Rustyline> rl;
public:
ReadlineLikeInteracter(std::string historyFile)
: historyFile(historyFile)
{
}
virtual Guard init(detail::ReplCompleterMixin * repl) override;
ReadlineLikeInteracter(std::string historyFile);
virtual void init(detail::ReplCompleterMixin * repl) override;
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
/** Writes the current history to the history file.
*
@@ -54,7 +55,6 @@ class AutomationInteracter final : public ReplInteracter
{
public:
AutomationInteracter() = default;
virtual Guard init(detail::ReplCompleterMixin * repl) override;
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
virtual ~AutomationInteracter() override = default;
};
+4 -10
View File
@@ -1,6 +1,5 @@
#include <algorithm>
#include <cstdio>
#include <editline.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
@@ -380,7 +379,7 @@ ReplExitStatus NixRepl::mainLoop()
std::list<ReplLoadable> loadables = std::exchange(loaded, {});
loadFiles(loadables);
auto _guard = interacter->init(static_cast<detail::ReplCompleterMixin *>(this));
interacter->init(static_cast<detail::ReplCompleterMixin *>(this));
/* Stop the progress bar because it interferes with the display of
the repl. */
@@ -441,15 +440,10 @@ StringSet NixRepl::completePrefix(const std::string &prefix)
{
StringSet completions;
// We should only complete colon commands if there's a colon at the beginning,
// but editline (for... whatever reason) doesn't *give* us the colon in the
// completion callback. If the user types :rel<TAB>, `prefix` will only be `rel`.
// Luckily, editline provides a global variable for its current buffer, so we can
// check for the presence of a colon there.
if (rl_line_buffer != nullptr && rl_line_buffer[0] == ':') {
if (prefix.starts_with(':')) {
for (auto const & [colonCmd, cmd] : registeredCommands) {
if ((!cmd->attributes.debugModeOnly || inDebugger()) && colonCmd.starts_with(prefix)) {
completions.insert(std::string(colonCmd));
if ((!cmd->attributes.debugModeOnly || inDebugger()) && colonCmd.starts_with(prefix.substr(1))) {
completions.insert(":" + colonCmd);
}
}
@@ -2,4 +2,4 @@
name: repl-automation
internalName: ReplAutomation
---
Makes the repl not use editline, print ENQ (U+0005) when ready for a command, and take commands followed by newline.
Makes the repl not use a line editor, print ENQ (U+0005) when ready for a command, and take commands followed by newline.
+2
View File
@@ -12,6 +12,8 @@ default = []
[dependencies]
lix-doc.path = "../lix-doc"
rustyline.workspace = true
rustyline-derive.workspace = true
[build-dependencies]
zngur.workspace = true
+1
View File
@@ -3,6 +3,7 @@ lix_rs_files += files(
# keep-sorted start
'src/ffi_test.rs',
'src/lib.rs',
'src/repl.rs',
# keep-sorted end
)
+1
View File
@@ -146,3 +146,4 @@ pub(crate) mod log {
}
mod ffi_test;
mod repl;
+1
View File
@@ -1,4 +1,5 @@
merge "./ffi_test.zng";
merge "./repl.zng";
mod crate::ffi {
use ::std::result::Result as Result;
+94
View File
@@ -0,0 +1,94 @@
use std::{
error::Error,
path::{Path, PathBuf},
};
use rustyline::{
completion::{extract_word, Completer},
error::ReadlineError,
history::FileHistory,
CompletionType, Config, Editor,
};
use rustyline_derive::*;
pub(crate) use crate::generated::cpp::CxxCompleter;
pub(crate) trait ReplCompleter {
fn complete(&self, input: &str) -> Vec<String>;
}
#[derive(Helper, Validator, Hinter, Highlighter)]
struct Helper<'a> {
cxx: &'a CxxCompleter,
}
impl<'a> Completer for Helper<'a> {
type Candidate = String;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
let line = &line[0..pos];
if line == "" {
Ok((0, vec![]))
} else {
let (start, token) = if line.starts_with(':') {
(0, line)
} else {
// Same as editline's SEPS, except for double and single quotes:
extract_word(line, pos, None, |c| {
"#$&()*:;<=>?[\\]^`{,}~\n\t ".contains(c)
})
};
Ok((start, self.cxx.complete(token)))
}
}
}
#[derive(Debug)]
pub struct Rustyline<'a> {
editor: Editor<Helper<'a>, FileHistory>,
history_file: PathBuf,
}
impl<'a> Rustyline<'a> {
pub fn new(
history_file: impl AsRef<Path>,
completer: &'a CxxCompleter,
) -> Result<Self, Box<dyn Error>> {
let history_file = history_file.as_ref();
// TODO add settings and stuff
let config = Config::builder()
.auto_add_history(true)
.max_history_size(1000)?
.history_ignore_dups(true)?
.completion_type(CompletionType::List)
.build();
let history = FileHistory::with_config(&config);
let mut editor = Editor::with_history(config, history)?;
editor.set_helper(Some(Helper { cxx: completer }));
// ignore history load errors for historical reasons
// TODO stop that some day
let _ = editor.load_history(history_file);
Ok(Rustyline {
editor,
history_file: history_file.to_path_buf(),
})
}
pub fn ask(&mut self, prompt: &str) -> Result<String, ReadlineError> {
self.editor.readline(prompt)
}
pub fn write_history(&mut self) {
if let Err(e) = self.editor.save_history(&self.history_file) {
log_warning!("failed to write history file: {}", e);
}
}
}
+61
View File
@@ -0,0 +1,61 @@
mod crate::repl {
use ::std::string::String as String;
use ::std::vec::Vec as Vec;
use ::std::error::Error as Error;
use ::std::result::Result as Result;
use ::rustyline::error::ReadlineError as ReadlineError;
#cpp_additional_includes "
namespace nix::detail {
struct ReplCompleterMixin;
}
"
type CxxCompleter {
#cpp_ref "::nix::detail::ReplCompleterMixin";
}
extern "C++" {
impl ReplCompleter for CxxCompleter {
safe fn complete(&self, &str) -> Vec<String>;
}
}
type ReadlineError {
#layout_conservative(size = 16, align = 8);
constructor Eof;
constructor Interrupted;
constructor Io(::std::io::Error);
fn into(self) -> Box<dyn Error>;
}
type Rustyline {
#heap_allocated;
fn new(&str, &CxxCompleter) -> Result<Rustyline, Box<dyn Error>>;
fn ask(&mut self, &str) -> Result<String, ReadlineError>;
fn write_history(&mut self);
}
type Result<Rustyline, Box<dyn Error>> {
#heap_allocated;
constructor Ok(Rustyline);
constructor Err(Box<dyn Error>);
fn unwrap(self) -> Rustyline;
fn unwrap_err(self) -> Box<dyn Error>;
}
type Result<String, ReadlineError> {
#layout_conservative(size = 24, align = 8);
constructor Ok(String);
constructor Err(ReadlineError);
fn unwrap(self) -> String;
fn unwrap_err(self) -> ReadlineError;
}
}
+1 -1
View File
@@ -7,6 +7,6 @@ Description: Lix Package Manager (libraries)
Version: @PACKAGE_VERSION@
# dependencies on boost, libarchive, nlohmann_json are omitted since they are optional (only required by some headers)
Requires: lix-base kj-async @BOEHM_IF_FOUND@
Requires.private: @AWS_SDK_IF_FOUND@ @BOEHM_IF_FOUND@ @CPUID_IF_FOUND@ @SECCOMP_IF_FOUND@ libeditline lowdown ncurses libbrotlidec libbrotlienc libarchive libcrypto capnp-rpc libcurl sqlite3
Requires.private: @AWS_SDK_IF_FOUND@ @BOEHM_IF_FOUND@ @CPUID_IF_FOUND@ @SECCOMP_IF_FOUND@ lowdown libbrotlidec libbrotlienc libarchive libcrypto capnp-rpc libcurl sqlite3
Cflags: -I${includedir}
Libs: -L${libdir} -llix
-2
View File
@@ -139,12 +139,10 @@ dependencies = [
capnp_rpc,
cpuid,
curl,
editline,
kj,
libarchive,
libatomic,
lowdown,
ncurses,
nlohmann_json,
openssl,
seccomp,
-3
View File
@@ -379,7 +379,6 @@ brotli = [
]
openssl = dependency('libcrypto', 'openssl', required : true, include_type : 'system')
ncurses = dependency('ncurses', required : true, include_type : 'system')
# FIXME: confirm we actually support such old versions of aws-sdk-cpp
aws_sdk = dependency('aws-cpp-sdk-core', required : false, version : '>=1.8', include_type : 'system')
@@ -432,8 +431,6 @@ sqlite = dependency('sqlite3', 'sqlite', version : '>=3.6.19', required : true,
curl = dependency('libcurl', 'curl', required : true, include_type : 'system')
editline = dependency('libeditline', 'editline', version : '>=1.14', required : true, include_type : 'system')
lowdown = dependency('lowdown', version : '>=1.4.0', required : true, include_type : 'system')
# TODO(sterni): drop the corresponding #ifdef after NixOS 25.11 is EOL which still distributes lowdown < 3.0.0
-18
View File
@@ -24,8 +24,6 @@
curl-lix ? __forDefaults.curl-lix,
curl,
doxygen,
editline-lix ? __forDefaults.editline-lix,
editline,
git,
gtest,
jq,
@@ -45,7 +43,6 @@
mercurial,
meson,
ninja,
ncurses,
openssl,
passt,
pegtl,
@@ -132,19 +129,6 @@
);
});
editline-lix = editline.overrideAttrs (prev: {
configureFlags = (prev.configureFlags or [ ]) ++ [
# Enable SIGSTOP (Ctrl-Z) behavior.
(lib.enableFeature true "sigstop")
# Enable ANSI arrow keys.
(lib.enableFeature true "arrow-keys")
# Use termcap library to query terminal size.
(lib.enableFeature true "termcap")
];
propagatedBuildInputs = (prev.propagatedBuildInputs or [ ]) ++ [ ncurses ];
});
# Avoid a bunch of build closure of the tracer, we just need the dtrace
# generator.
systemtap-lix = buildPackages.linuxPackages.systemtap.override { withStap = false; };
@@ -548,7 +532,6 @@ stdenv.mkDerivation (finalAttrs: {
bzip2
xz
brotli
editline-lix
openssl
sqlite
libarchive
@@ -757,7 +740,6 @@ stdenv.mkDerivation (finalAttrs: {
inherit (__forDefaults)
boehmgc-nix
curl-lix
editline-lix
build-release-notes
pegtl
;
@@ -4,7 +4,6 @@ libstoreconsumer_tester = executable(
dependencies : [
libasanoptions,
liblix,
editline,
boost,
lowdown,
kj,