diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc
index b0f80db32a884eee2cced5f753b769c77956cd34..06b472d8bfaf0ff35a8225e90df19ad9b1e63e39 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 46a34195093914d89e686cde50fb2daea57fd556..716e5ba274641f826a9a657bb6563b6116a8c1df 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 7af2a1bf731a76eb74a73ad724fa8966b1af5a74..b7baad37536af21c64fd64e5d6d9032c1813b2b6 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<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;
+}
+
 std::string renderLabels(const Strings & labels)
 {
     std::string res;
diff --git a/src/libutil/args.hh b/src/libutil/args.hh
index ad5fcca394186f9e2c39d5e2bb8fb838b47796ca..1e29bd4fae112add76dfe142f7bf4fca469cff7c 100644
--- a/src/libutil/args.hh
+++ b/src/libutil/args.hh
@@ -5,6 +5,7 @@
 #include <memory>
 
 #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 a6169f1183cd06e277cdfc0795d143b6668ddb84..553765f137a008046e50228e9123c656440ec07e 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<Strings>(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 f857b2e89c2982863e5bea1a2614be88dcdd3110..35c7aec6678a0a87dfaea810cf041b4486703629 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)
              << "  <x> = <expr>  Bind expression to variable\n"
              << "  :a <expr>     Add attributes from resulting set to scope\n"
              << "  :b <expr>     Build derivation\n"
+             << "  :e <expr>     Open the derivation in $EDITOR\n"
              << "  :i <expr>     Build derivation, then install result into current profile\n"
              << "  :l <path>     Load Nix expression and add it to scope\n"
              << "  :p <expr>     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);