diff --git a/nixd/include/nixd/AST/ParseAST.h b/nixd/include/nixd/AST/ParseAST.h index 2590cb60d..6bf9d1be3 100644 --- a/nixd/include/nixd/AST/ParseAST.h +++ b/nixd/include/nixd/AST/ParseAST.h @@ -212,14 +212,6 @@ class ParseAST { [[nodiscard]] Links documentLink(const std::string &File) const; - // Completion - - [[nodiscard]] lspserver::CompletionItem - toCompletionItem(const nix::Symbol &V) const; - - [[nodiscard]] std::vector - completion(const lspserver::Position &Pos) const; - [[nodiscard]] LocationContext getContext(lspserver::Position Pos) const; }; } // namespace nixd diff --git a/nixd/include/nixd/Sema/CompletionBuilder.h b/nixd/include/nixd/Sema/CompletionBuilder.h new file mode 100644 index 000000000..55ec1c28a --- /dev/null +++ b/nixd/include/nixd/Sema/CompletionBuilder.h @@ -0,0 +1,49 @@ +#pragma once + +#include "nixd/AST/EvalAST.h" +#include "nixd/AST/ParseAST.h" + +#include "lspserver/Protocol.h" + +#include + +namespace nixd { + +using CompletionResult = lspserver::CompletionList; + +class CompletionBuilder { + CompletionResult Result; + size_t Limit = 1000; + +public: + /// { a = 1; b = 2; }.| + /// ^ "a", "b" + void addAttrFields(const EvalAST &AST, const lspserver::Position &Pos, + nix::EvalState &State); + + /// Symbols from let-bindings, rec-attrs. + void addSymbols(const ParseAST &AST, const nix::Expr *Node); + + /// + /// ( + /// { foo ? 1 # <-- Lambda formal + /// , bar ? 2 + /// }: foo + bar) { | } + /// ^ "foo", "bar" + void addLambdaFormals(const EvalAST &AST, nix::EvalState &State, + const nix::Expr *Node); + + /// with pkgs; [ ] + void addEnv(nix::EvalState &State, nix::Env &NixEnv); + + void addEnv(const EvalAST &AST, nix::EvalState &State, const nix::Expr *Node); + + /// builtins.* + void addStaticEnv(const nix::SymbolTable &STable, const nix::StaticEnv &SEnv); + + CompletionResult &getResult() { return Result; } + + void setLimit(size_t Limit) { this->Limit = Limit; } +}; + +} // namespace nixd diff --git a/nixd/include/nixd/Support/CompletionHelper.h b/nixd/include/nixd/Support/CompletionHelper.h deleted file mode 100644 index 848f63c24..000000000 --- a/nixd/include/nixd/Support/CompletionHelper.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "lspserver/Protocol.h" - -#include - -#include - -namespace nixd { - -struct CompletionHelper { - using Items = std::vector; - static void fromEnv(nix::EvalState &State, nix::Env &NixEnv, Items &Items); - static void fromStaticEnv(const nix::SymbolTable &STable, - const nix::StaticEnv &SEnv, Items &Items); -}; - -} // namespace nixd diff --git a/nixd/lib/AST/ParseAST.cpp b/nixd/lib/AST/ParseAST.cpp index 616edcc74..548dbc2e5 100644 --- a/nixd/lib/AST/ParseAST.cpp +++ b/nixd/lib/AST/ParseAST.cpp @@ -279,29 +279,6 @@ ParseAST::Links ParseAST::documentLink(const std::string &File) const { return V.Result; } -lspserver::CompletionItem -ParseAST::toCompletionItem(const nix::Symbol &V) const { - lspserver::CompletionItem R; - R.kind = lspserver::CompletionItemKind::Interface; - R.label = symbols()[V]; - return R; -} - -std::vector -ParseAST::completion(const lspserver::Position &Pos) const { - std::vector Items; - if (const auto *Node = lookupContainMin(Pos)) { - std::vector Symbols; - collectSymbols(Node, Symbols); - - // Insert symbols to our completion list. - std::transform( - Symbols.begin(), Symbols.end(), std::back_inserter(Items), - [this](const nix::Symbol &V) { return toCompletionItem(V); }); - }; - return Items; -} - namespace { struct AttrDefVisitor : RecursiveASTVisitor { const ParseAST &AST; diff --git a/nixd/lib/Sema/CompletionBuilder.cpp b/nixd/lib/Sema/CompletionBuilder.cpp new file mode 100644 index 000000000..67da84115 --- /dev/null +++ b/nixd/lib/Sema/CompletionBuilder.cpp @@ -0,0 +1,134 @@ +#include "nixd/Sema/CompletionBuilder.h" +#include "nixd/AST/ParseAST.h" +#include "nixd/Nix/Eval.h" + +#include "lspserver/Protocol.h" + +#include + +namespace nixd { + +using lspserver::CompletionItem; +using lspserver::CompletionItemKind; + +void CompletionBuilder::addAttrFields(const EvalAST &AST, + const lspserver::Position &Pos, + nix::EvalState &State) { + try { + if (const auto *Node = AST.lookupEnd(Pos)) { + auto Value = AST.getValueEval(Node, State); + if (Value.type() == nix::ValueType::nAttrs) { + // Traverse attribute bindings + for (auto Binding : *Value.attrs) { + CompletionItem R; + R.label = State.symbols[Binding.name]; + R.kind = CompletionItemKind::Field; + Result.items.emplace_back(std::move(R)); + if (Result.items.size() > Limit) { + Result.isIncomplete = true; + break; + } + } + } + } + } catch (std::out_of_range &) { + } +} + +void CompletionBuilder::addSymbols(const ParseAST &AST, const nix::Expr *Node) { + std::vector Symbols; + AST.collectSymbols(Node, Symbols); + // Insert symbols to our completion list. + std::transform(Symbols.begin(), Symbols.end(), + std::back_inserter(Result.items), [&](const nix::Symbol &V) { + lspserver::CompletionItem R; + R.kind = CompletionItemKind::Interface; + R.label = AST.symbols()[V]; + return R; + }); +} + +void CompletionBuilder::addLambdaFormals(const EvalAST &AST, + nix::EvalState &State, + const nix::Expr *Node) { + + // Nix only accepts attr set formals + if (!dynamic_cast(Node)) + return; + + try { + const auto *Parent = AST.parent(Node); + if (const auto *SomeExprCall = + dynamic_cast(Parent)) { + auto Value = AST.getValueEval(SomeExprCall->fun, State); + if (!Value.isLambda()) + return; + auto *Fun = Value.lambda.fun; + if (!Fun->hasFormals()) + return; + for (auto Formal : Fun->formals->formals) { + CompletionItem R; + R.label = State.symbols[Formal.name]; + R.kind = CompletionItemKind::Constructor; + Result.items.emplace_back(std::move(R)); + } + } + } catch (std::out_of_range &) { + } +} + +void CompletionBuilder::addEnv(nix::EvalState &State, nix::Env &NixEnv) { + if (NixEnv.type == nix::Env::HasWithExpr) { + nix::Value *V = State.allocValue(); + State.evalAttrs(*NixEnv.up, (nix::Expr *)NixEnv.values[0], *V, nix::noPos, + ""); + NixEnv.values[0] = V; + NixEnv.type = nix::Env::HasWithAttrs; + } + if (NixEnv.type == nix::Env::HasWithAttrs) { + for (const auto &SomeAttr : *NixEnv.values[0]->attrs) { + std::string Name = State.symbols[SomeAttr.name]; + lspserver::CompletionItem R; + R.label = Name; + R.kind = lspserver::CompletionItemKind::Variable; + Result.items.emplace_back(std::move(R)); + + if (Result.items.size() > Limit) { + Result.isIncomplete = true; + break; + } + } + } + if (NixEnv.up) + addEnv(State, *NixEnv.up); +} + +void CompletionBuilder::addEnv(const EvalAST &AST, nix::EvalState &State, + const nix::Expr *Node) { + try { + // Eval this node, for reaching deeper envs (e.g. with expressions). + AST.getValueEval(Node, State); + if (auto *ExprEnv = AST.searchUpEnv(Node)) + addEnv(State, *ExprEnv); + } catch (std::out_of_range &) { + } +} + +void CompletionBuilder::addStaticEnv(const nix::SymbolTable &STable, + const nix::StaticEnv &SEnv) { + for (auto [Symbol, Displ] : SEnv.vars) { + std::string Name = STable[Symbol]; + if (Name.starts_with("__")) + continue; + + CompletionItem R; + R.label = Name; + R.kind = CompletionItemKind::Constant; + Result.items.emplace_back(std::move(R)); + } + + if (SEnv.up) + addStaticEnv(STable, SEnv); +} + +} // namespace nixd diff --git a/nixd/lib/Sema/meson.build b/nixd/lib/Sema/meson.build new file mode 100644 index 000000000..2313d3d56 --- /dev/null +++ b/nixd/lib/Sema/meson.build @@ -0,0 +1,17 @@ +libnixdSemaDeps = [ nixd_lsp_server + , nix_all + , nixdAST + , llvm + ] + +libnixdSema = library('nixdSema' +, 'CompletionBuilder.cpp' +, include_directories: nixd_inc +, dependencies: libnixdSemaDeps +, install: true +) + +nixdSema = declare_dependency( include_directories: nixd_inc + , link_with: libnixdSema + , dependencies: libnixdSemaDeps + ) diff --git a/nixd/lib/Server/EvalWorker.cpp b/nixd/lib/Server/EvalWorker.cpp index 9b829d4aa..9a72a9fdf 100644 --- a/nixd/lib/Server/EvalWorker.cpp +++ b/nixd/lib/Server/EvalWorker.cpp @@ -1,9 +1,9 @@ #include "nixd/Server/EvalWorker.h" #include "nixd/Nix/Init.h" #include "nixd/Nix/Value.h" +#include "nixd/Sema/CompletionBuilder.h" #include "nixd/Server/EvalDraftStore.h" #include "nixd/Server/IPC.h" -#include "nixd/Support/CompletionHelper.h" #include "nixd/Support/String.h" #include "lspserver/LSPServer.h" @@ -163,93 +163,22 @@ void EvalWorker::onCompletion(const lspserver::CompletionParams &Params, auto Action = [Params, this](const nix::ref &AST, ReplyRAII &&RR) { auto State = IER->Session->getState(); + CompletionBuilder Builder; // Lookup an AST node, and get it's 'Env' after evaluation - CompletionHelper::Items Items; lspserver::log("current trigger character is {0}", Params.context.triggerCharacter); if (Params.context.triggerCharacter == ".") { - const auto *Node = AST->lookupEnd(Params.position); - if (!Node) - return; - try { - auto Value = AST->getValueEval(Node, *IER->Session->getState()); - if (Value.type() == nix::ValueType::nAttrs) { - // Traverse attribute bindings - for (auto Binding : *Value.attrs) { - Items.emplace_back( - CompletionItem{.label = State->symbols[Binding.name], - .kind = CompletionItemKind::Field}); - if (Items.size() > 5000) - break; - } - } - } catch (std::out_of_range &) { - RR.Response = error("no associated value on requested attrset."); - return; - } + Builder.addAttrFields(*AST, Params.position, *State); } else { - const auto *Node = AST->lookupContainMin(Params.position); - - // TODO: share the same code with static worker - std::vector Symbols; - AST->collectSymbols(Node, Symbols); - // Insert symbols to our completion list. - std::transform(Symbols.begin(), Symbols.end(), std::back_inserter(Items), - [&](const nix::Symbol &V) -> decltype(Items)::value_type { - decltype(Items)::value_type R; - R.kind = CompletionItemKind::Interface; - R.label = State->symbols[V]; - return R; - }); - - auto FromLambdaFormals = [&]() { - // Firstly, we check that if we are in "ExprAttrs", consider this - // is a - // special case for completion lambda formals - if (!dynamic_cast(Node)) - return; - // Then, check that if the parent of evaluates to a "" - try { - const auto *Parent = AST->parent(Node); - if (const auto *SomeExprCall = - dynamic_cast(Parent)) { - auto Value = AST->getValue(SomeExprCall->fun); - if (!Value.isLambda()) - return; - // Then, filling the completion list using that lambda - // formals. - auto *Fun = Value.lambda.fun; - if (!Fun->hasFormals()) - return; - for (auto Formal : Fun->formals->formals) { - CompletionItem Item; - Item.label = State->symbols[Formal.name]; - Item.kind = CompletionItemKind::Constructor; - Items.emplace_back(std::move(Item)); - } - } - - } catch (...) { - } - }; - FromLambdaFormals(); - try { - if (!Node) - return; - auto *ExprEnv = AST->searchUpEnv(Node); - CompletionHelper::fromEnv(*State, *ExprEnv, Items); - } catch (std::out_of_range &) { - } - CompletionHelper::fromStaticEnv(State->symbols, *State->staticBaseEnv, - Items); + Builder.addSymbols(*AST, Node); + Builder.addLambdaFormals(*AST, *State, Node); + Builder.addEnv(*AST, *State, Node); + Builder.addStaticEnv(State->symbols, *State->staticBaseEnv); } - // Make the response. - CompletionList List; - List.isIncomplete = false; - List.items = Items; - RR.Response = List; + if (!Builder.getResult().items.empty()) + RR.Response = Builder.getResult(); }; withAST(Params.textDocument.uri.file().str(), diff --git a/nixd/lib/Server/meson.build b/nixd/lib/Server/meson.build index 930e57205..fff8133ea 100644 --- a/nixd/lib/Server/meson.build +++ b/nixd/lib/Server/meson.build @@ -1,5 +1,6 @@ libnixdServerDeps = [ nixd_lsp_server , nix_all + , nixdSema , nixdAST , nixdNix , nixdSupport diff --git a/nixd/lib/Support/CompletionHelper.cpp b/nixd/lib/Support/CompletionHelper.cpp deleted file mode 100644 index 42925c9d7..000000000 --- a/nixd/lib/Support/CompletionHelper.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "nixd/Support/CompletionHelper.h" -#include "nixd/Nix/Eval.h" - -namespace nixd { - -void CompletionHelper::fromStaticEnv(const nix::SymbolTable &STable, - const nix::StaticEnv &SEnv, Items &Items) { - for (auto [Symbol, Displ] : SEnv.vars) { - std::string Name = STable[Symbol]; - if (Name.starts_with("__")) - continue; - - // Variables in static envs, let's mark it as "Constant". - Items.emplace_back(lspserver::CompletionItem{ - .label = Name, .kind = lspserver::CompletionItemKind::Constant}); - } - if (SEnv.up) - fromStaticEnv(STable, SEnv, Items); -} - -void CompletionHelper::fromEnv(nix::EvalState &State, nix::Env &NixEnv, - Items &Items) { - if (NixEnv.type == nix::Env::HasWithExpr) { - nix::Value *V = State.allocValue(); - State.evalAttrs(*NixEnv.up, (nix::Expr *)NixEnv.values[0], *V, nix::noPos, - ""); - NixEnv.values[0] = V; - NixEnv.type = nix::Env::HasWithAttrs; - } - if (NixEnv.type == nix::Env::HasWithAttrs) { - for (const auto &SomeAttr : *NixEnv.values[0]->attrs) { - std::string Name = State.symbols[SomeAttr.name]; - if (Items.size() > 5000) - break; - Items.emplace_back(lspserver::CompletionItem{ - .label = Name, .kind = lspserver::CompletionItemKind::Variable}); - } - } - if (NixEnv.up) - fromEnv(State, *NixEnv.up, Items); -} - -} // namespace nixd diff --git a/nixd/lib/Support/meson.build b/nixd/lib/Support/meson.build index 4021dc90d..485d196e3 100644 --- a/nixd/lib/Support/meson.build +++ b/nixd/lib/Support/meson.build @@ -5,7 +5,6 @@ libnixdSupportDeps = [ nix_all ] libnixdSupport = library('nixdSupport' -, 'CompletionHelper.cpp' , 'Diagnostic.cpp' , 'JSONSerialization.cpp' , include_directories: nixd_inc diff --git a/nixd/lib/meson.build b/nixd/lib/meson.build index a3a8cc7b6..39df63d31 100644 --- a/nixd/lib/meson.build +++ b/nixd/lib/meson.build @@ -3,4 +3,5 @@ subdir('Nix') subdir('Parser') subdir('AST') subdir('Support') +subdir('Sema') subdir('Server')