diff --git a/nixd/lib/Controller/Completion.cpp b/nixd/lib/Controller/Completion.cpp index efce25955..82939b964 100644 --- a/nixd/lib/Controller/Completion.cpp +++ b/nixd/lib/Controller/Completion.cpp @@ -12,6 +12,8 @@ #include +#include +#include #include #include #include @@ -250,6 +252,43 @@ class OptionCompletionProvider { } }; +void completeExprPath(const std::string &CurFilePath, + const nixf::ExprPath &ExprPath, + std::vector &Items) { + using namespace llvm::sys; + + if (!ExprPath.parts().isLiteral()) { + return; + } + + const auto &PathLiteral = ExprPath.parts().literal(); + if (PathLiteral.empty()) { + return; + } + + llvm::SmallVector Path{PathLiteral.begin(), PathLiteral.end()}; + if (PathLiteral[0] == '.') { + const auto &CurFileDir = path::parent_path(CurFilePath); + fs::make_absolute(CurFileDir, Path); + } + + if (fs::exists(Path) && fs::is_directory(Path)) { + std::error_code EC; + for (auto Iter = fs::directory_iterator(Path, EC, false); + Iter != fs::directory_iterator(); Iter.increment(EC)) { + if (EC) { + vlog("failed to read directory: {0}", EC.message()); + break; + } + addItem(Items, CompletionItem{ + .label = path::filename(Iter->path()).str(), + .kind = CompletionItemKind::File, + .data = ExprPath.parts().literal(), + }); + } + } +} + void completeAttrName(const std::vector &Scope, const std::string &Prefix, Controller::OptionMapTy &Options, bool CompletionSnippets, @@ -285,11 +324,12 @@ void Controller::onCompletion(const CompletionParams &Params, } CompletionList List; try { + bool FinishedCompletion = false; const ParentMapAnalysis &PM = *TU->parentMap(); + + // if we are in an attribute path, use AttrCompletionProvider only std::vector Scope; - using PathResult = FindAttrPathResult; - auto R = findAttrPath(*Desc, PM, Scope); - if (R == PathResult::OK) { + if (findAttrPath(*Desc, PM, Scope) == FindAttrPathResult::OK) { // Construct request. std::string Prefix = Scope.back(); Scope.pop_back(); @@ -298,7 +338,23 @@ void Controller::onCompletion(const CompletionParams &Params, completeAttrName(Scope, Prefix, Options, ClientCaps.CompletionSnippets, List.items); } - } else { + FinishedCompletion = true; + } + + // if we are in a literal path, use PathCompletionProvider only + if (!FinishedCompletion) { + const auto *Parent = PM.upExpr(*Desc); + if (Parent->kind() == Node::NK_ExprPath) { + const auto &Path = static_cast(*Parent); + if (Path.parts().isLiteral()) { + completeExprPath(File, Path, List.items); + FinishedCompletion = true; + } + } + } + + // otherwise, fallback to VLACompletionProvider + if (!FinishedCompletion) { const VariableLookupAnalysis &VLA = *TU->variableLookup(); VLACompletionProvider VLAP(VLA); VLAP.complete(*Desc, List.items, PM); diff --git a/nixd/tools/nixd/test/completion-path-root/foo/bar.nix b/nixd/tools/nixd/test/completion-path-root/foo/bar.nix new file mode 100644 index 000000000..e69de29bb diff --git a/nixd/tools/nixd/test/completion-path-root/main.nix b/nixd/tools/nixd/test/completion-path-root/main.nix new file mode 100644 index 000000000..e69de29bb diff --git a/nixd/tools/nixd/test/completion-path.md b/nixd/tools/nixd/test/completion-path.md new file mode 100644 index 000000000..0be2062bc --- /dev/null +++ b/nixd/tools/nixd/test/completion-path.md @@ -0,0 +1,85 @@ +# RUN: sed "s|ROOT|%S|g" < %s | nixd --lit-test | FileCheck %s + +<-- initialize(0) + +```json +{ + "jsonrpc":"2.0", + "id":0, + "method":"initialize", + "params":{ + "processId":123, + "rootPath":"", + "capabilities": { + }, + "trace":"off" + } +} +``` + + +<-- textDocument/didOpen + + +```json +{ + "jsonrpc":"2.0", + "method":"textDocument/didOpen", + "params":{ + "textDocument":{ + "uri":"file://ROOT/completion-path-root/main.nix", + "languageId":"nix", + "version":1, + "text":"{ bar = ./ }" + } + } +} +``` + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "textDocument/completion", + "params": { + "textDocument": { + "uri": "file://ROOT/completion-path-root/main.nix" + }, + "position": { + "line": 0, + "character": 9 + }, + "context": { + "triggerKind": 1, + "triggerCharacter": "/" + } + } +} +``` + +``` + CHECK: "id": 1, +CHECK-NEXT: "jsonrpc": "2.0", +CHECK-NEXT: "result": { +CHECK-NEXT: "isIncomplete": false, +CHECK-NEXT: "items": [ +CHECK-NEXT: { +CHECK-NEXT: "data": "./", +CHECK-NEXT: "kind": 17, +CHECK-NEXT: "label": "foo", +CHECK-NEXT: "score": 0 +CHECK-NEXT: }, +CHECK-NEXT: { +CHECK-NEXT: "data": "./", +CHECK-NEXT: "kind": 17, +CHECK-NEXT: "label": "main.nix", +CHECK-NEXT: "score": 0 +CHECK-NEXT: } +CHECK-NEXT: ] +CHECK-NEXT: } +``` + + +```json +{"jsonrpc":"2.0","method":"exit"} +```