diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index b0f80db32..06b472d8b 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -93,4 +93,36 @@ Value * findAlongAttrPath(EvalState & state, const string & attrPath, } +Pos findDerivationFilename(EvalState & state, Value & v, std::string what) +{ + Value * v2; + try { + auto dummyArgs = state.allocBindings(0); + v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v); + } catch (Error &) { + throw Error("package '%s' has no source location information", what); + } + + // FIXME: is it possible to extract the Pos object instead of doing this + // toString + parsing? + auto pos = state.forceString(*v2); + + auto colon = pos.rfind(':'); + if (colon == std::string::npos) + throw Error("cannot parse meta.position attribute '%s'", pos); + + std::string filename(pos, 0, colon); + unsigned int lineno; + try { + lineno = std::stoi(std::string(pos, colon + 1)); + } catch (std::invalid_argument & e) { + throw Error("cannot parse line number '%s'", pos); + } + + Symbol file = state.symbols.create(filename); + + return { file, lineno, 0 }; +} + + } diff --git a/src/libexpr/attr-path.hh b/src/libexpr/attr-path.hh index 46a341950..716e5ba27 100644 --- a/src/libexpr/attr-path.hh +++ b/src/libexpr/attr-path.hh @@ -10,4 +10,7 @@ namespace nix { Value * findAlongAttrPath(EvalState & state, const string & attrPath, Bindings & autoArgs, Value & vIn); +/* Heuristic to find the filename and lineno or a nix value. */ +Pos findDerivationFilename(EvalState & state, Value & v, std::string what); + } diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 7af2a1bf7..b7baad375 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -178,6 +178,19 @@ Strings argvToStrings(int argc, char * * argv) return args; } +Strings editorFor(Pos pos) +{ + auto editor = getEnv("EDITOR", "cat"); + auto args = tokenizeString(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; +} + std::string renderLabels(const Strings & labels) { std::string res; diff --git a/src/libutil/args.hh b/src/libutil/args.hh index ad5fcca39..1e29bd4fa 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -5,6 +5,7 @@ #include #include "util.hh" +#include "nixexpr.hh" namespace nix { @@ -190,6 +191,9 @@ public: Strings argvToStrings(int argc, char * * argv); +/* Helper function to generate args that invoke $EDITOR on filename:lineno */ +Strings editorFor(Pos pos); + /* Helper function for rendering argument labels. */ std::string renderLabels(const Strings & labels); diff --git a/src/nix/edit.cc b/src/nix/edit.cc index a6169f118..553765f13 100644 --- a/src/nix/edit.cc +++ b/src/nix/edit.cc @@ -36,45 +36,17 @@ struct CmdEdit : InstallableCommand auto v = installable->toValue(*state); - Value * v2; - try { - auto dummyArgs = state->allocBindings(0); - v2 = findAlongAttrPath(*state, "meta.position", *dummyArgs, *v); - } catch (Error &) { - throw Error("package '%s' has no source location information", installable->what()); - } - - auto pos = state->forceString(*v2); - debug("position is %s", pos); - - auto colon = pos.rfind(':'); - if (colon == std::string::npos) - throw Error("cannot parse meta.position attribute '%s'", pos); - - std::string filename(pos, 0, colon); - int lineno; - try { - lineno = std::stoi(std::string(pos, colon + 1)); - } catch (std::invalid_argument & e) { - throw Error("cannot parse line number '%s'", pos); - } - - auto editor = getEnv("EDITOR", "cat"); - - auto args = tokenizeString(editor); - - if (editor.find("emacs") != std::string::npos || - editor.find("nano") != std::string::npos || - editor.find("vim") != std::string::npos) - args.push_back(fmt("+%d", lineno)); - - args.push_back(filename); + Pos pos = findDerivationFilename(*state, *v, installable->what()); stopProgressBar(); + auto args = editorFor(pos); + execvp(args.front().c_str(), stringsToCharPtrs(args).data()); - throw SysError("cannot run editor '%s'", editor); + std::string command; + for (const auto &arg : args) command += " '" + arg + "'"; + throw SysError("cannot run command%s", command); } }; diff --git a/src/nix/repl.cc b/src/nix/repl.cc index f857b2e89..35c7aec66 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -22,6 +22,7 @@ extern "C" { #include "shared.hh" #include "eval.hh" #include "eval-inline.hh" +#include "attr-path.hh" #include "store-api.hh" #include "common-eval-args.hh" #include "get-drvs.hh" @@ -440,6 +441,7 @@ bool NixRepl::processLine(string line) << " = Bind expression to variable\n" << " :a Add attributes from resulting set to scope\n" << " :b Build derivation\n" + << " :e Open the derivation in $EDITOR\n" << " :i Build derivation, then install result into current profile\n" << " :l Load Nix expression and add it to scope\n" << " :p Evaluate and print expression recursively\n" @@ -466,6 +468,34 @@ bool NixRepl::processLine(string line) reloadFiles(); } + else if (command == ":e" || command == ":edit") { + Value v; + evalString(arg, v); + + Pos pos; + + if (v.type == tPath || v.type == tString) { + PathSet context; + auto filename = state.coerceToString(noPos, v, context); + pos.file = state.symbols.create(filename); + } else if (v.type == tLambda) { + pos = v.lambda.fun->pos; + } else { + // assume it's a derivation + pos = findDerivationFilename(state, v, arg); + } + + // Open in EDITOR + auto args = editorFor(pos); + auto editor = args.front(); + args.pop_front(); + runProgram(editor, args); + + // Reload right after exiting the editor + state.resetFileCache(); + reloadFiles(); + } + else if (command == ":t") { Value v; evalString(arg, v);