Skip to content

Commit

Permalink
nixd: support textDocument/inlayHint (#414)
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc authored Apr 15, 2024
1 parent f510da2 commit 2d71629
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 0 deletions.
4 changes: 4 additions & 0 deletions nixd/include/nixd/Controller/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ class Controller : public lspserver::LSPServer {
void onSemanticTokens(const lspserver::SemanticTokensParams &Params,
lspserver::Callback<lspserver::SemanticTokens> Reply);

void
onInlayHint(const lspserver::InlayHintsParams &Params,
lspserver::Callback<std::vector<lspserver::InlayHint>> Reply);

void onCompletion(const lspserver::CompletionParams &Params,
lspserver::Callback<lspserver::CompletionList> Reply);

Expand Down
4 changes: 4 additions & 0 deletions nixd/lib/Controller/Convert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ nixf::Position toNixfPosition(const lspserver::Position &P) {
return {P.line, P.character};
}

nixf::PositionRange toNixfRange(const lspserver::Range &P) {
return {toNixfPosition(P.start), toNixfPosition(P.end)};
}

lspserver::Range toLSPRange(const nixf::LexerCursorRange &R) {
return lspserver::Range{toLSPPosition(R.lCur()), toLSPPosition(R.rCur())};
}
Expand Down
2 changes: 2 additions & 0 deletions nixd/lib/Controller/Convert.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ lspserver::Position toLSPPosition(const nixf::LexerCursor &P);

nixf::Position toNixfPosition(const lspserver::Position &P);

nixf::PositionRange toNixfRange(const lspserver::Range &P);

lspserver::Range toLSPRange(const nixf::LexerCursorRange &R);

int getLSPSeverity(nixf::Diagnostic::DiagnosticKind Kind);
Expand Down
130 changes: 130 additions & 0 deletions nixd/lib/Controller/InlayHints.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/// \file
/// \brief Implementation of [Inlay Hints].
/// [Inlay Hints]:
/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint
///
/// In nixd, "Inlay Hints" are placed after each "package" node, showing it's
/// version.
///
/// For example
///
/// nixd[: 1.2.3]
/// nix[: 2.19.3]
///
///
#include "AST.h"
#include "Convert.h"

#include "nixd/CommandLine/Options.h"
#include "nixd/Controller/Controller.h"

#include <boost/asio/post.hpp>

#include <llvm/Support/CommandLine.h>

#include <semaphore>

using namespace nixd;
using namespace nixf;
using namespace lspserver;
using namespace llvm::cl;

namespace {

opt<bool> EnableInlayHints{"inlay-hints", desc("Enable/Disable inlay-hints"),
init(true), cat(NixdCategory)};

/// Ask nixpkgs provider to compute package information, to get inlay-hints.
class NixpkgsInlayHintsProvider {
AttrSetClient &NixpkgsProvider;
const VariableLookupAnalysis &VLA;
const ParentMapAnalysis &PMA;

/// Only positions contained in this range should be computed && added;
std::optional<nixf::PositionRange> Range;

std::vector<InlayHint> &Hints;

bool rangeOK(const nixf::PositionRange &R) {
if (!Range)
return true; // Always OK if there is no limitation.
return Range->contains(R);
}

public:
NixpkgsInlayHintsProvider(AttrSetClient &NixpkgsProvider,
const VariableLookupAnalysis &VLA,
const ParentMapAnalysis &PMA,
std::optional<lspserver::Range> Range,
std::vector<InlayHint> &Hints)
: NixpkgsProvider(NixpkgsProvider), VLA(VLA), PMA(PMA), Hints(Hints) {
if (Range)
this->Range = toNixfRange(*Range);
}

void dfs(const Node *N) {
if (!N)
return;
if (N->kind() == Node::NK_ExprVar) {
if (havePackageScope(*N, VLA, PMA)) {
if (!rangeOK(N->positionRange()))
return;
// Ask nixpkgs eval to provide it's information.
// This is relatively slow. Maybe better query a set of packages in the
// future?
std::binary_semaphore Ready(0);
const std::string &Name = static_cast<const ExprVar &>(*N).id().name();
AttrPathInfoResponse R;
auto OnReply = [&Ready, &R](llvm::Expected<AttrPathInfoResponse> Resp) {
if (!Resp) {
Ready.release();
return;
}
R = *Resp;
Ready.release();
};
NixpkgsProvider.attrpathInfo({Name}, std::move(OnReply));
Ready.acquire();

if (R.Version) {
// Construct inlay hints.
InlayHint H{
.position = toLSPPosition(N->rCur()),
.label = ": " + *R.Version,
.kind = InlayHintKind::Designator,
.range = toLSPRange(N->range()),
};
Hints.emplace_back(std::move(H));
}
}
}
// FIXME: process other node kinds. e.g. ExprSelect.
for (const Node *Ch : N->children())
dfs(Ch);
}
};

} // namespace

void Controller::onInlayHint(const InlayHintsParams &Params,
Callback<std::vector<InlayHint>> Reply) {
// If not enabled, exit early.
if (!EnableInlayHints)
return Reply(std::vector<InlayHint>{});

auto Action = [Reply = std::move(Reply), URI = Params.textDocument.uri,
Range = Params.range, this]() mutable {
std::string File(URI.file());
if (std::shared_ptr<NixTU> TU = getTU(File, Reply)) [[likely]] {
if (std::shared_ptr<Node> AST = getAST(*TU, Reply)) [[likely]] {
// Perform inlay hints computation on the range.
std::vector<InlayHint> Response;
NixpkgsInlayHintsProvider NP(*nixpkgsClient(), *TU->variableLookup(),
*TU->parentMap(), Range, Response);
NP.dfs(AST.get());
Reply(std::move(Response));
}
}
};
boost::asio::post(Pool, std::move(Action));
}
1 change: 1 addition & 0 deletions nixd/lib/Controller/LifeTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ void Controller::
{"full", true},
},
},
{"inlayHintProvider", true},
{"completionProvider",
Object{
{"resolveProvider", true},
Expand Down
1 change: 1 addition & 0 deletions nixd/lib/Controller/Support.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Controller::Controller(std::unique_ptr<lspserver::InboundPort> In,
&Controller::onDocumentSymbol);
Registry.addMethod("textDocument/semanticTokens/full", this,
&Controller::onSemanticTokens);
Registry.addMethod("textDocument/inlayHint", this, &Controller::onInlayHint);
Registry.addMethod("textDocument/completion", this,
&Controller::onCompletion);
Registry.addMethod("completionItem/resolve", this,
Expand Down
1 change: 1 addition & 0 deletions nixd/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ libnixd_lib = library(
'lib/Controller/EvalClient.cpp',
'lib/Controller/FindReferences.cpp',
'lib/Controller/Hover.cpp',
'lib/Controller/InlayHints.cpp',
'lib/Controller/LifeTime.cpp',
'lib/Controller/NixTU.cpp',
'lib/Controller/Rename.cpp',
Expand Down
1 change: 1 addition & 0 deletions nixd/tools/nixd/test/initialize.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ CHECK-NEXT: "definitionProvider": true,
CHECK-NEXT: "documentHighlightProvider": true,
CHECK-NEXT: "documentSymbolProvider": true,
CHECK-NEXT: "hoverProvider": true,
CHECK-NEXT: "inlayHintProvider": true,
CHECK-NEXT: "referencesProvider": true,
CHECK-NEXT: "renameProvider": {
CHECK-NEXT: "prepareProvider": true
Expand Down
80 changes: 80 additions & 0 deletions nixd/tools/nixd/test/inlay-hint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# RUN: nixd --lit-test \
# RUN: --nixpkgs-expr="{ hello.version = \"0.3.12\"; }" \
# RUN: < %s | FileCheck %s

<-- initialize(0)

```json
{
"jsonrpc":"2.0",
"id":0,
"method":"initialize",
"params":{
"processId":123,
"rootPath":"",
"capabilities":{
},
"trace":"off"
}
}
```

```json
{
"jsonrpc":"2.0",
"method":"textDocument/didOpen",
"params":{
"textDocument":{
"uri":"file:///basic.nix",
"languageId":"nix",
"version":1,
"text":"with pkgs; [ hello ]"
}
}
}
```



```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/inlayHint",
"params": {
"textDocument":{
"uri":"file:///basic.nix"
},
"range": {
"start":{
"line": 0,
"character":0
},
"end":{
"line":0,
"character":20
}
}
}
}
```

```
CHECK: "id": 1,
CHECK-NEXT: "jsonrpc": "2.0",
CHECK-NEXT: "result": [
CHECK-NEXT: {
CHECK-NEXT: "label": ": 0.3.12",
CHECK-NEXT: "paddingLeft": false,
CHECK-NEXT: "paddingRight": false,
CHECK-NEXT: "position": {
CHECK-NEXT: "character": 18,
CHECK-NEXT: "line": 0
CHECK-NEXT: }
CHECK-NEXT: }
```


```json
{"jsonrpc":"2.0","method":"exit"}
```

0 comments on commit 2d71629

Please sign in to comment.