From 010d93393e7dd978927f1c805c9b0c64e96b0ecc Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 31 May 2024 18:29:10 -0600 Subject: [PATCH] repl: implement tab completing :colon commands This uses a minor hack in which we check the rl_line_buffer global variable to workaround editline not including the colon in its completion callback. Fixes #361 Change-Id: Id159d209c537443ef5e37a975982e8e12ce1f486 --- doc/manual/rl-next/repl-complete-colon.md | 8 +++ src/libcmd/repl.cc | 63 +++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 doc/manual/rl-next/repl-complete-colon.md diff --git a/doc/manual/rl-next/repl-complete-colon.md b/doc/manual/rl-next/repl-complete-colon.md new file mode 100644 index 000000000..19733575b --- /dev/null +++ b/doc/manual/rl-next/repl-complete-colon.md @@ -0,0 +1,8 @@ +--- +synopsis: "`nix repl` now allows tab-completing the special repl :colon commands" +cls: 1367 +credits: Qyriad +category: Improvements +--- + +The REPL (`nix repl`) supports pressing `` to complete a partial expression, but now also supports completing the special :colon commands as well (`:b`, `:edit`, `:doc`, etc), if the line starts with a colon. diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index 49865aa90..24ab8acb9 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -1,7 +1,9 @@ +#include #include #include #include #include +#include #include "box_ptr.hh" #include "repl-interacter.hh" @@ -79,6 +81,8 @@ enum class ProcessLineResult { PromptAgain, }; +using namespace std::literals::string_view_literals; + struct NixRepl : AbstractNixRepl , detail::ReplCompleterMixin @@ -86,6 +90,35 @@ struct NixRepl , gc #endif { + /* clang-format: off */ + static constexpr std::array COMMANDS = { + "add"sv, "a"sv, + "load"sv, "l"sv, + "load-flake"sv, "lf"sv, + "reload"sv, "r"sv, + "edit"sv, "e"sv, + "t"sv, + "u"sv, + "b"sv, + "bl"sv, + "i"sv, + "sh"sv, + "log"sv, + "print"sv, "p"sv, + "quit"sv, "q"sv, + "doc"sv, + "te"sv, + }; + + static constexpr std::array DEBUG_COMMANDS = { + "env"sv, + "bt"sv, "backtrace"sv, + "st"sv, + "c"sv, "continue"sv, + "s"sv, "step"sv, + }; + /* clang-format: on */ + size_t debugTraceIndex; Strings loadedFiles; @@ -323,6 +356,36 @@ 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, `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] == ':') { + for (auto const & colonCmd : this->COMMANDS) { + if (colonCmd.starts_with(prefix)) { + completions.insert(std::string(colonCmd)); + } + } + + if (state->debugRepl) { + for (auto const & colonCmd : this->DEBUG_COMMANDS) { + if (colonCmd.starts_with(prefix)) { + completions.insert(std::string(colonCmd)); + } + } + } + + // If there were : command completions, then we should only return those, + // because otherwise this is not valid Nix syntax. + // However if we didn't get any completions, then this could be something + // like `:b pkgs.hel`, in which case we should do expression completion + // as normal. + if (!completions.empty()) { + return completions; + } + } + size_t start = prefix.find_last_of(" \n\r\t(){}[]"); std::string prev, cur; if (start == std::string::npos) {