diff --git a/nixd/include/nixd/CommandLine/Options.h b/common/include/nixd/CommandLine/Options.h similarity index 100% rename from nixd/include/nixd/CommandLine/Options.h rename to common/include/nixd/CommandLine/Options.h diff --git a/nixd/include/nixd/Eval/AttrSetClient.h b/common/include/nixd/Eval/AttrSetClient.h similarity index 96% rename from nixd/include/nixd/Eval/AttrSetClient.h rename to common/include/nixd/Eval/AttrSetClient.h index 11931d9b4..e7bd04399 100644 --- a/nixd/include/nixd/Eval/AttrSetClient.h +++ b/common/include/nixd/Eval/AttrSetClient.h @@ -67,6 +67,10 @@ class AttrSetClient : public lspserver::LSPServer { void exit() { Exit(nullptr); } + void setLoggingEnabled(bool Enabled) { + LSPServer::setLoggingEnabled(Enabled); + } + /// Get executable path for launching the server. /// \returns null terminated string. static const char *getExe(); diff --git a/nixd/include/nixd/Eval/AttrSetProvider.h b/common/include/nixd/Eval/AttrSetProvider.h similarity index 100% rename from nixd/include/nixd/Eval/AttrSetProvider.h rename to common/include/nixd/Eval/AttrSetProvider.h diff --git a/common/include/nixd/Eval/Spawn.h b/common/include/nixd/Eval/Spawn.h new file mode 100644 index 000000000..b63ac8d29 --- /dev/null +++ b/common/include/nixd/Eval/Spawn.h @@ -0,0 +1,15 @@ +#pragma once + +#include "nixd/Eval/AttrSetClient.h" + +#include +#include + +namespace nixd { + +/// Launches an attrset evaluator process whose stderr is redirected to +/// `WorkerStderr`. +void spawnAttrSetEval(std::string_view WorkerStderr, + std::unique_ptr &Worker); + +} // namespace nixd diff --git a/nixd/include/nixd/Protocol/AttrSet.h b/common/include/nixd/Protocol/AttrSet.h similarity index 99% rename from nixd/include/nixd/Protocol/AttrSet.h rename to common/include/nixd/Protocol/AttrSet.h index 7db9c8255..bb55d3b7c 100644 --- a/nixd/include/nixd/Protocol/AttrSet.h +++ b/common/include/nixd/Protocol/AttrSet.h @@ -108,6 +108,7 @@ struct AttrPathCompleteParams { Selector Scope; /// \brief Search for packages prefixed with this "prefix" std::string Prefix; + std::optional MaxItems; }; llvm::json::Value toJSON(const AttrPathCompleteParams &Params); diff --git a/nixd/include/nixd/Support/AutoCloseFD.h b/common/include/nixd/Support/AutoCloseFD.h similarity index 100% rename from nixd/include/nixd/Support/AutoCloseFD.h rename to common/include/nixd/Support/AutoCloseFD.h diff --git a/nixd/include/nixd/Support/ForkPiped.h b/common/include/nixd/Support/ForkPiped.h similarity index 100% rename from nixd/include/nixd/Support/ForkPiped.h rename to common/include/nixd/Support/ForkPiped.h diff --git a/nixd/include/nixd/Support/PipedProc.h b/common/include/nixd/Support/PipedProc.h similarity index 100% rename from nixd/include/nixd/Support/PipedProc.h rename to common/include/nixd/Support/PipedProc.h diff --git a/nixd/include/nixd/Support/StreamProc.h b/common/include/nixd/Support/StreamProc.h similarity index 100% rename from nixd/include/nixd/Support/StreamProc.h rename to common/include/nixd/Support/StreamProc.h diff --git a/nixd/lib/CommandLine/Options.cpp b/common/lib/CommandLine/Options.cpp similarity index 100% rename from nixd/lib/CommandLine/Options.cpp rename to common/lib/CommandLine/Options.cpp diff --git a/nixd/lib/Eval/AttrSetClient.cpp b/common/lib/Eval/AttrSetClient.cpp similarity index 100% rename from nixd/lib/Eval/AttrSetClient.cpp rename to common/lib/Eval/AttrSetClient.cpp diff --git a/nixd/lib/Eval/AttrSetProvider.cpp b/common/lib/Eval/AttrSetProvider.cpp similarity index 97% rename from nixd/lib/Eval/AttrSetProvider.cpp rename to common/lib/Eval/AttrSetProvider.cpp index 125c5af46..d5cc4d987 100644 --- a/nixd/lib/Eval/AttrSetProvider.cpp +++ b/common/lib/Eval/AttrSetProvider.cpp @@ -162,9 +162,9 @@ void fillOptionDescription(nix::EvalState &State, nix::Value &V, std::vector completeNames(nix::Value &Scope, const nix::EvalState &State, - std::string_view Prefix) { - int Num = 0; + std::string_view Prefix, int Limit) { std::vector Names; + const bool Unlimited = Limit <= 0; // FIXME: we may want to use "Trie" to speedup the string searching. // However as my (roughtly) profiling the critical in this loop is @@ -174,10 +174,9 @@ std::vector completeNames(nix::Value &Scope, const nix::Attr &Attr = *AttrPtr; const std::string_view Name = State.symbols[Attr.name]; if (Name.starts_with(Prefix)) { - ++Num; Names.emplace_back(Name); // We set this a very limited number as to speedup - if (Num > MaxItems) + if (!Unlimited && Names.size() >= static_cast(Limit)) break; } } @@ -288,7 +287,8 @@ void AttrSetProvider::onAttrPathComplete( return; } - return Reply(completeNames(Scope, state(), Params.Prefix)); + const int Limit = Params.MaxItems.value_or(MaxItems); + return Reply(completeNames(Scope, state(), Params.Prefix, Limit)); } catch (const nix::BaseError &Err) { return Reply(error(Err.info().msg.str())); } catch (const std::exception &Err) { diff --git a/common/lib/Eval/Spawn.cpp b/common/lib/Eval/Spawn.cpp new file mode 100644 index 000000000..bbb77947c --- /dev/null +++ b/common/lib/Eval/Spawn.cpp @@ -0,0 +1,24 @@ +#include "nixd/Eval/Spawn.h" + +#include + +#include +#include + +namespace nixd { + +namespace { +constexpr std::string_view NullDevice{"/dev/null"}; +} + +void spawnAttrSetEval(std::string_view WorkerStderr, + std::unique_ptr &Worker) { + std::string Path = WorkerStderr.empty() ? std::string(NullDevice) + : std::string(WorkerStderr); + Worker = std::make_unique([Path = std::move(Path)]() { + freopen(Path.c_str(), "w", stderr); + return execl(AttrSetClient::getExe(), "nixd-attrset-eval", nullptr); + }); +} + +} // namespace nixd diff --git a/nixd/lib/Protocol/AttrSet.cpp b/common/lib/Protocol/AttrSet.cpp similarity index 92% rename from nixd/lib/Protocol/AttrSet.cpp rename to common/lib/Protocol/AttrSet.cpp index cf3dc2e8a..e6d023bcb 100644 --- a/nixd/lib/Protocol/AttrSet.cpp +++ b/common/lib/Protocol/AttrSet.cpp @@ -112,15 +112,17 @@ bool nixd::fromJSON(const llvm::json::Value &Params, AttrPathInfoResponse &R, } Value nixd::toJSON(const AttrPathCompleteParams &Params) { - return Object{{"Scope", Params.Scope}, {"Prefix", Params.Prefix}}; + return Object{{"Scope", Params.Scope}, + {"Prefix", Params.Prefix}, + {"MaxItems", Params.MaxItems}}; } bool nixd::fromJSON(const llvm::json::Value &Params, AttrPathCompleteParams &R, llvm::json::Path P) { ObjectMapper O(Params, P); - return O // - && O.map("Scope", R.Scope) // - && O.map("Prefix", R.Prefix) // - ; + return O // + && O.map("Scope", R.Scope) // + && O.map("Prefix", R.Prefix) // + && O.mapOptional("MaxItems", R.MaxItems); // } llvm::json::Value nixd::toJSON(const ValueDescription &Params) { diff --git a/nixd/lib/Support/AutoCloseFD.cpp b/common/lib/Support/AutoCloseFD.cpp similarity index 100% rename from nixd/lib/Support/AutoCloseFD.cpp rename to common/lib/Support/AutoCloseFD.cpp diff --git a/nixd/lib/Support/ForkPiped.cpp b/common/lib/Support/ForkPiped.cpp similarity index 100% rename from nixd/lib/Support/ForkPiped.cpp rename to common/lib/Support/ForkPiped.cpp diff --git a/nixd/lib/Support/StreamProc.cpp b/common/lib/Support/StreamProc.cpp similarity index 100% rename from nixd/lib/Support/StreamProc.cpp rename to common/lib/Support/StreamProc.cpp diff --git a/common/meson.build b/common/meson.build new file mode 100644 index 000000000..c76784dff --- /dev/null +++ b/common/meson.build @@ -0,0 +1,84 @@ +project('nixd-common', ['cpp'], default_options : ['cpp_std=gnu++20'], version : 'nightly') + +config_h = configuration_data() + +git = find_program('git', required : false) +if git.found() + res = run_command([git, 'describe', '--tags', '--always'], capture : true, check : false) + describe = res.stdout().strip() + if describe != '' + config_h.set_quoted('NIXD_VCS_TAG', describe) + endif +endif + +config_h.set_quoted('NIXD_VERSION', meson.project_version()) +config_h.set_quoted('NIXD_LIBEXEC', get_option('prefix') / get_option('libexecdir')) + +configure_file( + output : 'nixd-config.h', + configuration : config_h, +) + +cpp = meson.get_compiler('cpp') + +add_project_arguments([ + '-I' + meson.project_build_root(), + cpp.get_supported_arguments( + '-Werror=documentation', + '-Werror=delete-non-abstract-non-virtual-dtor', + '-Werror=pragma-once-outside-header', + '-Wno-unused-parameter', + '-Wno-missing-field-initializers' + ), +], language : 'cpp') + +if get_option('buildtype').startswith('debug') + add_project_arguments([ + cpp.get_supported_arguments('-Werror=return-type'), + ], language : 'cpp') +endif + +if get_option('buildtype').startswith('release') + add_project_arguments([ + cpp.get_supported_arguments( + '-Wno-return-type', + '-Wno-maybe-uninitialized', + '-Wno-unused-variable' + ), + ], language : 'cpp') +endif + +llvm = dependency('llvm') +nixd_lsp_server = dependency('nixd-lspserver', fallback : ['nixd-lspserver', 'nixd_lsp_server']) +nixt = dependency('nixt', fallback : ['libnixt', 'nixt']) + +common_inc = include_directories('include') + +common_deps = [nixd_lsp_server, llvm, nixt] + +nixd_common_lib = library( + 'nixd-common', + 'lib/Eval/AttrSetClient.cpp', + 'lib/Eval/AttrSetProvider.cpp', + 'lib/Eval/Spawn.cpp', + 'lib/Protocol/AttrSet.cpp', + 'lib/CommandLine/Options.cpp', + 'lib/Support/AutoCloseFD.cpp', + 'lib/Support/ForkPiped.cpp', + 'lib/Support/StreamProc.cpp', + include_directories : common_inc, + dependencies : common_deps, + install : true, +) + +subdir('tools') + +nixd_common = declare_dependency( + include_directories : common_inc, + link_with : nixd_common_lib, + dependencies : common_deps, +) + +meson.override_dependency('nixd-common', nixd_common) + +install_subdir('include', install_dir : 'include') diff --git a/common/subprojects/libnixt b/common/subprojects/libnixt new file mode 120000 index 000000000..5be85ff97 --- /dev/null +++ b/common/subprojects/libnixt @@ -0,0 +1 @@ +../../libnixt \ No newline at end of file diff --git a/common/subprojects/nixd-lspserver b/common/subprojects/nixd-lspserver new file mode 120000 index 000000000..ebb89fb5d --- /dev/null +++ b/common/subprojects/nixd-lspserver @@ -0,0 +1 @@ +../../nixd/lspserver \ No newline at end of file diff --git a/common/tools/meson.build b/common/tools/meson.build new file mode 100644 index 000000000..c23cd6d3a --- /dev/null +++ b/common/tools/meson.build @@ -0,0 +1,11 @@ +nixd_attrset_eval = executable( + 'nixd-attrset-eval', + 'nixd-attrset-eval.cpp', + include_directories : common_inc, + link_with : nixd_common_lib, + dependencies : common_deps, + install : true, + install_dir : get_option('libexecdir'), +) + +meson.override_find_program('nixd-attrset-eval', nixd_attrset_eval) diff --git a/nixd/tools/nixd-attrset-eval.cpp b/common/tools/nixd-attrset-eval.cpp similarity index 100% rename from nixd/tools/nixd-attrset-eval.cpp rename to common/tools/nixd-attrset-eval.cpp diff --git a/flake.nix b/flake.nix index e703e52ad..d81392471 100644 --- a/flake.nix +++ b/flake.nix @@ -35,7 +35,9 @@ ; nixComponents = nixVersions.nixComponents_2_30; llvmPackages = llvmPackages_19; - nixf = callPackage ./libnixf { }; + nixf = callPackage ./libnixf { + inherit llvmPackages nixComponents; + }; nixt = callPackage ./libnixt { inherit nixComponents; }; nixd = callPackage ./nixd { inherit nixComponents nixf nixt; diff --git a/libnixf/default.nix b/libnixf/default.nix index 5d2813bb4..f823697be 100644 --- a/libnixf/default.nix +++ b/libnixf/default.nix @@ -9,6 +9,8 @@ boost, nlohmann_json, python312, + llvmPackages, + nixComponents, }: stdenv.mkDerivation { @@ -39,8 +41,12 @@ stdenv.mkDerivation { nativeCheckInputs = [ lit ]; buildInputs = [ - gtest boost + gtest + llvmPackages.llvm + nixComponents.nix-cmd + nixComponents.nix-expr + nixComponents.nix-main nlohmann_json ]; diff --git a/libnixf/include/nixf/Basic/Nodes/Expr.h b/libnixf/include/nixf/Basic/Nodes/Expr.h index 963e094ea..57dac23b9 100644 --- a/libnixf/include/nixf/Basic/Nodes/Expr.h +++ b/libnixf/include/nixf/Basic/Nodes/Expr.h @@ -175,6 +175,30 @@ class ExprWith : public Expr { [[nodiscard]] ChildVector children() const override { return {KwWith.get(), TokSemi.get(), With.get(), E.get()}; } + + [[nodiscard]] std::vector selector() const { + std::vector Parts; + for (const auto &Child : With->children()) { + if (!Child) + continue; + switch (Child->kind()) { + case Node::NK_ExprVar: { + const auto &Var = static_cast(*Child); + Parts.emplace_back(Var.id().name()); + break; + } + case Node::NK_AttrPath: { + const auto &Path = static_cast(*Child); + for (const auto &Name : Path.names()) + Parts.emplace_back(Name->id()->name()); + break; + } + default: + break; + } + } + return Parts; + } }; } // namespace nixf diff --git a/libnixf/include/nixf/Sema/VariableLookup.h b/libnixf/include/nixf/Sema/VariableLookup.h index 7b0843f3e..3a0eb434f 100644 --- a/libnixf/include/nixf/Sema/VariableLookup.h +++ b/libnixf/include/nixf/Sema/VariableLookup.h @@ -18,8 +18,15 @@ #include #include #include +#include +#include #include +namespace nixd { +class AttrSetClient; +class AttrSetClientProc; +} // namespace nixd + namespace nixf { /// \brief Represents a definition @@ -162,8 +169,16 @@ class VariableLookupAnalysis { std::map Results; + std::unique_ptr NixpkgsEval; + nixd::AttrSetClient *NixpkgsClient = nullptr; + std::unordered_map> + NixpkgsKnownAttrs; + const std::unordered_set * + ensureNixpkgsKnownAttrsCached(const std::vector &Scope); + public: VariableLookupAnalysis(std::vector &Diags); + ~VariableLookupAnalysis(); /// \brief Perform variable lookup analysis (def-use) on AST. /// \note This method should be invoked after any other method called. @@ -196,6 +211,10 @@ class VariableLookupAnalysis { } const EnvNode *env(const Node *N) const; + + [[nodiscard]] nixd::AttrSetClient *nixpkgsClient() const { + return NixpkgsClient; + } }; } // namespace nixf diff --git a/libnixf/meson.build b/libnixf/meson.build index 5b2481fca..53c8b7dfe 100644 --- a/libnixf/meson.build +++ b/libnixf/meson.build @@ -9,6 +9,8 @@ boost = dependency('boost') gtest = dependency('gtest') gtest_main = dependency('gtest_main') +nixd_common = dependency('nixd-common', fallback : ['common', 'nixd_common']) + pkgconfig = import('pkgconfig') diff --git a/libnixf/src/Sema/VariableLookup.cpp b/libnixf/src/Sema/VariableLookup.cpp index a4bf681a6..c2be54ac3 100644 --- a/libnixf/src/Sema/VariableLookup.cpp +++ b/libnixf/src/Sema/VariableLookup.cpp @@ -3,6 +3,16 @@ #include "nixf/Basic/Nodes/Attrs.h" #include "nixf/Basic/Nodes/Lambda.h" +#include "nixd/Eval/AttrSetClient.h" +#include "nixd/Eval/Spawn.h" +#include "nixd/Protocol/AttrSet.h" + +#include + +#include +#include +#include + using namespace nixf; namespace { @@ -74,6 +84,25 @@ void VariableLookupAnalysis::emitEnvLivenessWarning( } } +static constexpr std::array KnownIdioms = { + "pkgs", + "lib", +}; + +std::string joinScope(const std::vector &Scope) { + std::string Key; + bool First = true; + for (const auto &Part : Scope) { + if (Part.empty()) + continue; + if (!First) + Key.push_back('.'); + First = false; + Key.append(Part); + } + return Key; +} + void VariableLookupAnalysis::lookupVar(const ExprVar &Var, const std::shared_ptr &Env) { const auto &Name = Var.id().name(); @@ -91,8 +120,9 @@ void VariableLookupAnalysis::lookupVar(const ExprVar &Var, // with builtins; // generators <--- this variable may come from "lib" | "builtins" // - // We cannot determine where it precisely come from, thus mark all Envs - // alive. + // In general, we cannot determine where it precisely come from, thus mark + // all Envs alive. (We'll make an attempt to resolve the variable with + // nixpkgs client below.) if (CurEnv->isWith()) { WithEnvs.emplace_back(CurEnv); } @@ -105,6 +135,23 @@ void VariableLookupAnalysis::lookupVar(const ExprVar &Var, for (const auto *WithEnv : WithEnvs) { Def = WithDefs.at(WithEnv->syntax()); Def->usedBy(Var); + + // Attempt to resolve the variable with nixpkgs client. + bool IsKnown = false; + const auto &With = static_cast(*WithEnv->syntax()); + + auto Selector = With.selector(); + if (Selector.empty()) + continue; + for (std::string_view Idiom : KnownIdioms) { + if (Selector.front() != Idiom) + continue; + if (const auto *Known = ensureNixpkgsKnownAttrsCached(Selector)) + IsKnown = Known->contains(Name); + break; + } + if (IsKnown) + break; } Results.insert({&Var, LookupResult{LookupResultKind::FromWith, Def}}); } else { @@ -475,7 +522,56 @@ void VariableLookupAnalysis::runOnAST(const Node &Root) { } VariableLookupAnalysis::VariableLookupAnalysis(std::vector &Diags) - : Diags(Diags) {} + : Diags(Diags) { + spawnAttrSetEval({}, NixpkgsEval); + if (NixpkgsEval) + NixpkgsClient = NixpkgsEval->client(); + if (NixpkgsClient) + NixpkgsClient->setLoggingEnabled(false); +} + +const std::unordered_set * +VariableLookupAnalysis::ensureNixpkgsKnownAttrsCached( + const std::vector &Scope) { + if (!NixpkgsClient) + return nullptr; + + std::string FullPath = joinScope(Scope); + if (NixpkgsKnownAttrs.contains(FullPath)) + return &NixpkgsKnownAttrs.at(FullPath); + + if (NixpkgsKnownAttrs.empty()) { + std::binary_semaphore Ready(0); + NixpkgsClient->evalExpr( + "import { }", + [&Ready](llvm::Expected> Resp) { + Ready.release(); + }); + Ready.acquire(); + } + + nixd::AttrPathCompleteParams Params; + Params.Scope = Scope; + Params.Prefix = ""; + Params.MaxItems = 0; + + std::binary_semaphore Ready(0); + std::vector Names; + NixpkgsClient->attrpathComplete( + Params, + [&Names, &Ready](llvm::Expected Resp) { + if (Resp) + Names = *Resp; + Ready.release(); + }); + Ready.acquire(); + + auto &Bucket = NixpkgsKnownAttrs[FullPath]; + Bucket.insert(Names.begin(), Names.end()); + return &Bucket; +} + +VariableLookupAnalysis::~VariableLookupAnalysis() = default; const EnvNode *VariableLookupAnalysis::env(const Node *N) const { if (!Envs.contains(N)) diff --git a/libnixf/src/meson.build b/libnixf/src/meson.build index 8bdb7868b..98f4e17b7 100644 --- a/libnixf/src/meson.build +++ b/libnixf/src/meson.build @@ -1,7 +1,7 @@ -libnixf_deps = [ boost, nlohmann_json ] - libnixf_inc = include_directories('../include') +libnixf_deps = [ boost, nlohmann_json, nixd_common ] + python3 = find_program('python3') diagnostic_enum_h = custom_target( @@ -77,3 +77,5 @@ nixf = declare_dependency( install_subdir('../include/nixf', install_dir: 'include') + +meson.override_dependency('nixf', nixf) diff --git a/libnixf/subprojects/common b/libnixf/subprojects/common new file mode 120000 index 000000000..dc879abe9 --- /dev/null +++ b/libnixf/subprojects/common @@ -0,0 +1 @@ +../../common \ No newline at end of file diff --git a/libnixf/test/Sema/VariableLookup.cpp b/libnixf/test/Sema/VariableLookup.cpp index 4939b09b8..c2111d5be 100644 --- a/libnixf/test/Sema/VariableLookup.cpp +++ b/libnixf/test/Sema/VariableLookup.cpp @@ -350,4 +350,68 @@ __curPos ASSERT_EQ(Diags.size(), 0); } +TEST_F(VLATest, NestedWith_KnownMaintainer) { + const char *Src = R"( +{ lib }: { + meta = with lib; { + maintainers = with lib.maintainers; [ booxter ]; + }; +} + )"; + + std::shared_ptr AST = parse(Src, Diags); + VariableLookupAnalysis VLA(Diags); + VLA.runOnAST(*AST); + + ASSERT_EQ(Diags.size(), 1); +} + +TEST_F(VLATest, NestedWith_UnknownMaintainer) { + const char *Src = R"( +{ lib }: { + meta = with lib; { + maintainers = with lib.maintainers; [ booxter-missing ]; + }; +} + )"; + + std::shared_ptr AST = parse(Src, Diags); + VariableLookupAnalysis VLA(Diags); + VLA.runOnAST(*AST); + + ASSERT_EQ(Diags.size(), 0); +} + +TEST_F(VLATest, NestedWith_Pkgs_KnownMaintainer) { + const char *Src = R"( +{ lib, pkgs }: { + meta = with lib; { + maintainers = with pkgs.lib.maintainers; [ booxter ]; + }; +} + )"; + + std::shared_ptr AST = parse(Src, Diags); + VariableLookupAnalysis VLA(Diags); + VLA.runOnAST(*AST); + + ASSERT_EQ(Diags.size(), 1); +} + +TEST_F(VLATest, NestedWith_Pkgs_UnknownMaintainer) { + const char *Src = R"( +{ lib, pkgs }: { + meta = with lib; { + maintainers = with pkgs.lib.maintainers; [ booxter-missing ]; + }; +} + )"; + + std::shared_ptr AST = parse(Src, Diags); + VariableLookupAnalysis VLA(Diags); + VLA.runOnAST(*AST); + + ASSERT_EQ(Diags.size(), 0); +} + } // namespace diff --git a/libnixf/test/meson.build b/libnixf/test/meson.build index 931c1ffbb..0ffce298b 100644 --- a/libnixf/test/meson.build +++ b/libnixf/test/meson.build @@ -20,6 +20,25 @@ test('unit/libnixf/Parse', ) ) +common_sp = subproject('common', required : false) +if common_sp.found() + nixd_attrset_eval_prog = common_sp.get_variable('nixd_attrset_eval') + nixd_attrset_eval_dep = [nixd_attrset_eval_prog] +else + nixd_attrset_eval_prog = find_program('nixd-attrset-eval', required : true) + nixd_attrset_eval_dep = [] +endif + +fs = import('fs') +attrset_eval_dir = fs.parent(nixd_attrset_eval_prog.full_path()) + +libnixf_test_env = environment() + +libnixf_test_env.prepend('PATH', attrset_eval_dir) +libnixf_test_env.prepend('PATH', meson.current_build_dir()) +libnixf_test_env.set('MESON_BUILD_ROOT', meson.current_build_dir()) +libnixf_test_env.set('NIXD_ATTRSET_EVAL', nixd_attrset_eval_prog.full_path()) + test('unit/libnixf/Sema', executable('unit-libnixf-sema', 'Sema/SemaActions.cpp', @@ -27,5 +46,8 @@ test('unit/libnixf/Sema', 'Sema/ParentMap.cpp', dependencies: [ nixf, gtest_main ], include_directories: [ '../src/Sema' ] # Private headers - ) + ), + timeout: 120, + env: libnixf_test_env, + depends: nixd_attrset_eval_dep, ) diff --git a/libnixt/lib/meson.build b/libnixt/lib/meson.build index d4d4ec310..1fdc76f3f 100644 --- a/libnixt/lib/meson.build +++ b/libnixt/lib/meson.build @@ -24,3 +24,5 @@ nixt = declare_dependency(include_directories: libnixd_inc, ) install_subdir('../include/nixt', install_dir: 'include') + +meson.override_dependency('nixt', nixt) diff --git a/meson.build b/meson.build index c3911e812..7c6a96018 100644 --- a/meson.build +++ b/meson.build @@ -1,87 +1,14 @@ -project( 'nixd' - , ['c', 'cpp'] - , default_options : ['cpp_std=gnu++20'] - , version: 'nightly' - ) - -config_h = configuration_data() - -git = find_program('git', required : false) -if git.found() - res = run_command(['git', 'describe', '--tags', '--always'], capture : true, check : false) - describe = res.stdout().strip() - config_h.set_quoted('NIXD_VCS_TAG', describe) -endif - -config_h.set_quoted('NIXD_VERSION', meson.project_version()) -config_h.set_quoted('NIXD_LIBEXEC', get_option('prefix') / get_option('libexecdir')) - -configure_file( - output: 'nixd-config.h', - configuration: config_h, -) - -cpp = meson.get_compiler('cpp') - -add_project_arguments([ - '-I' + meson.project_build_root(), - cpp.get_supported_arguments( - '-Werror=documentation', - '-Werror=delete-non-abstract-non-virtual-dtor', - '-Werror=pragma-once-outside-header', - '-Wno-unused-parameter', - '-Wno-missing-field-initializers', - ), -], language: 'cpp') - -if get_option('buildtype').startswith('debug') - add_project_arguments([ - cpp.get_supported_arguments( - '-Werror=return-type', - ), - ], language: 'cpp') -endif - -if get_option('buildtype').startswith('release') - add_project_arguments([ - cpp.get_supported_arguments( - '-Wno-return-type', - '-Wno-maybe-uninitialized', - '-Wno-unused-variable', - ), - ], language: 'cpp') -endif - - -gtest = dependency('gtest') -gtest_main = dependency('gtest_main') - -llvm = dependency('llvm') -boost = dependency('boost') -nlohmann_json = dependency('nlohmann_json') - -lit = find_program('lit', required: false) - -pkgconfig = import('pkgconfig') - - -subdir('libnixf/src') -subdir('libnixf/tools') -subdir('libnixf/test') - - -nix_main = dependency('nix-main') -nix_expr = dependency('nix-expr') -nix_cmd = dependency('nix-cmd') -nix_flake = dependency('nix-flake') - - -subdir('libnixt/lib') -subdir('libnixt/test') - -subdir('nixd/lspserver') -subdir('nixd/lib') -subdir('nixd/tools') +project('nixd', ['c', 'cpp'], default_options : ['cpp_std=gnu++20'], version : 'nightly') +# Wire up the component projects via subproject fallbacks so the entire tree can +# still be built from the repository root. +nixd_common = dependency('nixd-common', fallback : ['common', 'nixd_common']) +nixf = dependency('nixf', fallback : ['libnixf', 'nixf']) +nixt = dependency('nixt', fallback : ['libnixt', 'nixt']) +# Configure the language server itself. This instantiates the standalone nixd +# project without duplicating library builds thanks to the overrides above. +subproject('nixd') +# Keep references alive to avoid "unused variable" warnings in Meson. +_ = [nixd_common, nixf, nixt] diff --git a/nixd/include/nixd/Eval/Launch.h b/nixd/include/nixd/Eval/Launch.h index d6e1fcf66..15e7d677e 100644 --- a/nixd/include/nixd/Eval/Launch.h +++ b/nixd/include/nixd/Eval/Launch.h @@ -1,6 +1,6 @@ #pragma once -#include "AttrSetClient.h" +#include "nixd/Eval/AttrSetClient.h" namespace nixd { diff --git a/nixd/lib/Eval/Launch.cpp b/nixd/lib/Eval/Launch.cpp index eeb2c05fc..54d78bbfd 100644 --- a/nixd/lib/Eval/Launch.cpp +++ b/nixd/lib/Eval/Launch.cpp @@ -1,5 +1,6 @@ #include "nixd/Eval/Launch.h" #include "nixd/CommandLine/Options.h" +#include "nixd/Eval/Spawn.h" #include @@ -23,10 +24,7 @@ opt NixpkgsWorkerStderr{ void nixd::startAttrSetEval(const std::string &Name, std::unique_ptr &Worker) { - Worker = std::make_unique([&Name]() { - freopen(Name.c_str(), "w", stderr); - return execl(AttrSetClient::getExe(), "nixd-attrset-eval", nullptr); - }); + spawnAttrSetEval(Name, Worker); } void nixd::startNixpkgs(std::unique_ptr &NixpkgsEval) { diff --git a/nixd/lib/meson.build b/nixd/lib/meson.build index 701850a24..a8c393ba1 100644 --- a/nixd/lib/meson.build +++ b/nixd/lib/meson.build @@ -1,11 +1,10 @@ libnixd_include = include_directories('../include') -libnixd_deps = [ nixd_lsp_server, nixf, llvm, nixt ] +libnixd_deps = [ nixd_common, nixf, llvm, nixt ] libnixd_lib = library( 'nixd', 'CommandLine/Configuration.cpp', - 'CommandLine/Options.cpp', 'Controller/AST.cpp', 'Controller/CodeAction.cpp', 'Controller/Completion.cpp', @@ -26,16 +25,10 @@ libnixd_lib = library( 'Controller/SemanticTokens.cpp', 'Controller/Support.cpp', 'Controller/TextDocumentSync.cpp', - 'Eval/AttrSetClient.cpp', - 'Eval/AttrSetProvider.cpp', 'Eval/Launch.cpp', - 'Protocol/AttrSet.cpp', 'Protocol/Protocol.cpp', - 'Support/AutoCloseFD.cpp', 'Support/AutoRemoveShm.cpp', - 'Support/ForkPiped.cpp', 'Support/JSON.cpp', - 'Support/StreamProc.cpp', dependencies: libnixd_deps, include_directories: libnixd_include, install: true diff --git a/nixd/lspserver/include/lspserver/Connection.h b/nixd/lspserver/include/lspserver/Connection.h index 030b16d03..6b4510f5f 100644 --- a/nixd/lspserver/include/lspserver/Connection.h +++ b/nixd/lspserver/include/lspserver/Connection.h @@ -35,10 +35,13 @@ class InboundPort { private: std::atomic Close; + template void maybeLog(const char *Fmt, Ts &&...Vals); + public: int In; JSONStreamStyle StreamStyle = JSONStreamStyle::Standard; + bool LoggingEnabled = true; /// Read one message as specified in the LSP standard. /// A Language Server Protocol message starts with a set of @@ -51,6 +54,8 @@ class InboundPort { /// \brief Notify the inbound port to close the connection void close() { Close = true; } + void setLoggingEnabled(bool Enabled) { LoggingEnabled = Enabled; } + InboundPort(int In = STDIN_FILENO, JSONStreamStyle StreamStyle = JSONStreamStyle::Standard) : Close(false), In(In), StreamStyle(StreamStyle) {}; @@ -76,6 +81,9 @@ class OutboundPort { std::mutex Mutex; bool Pretty = false; + bool LoggingEnabled = true; + + template void maybeLog(const char *Fmt, Ts &&...Vals); public: explicit OutboundPort(bool Pretty = false) @@ -88,6 +96,8 @@ class OutboundPort { void reply(llvm::json::Value ID, llvm::Expected Result); void sendMessage(llvm::json::Value Message); + + void setLoggingEnabled(bool Enabled) { LoggingEnabled = Enabled; } }; } // namespace lspserver diff --git a/nixd/lspserver/include/lspserver/LSPServer.h b/nixd/lspserver/include/lspserver/LSPServer.h index 14a946aa6..3c17886b6 100644 --- a/nixd/lspserver/include/lspserver/LSPServer.h +++ b/nixd/lspserver/include/lspserver/LSPServer.h @@ -9,6 +9,7 @@ #include #include +#include namespace lspserver { @@ -19,6 +20,13 @@ class LSPServer : public MessageHandler { std::unique_ptr In; std::unique_ptr Out; + bool LogsEnabled = true; + + template void maybeLog(const char *Fmt, Ts &&...Vals) const { + if (LogsEnabled) + log(Fmt, std::forward(Vals)...); + } + bool onNotify(llvm::StringRef Method, llvm::json::Value) override; bool onCall(llvm::StringRef Method, llvm::json::Value Params, llvm::json::Value ID) override; @@ -47,7 +55,7 @@ class LSPServer : public MessageHandler { void callMethod(llvm::StringRef Method, llvm::json::Value Params, Callback CB, OutboundPort *O) { llvm::json::Value ID(bindReply(std::move(CB))); - log("--> call {0}({1})", Method, ID.getAsInteger()); + maybeLog("--> call {0}({1})", Method, ID.getAsInteger()); O->call(Method, Params, ID); } @@ -58,8 +66,8 @@ class LSPServer : public MessageHandler { mkOutNotifiction(llvm::StringRef Method, OutboundPort *O = nullptr) { if (!O) O = Out.get(); - return [=](const T &Params) { - log("--> notify {0}", Method); + return [=, this](const T &Params) { + maybeLog("--> notify {0}", Method); O->notify(Method, Params); }; } @@ -87,6 +95,14 @@ class LSPServer : public MessageHandler { LSPServer(std::unique_ptr In, std::unique_ptr Out) : In(std::move(In)), Out(std::move(Out)) {}; + void setLoggingEnabled(bool Enabled) { + LogsEnabled = Enabled; + if (In) + In->setLoggingEnabled(Enabled); + if (Out) + Out->setLoggingEnabled(Enabled); + } + /// \brief Close the inbound port. void closeInbound() { In->close(); } void run(); diff --git a/nixd/lspserver/meson.build b/nixd/lspserver/meson.build index 512a42e1c..051b807a3 100644 --- a/nixd/lspserver/meson.build +++ b/nixd/lspserver/meson.build @@ -1,21 +1,51 @@ -nixd_lsp_server_deps = [ llvm ] +project('nixd-lspserver', ['cpp'], default_options : ['cpp_std=gnu++20'], version : 'nightly') + +cpp = meson.get_compiler('cpp') + +add_project_arguments([ + cpp.get_supported_arguments( + '-Werror=documentation', + '-Werror=delete-non-abstract-non-virtual-dtor', + '-Werror=pragma-once-outside-header', + '-Wno-unused-parameter', + '-Wno-missing-field-initializers' + ), +], language : 'cpp') + +if get_option('buildtype').startswith('release') + add_project_arguments([ + cpp.get_supported_arguments( + '-Wno-maybe-uninitialized', + ), + ], language: 'cpp') +endif + +llvm = dependency('llvm') nixd_lsp_server_inc = include_directories('include') -nixd_lsp_server_lib = library('nixd-lspserver' -, [ 'src/Connection.cpp' - , 'src/DraftStore.cpp' - , 'src/LSPServer.cpp' - , 'src/Logger.cpp' - , 'src/Protocol.cpp' - , 'src/SourceCode.cpp' - , 'src/URI.cpp' - ] -, include_directories: nixd_lsp_server_inc -, dependencies: nixd_lsp_server_deps -, install: true +nixd_lsp_server_lib = library( + 'nixd-lspserver', + [ + 'src/Connection.cpp', + 'src/DraftStore.cpp', + 'src/LSPServer.cpp', + 'src/Logger.cpp', + 'src/Protocol.cpp', + 'src/SourceCode.cpp', + 'src/URI.cpp', + ], + include_directories : nixd_lsp_server_inc, + dependencies : [llvm], + install : true, ) -nixd_lsp_server = declare_dependency( link_with: nixd_lsp_server_lib - , include_directories: nixd_lsp_server_inc - , dependencies: nixd_lsp_server_deps - ) + +nixd_lsp_server = declare_dependency( + link_with : nixd_lsp_server_lib, + include_directories : nixd_lsp_server_inc, + dependencies : [llvm], +) + +meson.override_dependency('nixd-lspserver', nixd_lsp_server) + +install_subdir('include', install_dir : 'include') diff --git a/nixd/lspserver/src/Connection.cpp b/nixd/lspserver/src/Connection.cpp index 9d6ffd206..c4a98d027 100644 --- a/nixd/lspserver/src/Connection.cpp +++ b/nixd/lspserver/src/Connection.cpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace { @@ -50,6 +51,20 @@ char ReadEOF::ID; namespace lspserver { +template +void OutboundPort::maybeLog(const char *Fmt, Ts &&...Vals) { + if (!LoggingEnabled) + return; + vlog(Fmt, std::forward(Vals)...); +} + +template +void InboundPort::maybeLog(const char *Fmt, Ts &&...Vals) { + if (!LoggingEnabled) + return; + vlog(Fmt, std::forward(Vals)...); +} + static llvm::json::Object encodeError(llvm::Error Error) { std::string Message; ErrorCode Code = ErrorCode::UnknownErrorCode; @@ -112,7 +127,7 @@ void OutboundPort::reply(llvm::json::Value ID, void OutboundPort::sendMessage(llvm::json::Value Message) { // Make sure our outputs are not interleaving between messages (json) - vlog(">>> {0}", Message); + maybeLog(">>> {0}", Message); std::lock_guard Guard(Mutex); OutputBuffer.clear(); llvm::raw_svector_ostream SVecOS(OutputBuffer); @@ -320,7 +335,7 @@ void InboundPort::loop(MessageHandler &Handler) { for (;;) { if (auto Message = readMessage(Buffer)) { - vlog("<<< {0}", jsonToString(*Message)); + maybeLog("<<< {0}", jsonToString(*Message)); if (!dispatch(*Message, Handler)) return; } else { diff --git a/nixd/lspserver/src/LSPServer.cpp b/nixd/lspserver/src/LSPServer.cpp index abb8177e6..f07f10702 100644 --- a/nixd/lspserver/src/LSPServer.cpp +++ b/nixd/lspserver/src/LSPServer.cpp @@ -15,33 +15,33 @@ namespace lspserver { void LSPServer::run() { In->loop(*this); } bool LSPServer::onNotify(llvm::StringRef Method, llvm::json::Value Params) { - log("<-- {0}", Method); + maybeLog("<-- {0}", Method); if (Method == "exit") return false; auto Handler = Registry.NotificationHandlers.find(Method); if (Handler != Registry.NotificationHandlers.end()) { Handler->second(std::move(Params)); } else { - log("unhandled notification {0}", Method); + maybeLog("unhandled notification {0}", Method); } return true; } bool LSPServer::onCall(llvm::StringRef Method, llvm::json::Value Params, llvm::json::Value ID) { - log("<-- {0}({1})", Method, ID); + maybeLog("<-- {0}({1})", Method, ID); auto Handler = Registry.MethodHandlers.find(Method); if (Handler != Registry.MethodHandlers.end()) Handler->second(std::move(Params), [=, Method = std::string(Method), this](llvm::Expected Response) mutable { if (Response) { - log("--> reply:{0}({1})", Method, ID); + maybeLog("--> reply:{0}({1})", Method, ID); Out->reply(std::move(ID), std::move(Response)); } else { llvm::Error Err = Response.takeError(); - log("--> reply:{0}({1}) {2:ms}, error: {3}", Method, ID, - Err); + maybeLog("--> reply:{0}({1}) {2:ms}, error: {3}", + Method, ID, Err); Out->reply(std::move(ID), std::move(Err)); } }); @@ -52,7 +52,7 @@ bool LSPServer::onCall(llvm::StringRef Method, llvm::json::Value Params, bool LSPServer::onReply(llvm::json::Value ID, llvm::Expected Result) { - log("<-- reply({0})", ID); + maybeLog("<-- reply({0})", ID); std::optional> CB; if (auto OptI = ID.getAsInteger()) { diff --git a/nixd/meson.build b/nixd/meson.build index 90f3c039c..eb3395f82 100644 --- a/nixd/meson.build +++ b/nixd/meson.build @@ -27,15 +27,24 @@ add_project_arguments([ ), ], language: 'cpp') +if get_option('buildtype').startswith('release') + add_project_arguments([ + cpp.get_supported_arguments( + '-Wno-maybe-uninitialized', + ), + ], language: 'cpp') +endif + gtest = dependency('gtest') gtest_main = dependency('gtest_main') -nixf = dependency('nixf') -nixt = dependency('nixt') llvm = dependency('llvm') +nixf = dependency('nixf', fallback : ['libnixf', 'nixf']) +nixt = dependency('nixt', fallback : ['libnixt', 'nixt']) +nixd_common = dependency('nixd-common', fallback : ['common', 'nixd_common']) + pkgconfig = import('pkgconfig') -subdir('lspserver') subdir('lib') subdir('tools') diff --git a/nixd/subprojects/common b/nixd/subprojects/common new file mode 120000 index 000000000..dc879abe9 --- /dev/null +++ b/nixd/subprojects/common @@ -0,0 +1 @@ +../../common \ No newline at end of file diff --git a/nixd/subprojects/libnixf b/nixd/subprojects/libnixf new file mode 120000 index 000000000..949449029 --- /dev/null +++ b/nixd/subprojects/libnixf @@ -0,0 +1 @@ +../../libnixf \ No newline at end of file diff --git a/nixd/subprojects/libnixt b/nixd/subprojects/libnixt new file mode 120000 index 000000000..5be85ff97 --- /dev/null +++ b/nixd/subprojects/libnixt @@ -0,0 +1 @@ +../../libnixt \ No newline at end of file diff --git a/nixd/tools/meson.build b/nixd/tools/meson.build index 072c21239..6774f6bd2 100644 --- a/nixd/tools/meson.build +++ b/nixd/tools/meson.build @@ -7,19 +7,24 @@ nixd = executable( lit = find_program('lit', required: false) -nixd_attrset_eval = executable( - 'nixd-attrset-eval', - 'nixd-attrset-eval.cpp', - install: true, - dependencies: libnixd, - install_dir: get_option('libexecdir'), -) +common_sp = subproject('common', required : false) +if common_sp.found() + nixd_attrset_eval_prog = common_sp.get_variable('nixd_attrset_eval') + nixd_attrset_eval_dep = [nixd_attrset_eval_prog] +else + nixd_attrset_eval_prog = find_program('nixd-attrset-eval', required : true) + nixd_attrset_eval_dep = [] +endif + +fs = import('fs') +attrset_eval_dir = fs.parent(nixd_attrset_eval_prog.full_path()) regression_controller_env = environment() +regression_controller_env.prepend('PATH', attrset_eval_dir) regression_controller_env.prepend('PATH', meson.current_build_dir()) regression_controller_env.set('MESON_BUILD_ROOT', meson.current_build_dir()) -regression_controller_env.set('NIXD_ATTRSET_EVAL', nixd_attrset_eval.path()) +regression_controller_env.set('NIXD_ATTRSET_EVAL', nixd_attrset_eval_prog.full_path()) if lit.found() @@ -32,12 +37,13 @@ if lit.found() meson.current_source_dir() + '/nixd/test' ], timeout: 120, - depends: [ nixd, nixd_attrset_eval ] ) + depends: [ nixd ] + nixd_attrset_eval_dep ) endif regression_worker_env = environment() regression_worker_env.append('PATH', meson.current_build_dir()) +regression_worker_env.append('PATH', attrset_eval_dir) regression_worker_env.set('MESON_BUILD_ROOT', meson.current_build_dir()) regression_worker_env.set('ASAN_OPTIONS', 'detect_leaks=0') @@ -50,5 +56,5 @@ if lit.found() '-vv', meson.current_source_dir() + '/nixd-attrset-eval/test' ], - depends: [ nixd_attrset_eval ] ) + depends: nixd_attrset_eval_dep ) endif diff --git a/nixd/tools/nixd/test/completion/nixpkgs.md b/nixd/tools/nixd/test/completion/nixpkgs.md index 6300d7ba0..ba4fea541 100644 --- a/nixd/tools/nixd/test/completion/nixpkgs.md +++ b/nixd/tools/nixd/test/completion/nixpkgs.md @@ -58,13 +58,13 @@ CHECK-NEXT: "label": "abort", CHECK-NEXT: "score": 0 CHECK-NEXT: }, CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"a\",\"Scope\":[]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"a\",\"Scope\":[]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "ax", CHECK-NEXT: "score": 0 CHECK-NEXT: }, CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"a\",\"Scope\":[]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"a\",\"Scope\":[]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "ay", CHECK-NEXT: "score": 0 diff --git a/nixd/tools/nixd/test/completion/select-lib.md b/nixd/tools/nixd/test/completion/select-lib.md index 8b42510e5..8cef5e456 100644 --- a/nixd/tools/nixd/test/completion/select-lib.md +++ b/nixd/tools/nixd/test/completion/select-lib.md @@ -52,7 +52,7 @@ lib.hel CHECK: "isIncomplete": false, CHECK-NEXT: "items": [ CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"hel\",\"Scope\":[\"lib\"]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"hel\",\"Scope\":[\"lib\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "hello", CHECK-NEXT: "score": 0 diff --git a/nixd/tools/nixd/test/completion/select.md b/nixd/tools/nixd/test/completion/select.md index d68f279e9..e61bfc47b 100644 --- a/nixd/tools/nixd/test/completion/select.md +++ b/nixd/tools/nixd/test/completion/select.md @@ -52,7 +52,7 @@ pkgs.hel CHECK: "isIncomplete": false, CHECK-NEXT: "items": [ CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"hel\",\"Scope\":[]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"hel\",\"Scope\":[]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "hello", CHECK-NEXT: "score": 0 diff --git a/nixd/tools/nixd/test/completion/with-expr-select.md b/nixd/tools/nixd/test/completion/with-expr-select.md index f0ae8d107..081c88c4d 100644 --- a/nixd/tools/nixd/test/completion/with-expr-select.md +++ b/nixd/tools/nixd/test/completion/with-expr-select.md @@ -46,13 +46,13 @@ with pkgs.ax; [ b ] ``` ``` - CHECK: "data": "{\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", + CHECK: "data": "{\"MaxItems\":null,\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "bx", CHECK-NEXT: "score": 0 CHECK-NEXT: }, CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "by", CHECK-NEXT: "score": 0 diff --git a/nixd/tools/nixd/test/completion/with-select.md b/nixd/tools/nixd/test/completion/with-select.md index 35c072ef6..b8fc41ee8 100644 --- a/nixd/tools/nixd/test/completion/with-select.md +++ b/nixd/tools/nixd/test/completion/with-select.md @@ -52,13 +52,13 @@ CHECK-NEXT: "result": { CHECK-NEXT: "isIncomplete": false, CHECK-NEXT: "items": [ CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "bx", CHECK-NEXT: "score": 0 CHECK-NEXT: }, CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "by", CHECK-NEXT: "score": 0 diff --git a/subprojects/common b/subprojects/common new file mode 120000 index 000000000..60d3b0a6a --- /dev/null +++ b/subprojects/common @@ -0,0 +1 @@ +../common \ No newline at end of file diff --git a/subprojects/libnixf b/subprojects/libnixf new file mode 120000 index 000000000..6961983fe --- /dev/null +++ b/subprojects/libnixf @@ -0,0 +1 @@ +../libnixf \ No newline at end of file diff --git a/subprojects/libnixt b/subprojects/libnixt new file mode 120000 index 000000000..7b39a8328 --- /dev/null +++ b/subprojects/libnixt @@ -0,0 +1 @@ +../libnixt \ No newline at end of file diff --git a/subprojects/nixd b/subprojects/nixd new file mode 120000 index 000000000..b097c357a --- /dev/null +++ b/subprojects/nixd @@ -0,0 +1 @@ +../nixd \ No newline at end of file