Skip to content

Commit

Permalink
Add plugin command support for nix repl
Browse files Browse the repository at this point in the history
- Refactor the repl core into libexpr
- Use dependency injection via std::function to provide the completion
  functions from editline so we don't introduce extra dependencies for
  libexpr
- Add a RegisterReplCmd analogous to RegisterPrimOp for repl commands
- Refactor: get rid of the "ugly" global curRepl and replace it with a
  trick with closures on the nix side (that are effectively globals, but
  unique per repl user and thus not as ugly ;p)
- Rip out readline support since there appears to be no build system
  support for it and it is thus dead code
- Integration test this new plugin functionality
  • Loading branch information
lf- committed Aug 15, 2020
1 parent 13e49be commit 5cd2e0e
Show file tree
Hide file tree
Showing 12 changed files with 844 additions and 691 deletions.
625 changes: 625 additions & 0 deletions src/libexpr/repl.cc

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions src/libexpr/repl.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "eval.hh"
#include <vector>
#include <functional>

#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
#include <gc/gc_cpp.h>
#endif

namespace nix {

struct NixRepl
#if HAVE_BOEHMGC
: gc
#endif
{
struct CompletionFunctions {
std::function<int (const char * filename)> writeHistory;
std::function<char * (const char * prompt)> readline;
};

string curDir;
std::unique_ptr<EvalState> state;
Bindings * autoArgs;

Strings loadedFiles;

const static int envSize = 32768;
StaticEnv staticEnv;
Env * env;
int displ;
StringSet varNames;

const Path historyFile;
CompletionFunctions completionFunctions;

NixRepl(const Strings & searchPath, nix::ref<Store> store,
CompletionFunctions completionFunctions);
~NixRepl();
void mainLoop(const std::vector<std::string> & files);
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);
void initEnv();
void reloadFiles();
void addAttrsToScope(Value & attrs);
void addVarToScope(const Symbol & name, Value & v);
Expr * parseString(string s);
void evalString(string s, Value & v);

static string removeWhitespace(string s);

typedef set<Value *> ValuesSeen;
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
};

using ReplCmdFun = std::function<void (string name, string arg)>;

/**
* A registry for extending the REPL commands list.
*/
struct RegisterReplCmd
{
struct ReplCmd
{
/**
* Names of the commands this matches, not prefixed by :. The first one
* is displayed in help.
*/
vector<string> names;
/**
* Argument placeholder, for example, "<expr>".
*/
string argPlaceholder;
/**
* Help message displayed in :?.
*/
string help;
/**
* Callback.
*/
ReplCmdFun cmd;
};

using ReplCmds = vector<ReplCmd>;

static ReplCmds * replCmds;

RegisterReplCmd(
vector<string> names,
string help,
ReplCmdFun cmd,
string argPlaceholder = ""
);
};
}
30 changes: 30 additions & 0 deletions src/libutil/callable.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once
// from https://stackoverflow.com/a/48368508

#include <type_traits>
#include <functional>

template <class F>
struct lambda_traits : lambda_traits<decltype(&F::operator())>
{ };

template <typename F, typename R, typename... Args>
struct lambda_traits<R(F::*)(Args...)> : lambda_traits<R(F::*)(Args...) const>
{ };

template <class F, class R, class... Args>
struct lambda_traits<R(F::*)(Args...) const> {
using pointer = typename std::add_pointer<R(Args...)>::type;

static pointer cify(F&& f) {
static F fn = std::forward<F>(f);
return [](Args... args) {
return fn(std::forward<Args>(args)...);
};
}
};

template <class F>
inline typename lambda_traits<F>::pointer cify(F&& f) {
return lambda_traits<F>::cify(std::forward<F>(f));
}
2 changes: 1 addition & 1 deletion src/libutil/ref.hh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace nix {

/* A simple non-nullable reference-counted pointer. Actually a wrapper
around std::shared_ptr that prevents non-null constructions. */
around std::shared_ptr that prevents null constructions. */
template<typename T>
class ref
{
Expand Down
12 changes: 12 additions & 0 deletions src/libutil/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,18 @@ Path getDataDir()
return dataDir ? *dataDir : getHome() + "/.local/share";
}

Strings editorFor(const Path & file, unsigned int line)
{
auto editor = getEnv("EDITOR").value_or("cat");
auto args = tokenizeString<Strings>(editor);
if (line > 0 && (
editor.find("emacs") != std::string::npos ||
editor.find("nano") != std::string::npos ||
editor.find("vim") != std::string::npos))
args.push_back(fmt("+%d", line));
args.push_back(file);
return args;
}

Paths createDirs(const Path & path)
{
Expand Down
4 changes: 4 additions & 0 deletions src/libutil/util.hh
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ std::vector<Path> getConfigDirs();
/* Return $XDG_DATA_HOME or $HOME/.local/share. */
Path getDataDir();

/* Helper function to generate args that invoke $EDITOR on
filename:lineno. */
Strings editorFor(const Path & file, unsigned int line);

/* Create a directory and all its parents, if necessary. Returns the
list of created directories, in order of creation. */
Paths createDirs(const Path & path);
Expand Down
13 changes: 0 additions & 13 deletions src/nix/command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,6 @@ void StorePathCommand::run(ref<Store> store)
run(store, *storePaths.begin());
}

Strings editorFor(const Pos & pos)
{
auto editor = getEnv("EDITOR").value_or("cat");
auto args = tokenizeString<Strings>(editor);
if (pos.line > 0 && (
editor.find("emacs") != std::string::npos ||
editor.find("nano") != std::string::npos ||
editor.find("vim") != std::string::npos))
args.push_back(fmt("+%d", pos.line));
args.push_back(pos.file);
return args;
}

MixProfile::MixProfile()
{
addFlag({
Expand Down
4 changes: 0 additions & 4 deletions src/nix/command.hh
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,6 @@ std::set<StorePath> toDerivations(ref<Store> store,
std::vector<std::shared_ptr<Installable>> installables,
bool useDeriver = false);

/* Helper function to generate args that invoke $EDITOR on
filename:lineno. */
Strings editorFor(const Pos & pos);

struct MixProfile : virtual StoreCommand
{
std::optional<Path> profile;
Expand Down
2 changes: 1 addition & 1 deletion src/nix/edit.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct CmdEdit : InstallableCommand

stopProgressBar();

auto args = editorFor(pos);
auto args = editorFor(pos.file, pos.line);

restoreSignals();
execvp(args.front().c_str(), stringsToCharPtrs(args).data());
Expand Down
Loading

0 comments on commit 5cd2e0e

Please sign in to comment.