Skip to content

Commit

Permalink
nixd: provide trigger character "." (#428)
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Apr 18, 2024
1 parent 30e7a1b commit 47ff5f2
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 27 deletions.
7 changes: 6 additions & 1 deletion nixd/include/nixd/Controller/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@
namespace nixd {

class Controller : public lspserver::LSPServer {
public:
using OptionMapTy = std::map<std::string, std::unique_ptr<AttrSetClientProc>>;

private:
std::unique_ptr<OwnedEvalClient> Eval;

// Use this worker for evaluating nixpkgs.
std::unique_ptr<AttrSetClientProc> NixpkgsEval;

std::mutex OptionsLock;
// Map of option providers.
//
// e.g. "nixos" -> nixos worker
// "home-manager" -> home-manager worker
std::map<std::string, std::unique_ptr<AttrSetClientProc>> Options;
OptionMapTy Options; // GUARDED_BY(OptionsLock)

AttrSetClientProc &nixpkgsEval() {
assert(NixpkgsEval);
Expand Down
27 changes: 27 additions & 0 deletions nixd/lib/Controller/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,30 @@ nixd::getSelectAttrPath(const nixf::AttrName &N,
__builtin_unreachable();
}
}

std::optional<std::vector<std::string_view>>
nixd::findAttrPath(const nixf::Node &N, const nixf::ParentMapAnalysis &PM) {
if (const Node *Name = PM.upTo(N, Node::NK_AttrName)) {
std::vector<std::string_view> AttrPath;
if (const auto *Expr = PM.upExpr(N))
AttrPath = getValueAttrPath(*Expr, PM);
auto Select = getSelectAttrPath(static_cast<const AttrName &>(*Name), PM);
AttrPath.insert(AttrPath.end(), Select.begin(), Select.end());
assert(!AttrPath.empty());
return AttrPath;
}

// Consider this is an "extra" dot.
if (const Node *APNode = PM.upTo(N, Node::NK_AttrPath)) {
const auto &AP = static_cast<const AttrPath &>(*APNode);
std::vector<std::string_view> AttrPathVec;
if (const auto *Expr = PM.upExpr(N))
AttrPathVec = getValueAttrPath(*Expr, PM);
assert(!AP.names().empty());
auto Select = getSelectAttrPath(*AP.names().back(), PM);
AttrPathVec.insert(AttrPathVec.end(), Select.begin(), Select.end());
AttrPathVec.emplace_back("");
return AttrPathVec;
}
return std::nullopt;
}
7 changes: 7 additions & 0 deletions nixd/lib/Controller/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <nixf/Sema/ParentMap.h>
#include <nixf/Sema/VariableLookup.h>

#include <optional>

namespace nixd {

/// \brief Search up until there are some node associated with "EnvNode".
Expand Down Expand Up @@ -45,4 +47,9 @@ getValueAttrPath(const nixf::Node &N, const nixf::ParentMapAnalysis &PM);
std::vector<std::string_view>
getSelectAttrPath(const nixf::AttrName &N, const nixf::ParentMapAnalysis &PM);

/// \brief Heuristically find attrpath suitable for "attrpath" completion.
/// \returns non-empty std::vector attrpath.
std::optional<std::vector<std::string_view>>
findAttrPath(const nixf::Node &N, const nixf::ParentMapAnalysis &PM);

} // namespace nixd
43 changes: 22 additions & 21 deletions nixd/lib/Controller/Completion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,20 @@ class OptionCompletionProvider {
}
};

void completeAttrName(std::vector<std::string> Scope, std::string Prefix,
Controller::OptionMapTy &Options, bool CompletionSnippets,
std::vector<CompletionItem> &List) {
for (const auto &[Name, Provider] : Options) {
AttrSetClient *Client = Options.at(Name)->client();
if (!Client) [[unlikely]] {
elog("skipped client {0} as it is dead", Name);
continue;
}
OptionCompletionProvider OCP(*Client, Name, CompletionSnippets);
OCP.completeOptions(Scope, Prefix, List);
}
}

} // namespace

void Controller::onCompletion(const CompletionParams &Params,
Expand All @@ -246,32 +260,19 @@ void Controller::onCompletion(const CompletionParams &Params,
CompletionList List;
try {
const ParentMapAnalysis &PM = *TU->parentMap();

if (const Node *Name = PM.upTo(*Desc, Node::NK_AttrName)) {
// Complete attrpath.
std::vector<std::string_view> AttrPath;
if (const auto *Expr = PM.upExpr(*Desc))
AttrPath = getValueAttrPath(*Expr, PM);
auto Select =
getSelectAttrPath(static_cast<const AttrName &>(*Name), PM);
AttrPath.insert(AttrPath.end(), Select.begin(), Select.end());
assert(!AttrPath.empty());
if (auto AP = findAttrPath(*Desc, PM)) {
// Construct request.
std::vector<std::string> Scope;
Scope.reserve(AttrPath.size());
for (std::string_view Name : AttrPath) {
Scope.reserve(AP->size());
for (std::string_view Name : *AP) {
Scope.emplace_back(Name);
}
std::string Prefix = Scope.back();
Scope.pop_back();
for (const auto &[Name, Provider] : Options) {
AttrSetClient *Client = Options.at(Name)->client();
if (!Client) [[unlikely]] {
elog("skipped client {0} as it is dead", Name);
continue;
}
OptionCompletionProvider OCP(*Client, Name,
ClientCaps.CompletionSnippets);
OCP.completeOptions(Scope, Prefix, List.items);
{
std::lock_guard _(OptionsLock);
completeAttrName(std::move(Scope), std::move(Prefix), Options,
ClientCaps.CompletionSnippets, List.items);
}
} else {
const VariableLookupAnalysis &VLA = *TU->variableLookup();
Expand Down
1 change: 1 addition & 0 deletions nixd/lib/Controller/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ void Controller::updateConfig(Configuration NewConfig) {
}
}
if (!Config.options.empty()) {
std::lock_guard _(OptionsLock);
// Stop option workers that are not listed in config.
for (const auto &[Name, _] : Options) {
if (!Config.options.contains(Name)) {
Expand Down
11 changes: 7 additions & 4 deletions nixd/lib/Controller/LifeTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ void Controller::
{"completionProvider",
Object{
{"resolveProvider", true},
{"triggerCharacters", {"."}},
}},
{"referencesProvider", true},
{"documentHighlightProvider", true},
Expand Down Expand Up @@ -150,11 +151,13 @@ void Controller::
}

// Launch nixos worker also.
startOption("nixos", Options["nixos"]);

if (AttrSetClient *Client = Options["nixos"]->client())
evalExprWithProgress(*Client, DefaultNixOSOptionsExpr, "nixos options");
{
std::lock_guard _(OptionsLock);
startOption("nixos", Options["nixos"]);

if (AttrSetClient *Client = Options["nixos"]->client())
evalExprWithProgress(*Client, DefaultNixOSOptionsExpr, "nixos options");
}
fetchConfig();
}

Expand Down
74 changes: 74 additions & 0 deletions nixd/tools/nixd/test/completion-dot-select.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# RUN: nixd --lit-test \
# RUN: --nixos-options-expr="{ foo.bar = { _type = \"option\"; }; }" \
# RUN: < %s | 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:///completion.nix",
"languageId":"nix",
"version":1,
"text":"{ bar = 1; foo = foo.foo.; }"
}
}
}
```

```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/completion",
"params": {
"textDocument": {
"uri": "file:///completion.nix"
},
"position": {
"line": 0,
"character": 24
},
"context": {
"triggerKind": 1,
"triggerCharacter": "."
}
}
}
```

```
CHECK: "id": 1,
CHECK-NEXT: "jsonrpc": "2.0",
CHECK-NEXT: "result": {
CHECK-NEXT: "isIncomplete": false,
CHECK-NEXT: "items": []
CHECK-NEXT: }
```


```json
{"jsonrpc":"2.0","method":"exit"}
```
82 changes: 82 additions & 0 deletions nixd/tools/nixd/test/completion-dot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# RUN: nixd --lit-test \
# RUN: --nixos-options-expr="{ foo.bar = { _type = \"option\"; }; }" \
# RUN: < %s | 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:///completion.nix",
"languageId":"nix",
"version":1,
"text":"{ bar = 1; foo. }"
}
}
}
```

```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/completion",
"params": {
"textDocument": {
"uri": "file:///completion.nix"
},
"position": {
"line": 0,
"character": 14
},
"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: "detail": "nixos",
CHECK-NEXT: "kind": 7,
CHECK-NEXT: "label": "foo",
CHECK-NEXT: "score": 0
CHECK-NEXT: }
CHECK-NEXT: ]
CHECK-NEXT: }
```


```json
{"jsonrpc":"2.0","method":"exit"}
```
5 changes: 4 additions & 1 deletion nixd/tools/nixd/test/initialize.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ CHECK-NEXT: ],
CHECK-NEXT: "resolveProvider": false
CHECK-NEXT: },
CHECK-NEXT: "completionProvider": {
CHECK-NEXT: "resolveProvider": true
CHECK-NEXT: "resolveProvider": true,
CHECK-NEXT: "triggerCharacters": [
CHECK-NEXT: "."
CHECK-NEXT: ]
CHECK-NEXT: },
CHECK-NEXT: "definitionProvider": true,
CHECK-NEXT: "documentFormattingProvider": true,
Expand Down

0 comments on commit 47ff5f2

Please sign in to comment.