Skip to content

Commit

Permalink
nixd: support textDocument/rename, textDocument/prepareRename (#405)
Browse files Browse the repository at this point in the history
Fixes: #255
  • Loading branch information
inclyc authored Apr 9, 2024
1 parent 94c9ba8 commit 3b6e46f
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 3 deletions.
3 changes: 3 additions & 0 deletions libnixf/include/nixf/Sema/VariableLookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class VariableLookupAnalysis {
Undefined,
FromWith,
Defined,
NoSuchVar,
};

struct LookupResult {
Expand Down Expand Up @@ -129,6 +130,8 @@ class VariableLookupAnalysis {

/// \brief Query the which name/with binds to specific varaible.
[[nodiscard]] LookupResult query(const ExprVar &Var) const {
if (!Results.contains(&Var))
return {.Kind = LookupResultKind::NoSuchVar};
return Results.at(&Var);
}

Expand Down
6 changes: 6 additions & 0 deletions nixd/include/nixd/Controller/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ class Controller : public lspserver::LSPServer {
std::optional<int64_t> Version,
const std::vector<nixf::Diagnostic> &Diagnostics);

void onRename(const lspserver::RenameParams &Params,
lspserver::Callback<lspserver::WorkspaceEdit> Reply);

void onPrepareRename(const lspserver::TextDocumentPositionParams &Params,
lspserver::Callback<lspserver::Range> Reply);

public:
Controller(std::unique_ptr<lspserver::InboundPort> In,
std::unique_ptr<lspserver::OutboundPort> Out);
Expand Down
7 changes: 5 additions & 2 deletions nixd/lib/Controller/Definition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,14 @@ nixd::findDefinition(const Node &N, const ParentMapAnalysis &PMA,
assert(Var->kind() == Node::NK_ExprVar);
LookupResult Result = VLA.query(static_cast<const ExprVar &>(*Var));

assert(Result.Def);

if (Result.Kind == ResultKind::Undefined)
return error("this varaible is undefined");

if (Result.Kind == ResultKind::NoSuchVar)
return error("this varaible is not used in var lookup (duplicated attr?)");

assert(Result.Def);

if (Result.Def->isBuiltin())
return error("this is a builtin variable");

Expand Down
6 changes: 5 additions & 1 deletion nixd/lib/Controller/LifeTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ void Controller::
{"definitionProvider", true},
{"referencesProvider", true},
{"documentHighlightProvider", true},
{"hoverProvider", true}},
{"hoverProvider", true},
{"renameProvider",
Object{
{"prepareProvider", true},
}}},
};

Object Result{{
Expand Down
98 changes: 98 additions & 0 deletions nixd/lib/Controller/Rename.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/// \file
/// \brief This implements [Rename].
/// [Rename]:
/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename

#include "Convert.h"
#include "Definition.h"

#include "lspserver/Protocol.h"
#include "nixd/Controller/Controller.h"

#include <boost/asio/post.hpp>

using namespace lspserver;
using namespace nixd;
using namespace llvm;
using namespace nixf;

namespace {
llvm::Expected<WorkspaceEdit> rename(const nixf::Node &Desc,
const std::string &NewText,
const ParentMapAnalysis &PMA,
const VariableLookupAnalysis &VLA,
const URIForFile &URI) {
using lspserver::TextEdit;
// Find "definition"
auto Def = findDefinition(Desc, PMA, VLA);
if (!Def)
return Def.takeError();

std::vector<TextEdit> Edits;

for (const auto *Use : Def->uses()) {
Edits.emplace_back(TextEdit{
.range = toLSPRange(Use->range()),
.newText = NewText,
});
}

Edits.emplace_back(TextEdit{
.range = toLSPRange(Def->syntax()->range()),
.newText = NewText,
});
WorkspaceEdit WE;
WE.changes = std::map<std::string, std::vector<TextEdit>>{
{URI.uri(), std::move(Edits)}};
return WE;
}
} // namespace

void Controller::onRename(const RenameParams &Params,
Callback<WorkspaceEdit> Reply) {
auto Action = [Reply = std::move(Reply), URI = Params.textDocument.uri,
Pos = toNixfPosition(Params.position),
NewText = Params.newName, this]() mutable {
std::string File(URI.file());
if (std::shared_ptr<NixTU> TU = getTU(File, Reply)) [[likely]] {
if (std::shared_ptr<nixf::Node> AST = getAST(*TU, Reply)) [[likely]] {
const nixf::Node *Desc = AST->descend({Pos, Pos});
if (!Desc) {
Reply(error("cannot find corresponding node on given position"));
return;
}
Reply(rename(*Desc, NewText, *TU->parentMap(), *TU->variableLookup(),
URI));
return;
}
}
};
boost::asio::post(Pool, std::move(Action));
}

void Controller::onPrepareRename(
const lspserver::TextDocumentPositionParams &Params,
Callback<lspserver::Range> Reply) {
auto Action = [Reply = std::move(Reply), URI = Params.textDocument.uri,
Pos = toNixfPosition(Params.position), this]() mutable {
std::string File(URI.file());
if (std::shared_ptr<NixTU> TU = getTU(File, Reply)) [[likely]] {
if (std::shared_ptr<nixf::Node> AST = getAST(*TU, Reply)) [[likely]] {
const nixf::Node *Desc = AST->descend({Pos, Pos});
if (!Desc) {
Reply(error("cannot find corresponding node on given position"));
return;
}
llvm::Expected<WorkspaceEdit> Exp =
rename(*Desc, "", *TU->parentMap(), *TU->variableLookup(), URI);
if (Exp) {
Reply(toLSPRange(Desc->range()));
return;
}
Reply(Exp.takeError());
return;
}
}
};
boost::asio::post(Pool, std::move(Action));
}
3 changes: 3 additions & 0 deletions nixd/lib/Controller/Support.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ Controller::Controller(std::unique_ptr<lspserver::InboundPort> In,
Registry.addMethod("textDocument/codeAction", this,
&Controller::onCodeAction);
Registry.addMethod("textDocument/hover", this, &Controller::onHover);
Registry.addMethod("textDocument/rename", this, &Controller::onRename);
Registry.addMethod("textDocument/prepareRename", this,
&Controller::onPrepareRename);
}

} // namespace nixd
1 change: 1 addition & 0 deletions nixd/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ libnixd_lib = library(
'lib/Controller/Hover.cpp',
'lib/Controller/LifeTime.cpp',
'lib/Controller/NixTU.cpp',
'lib/Controller/Rename.cpp',
'lib/Controller/Support.cpp',
'lib/Controller/TextDocumentSync.cpp',
'lib/Eval/EvalProvider.cpp',
Expand Down
3 changes: 3 additions & 0 deletions nixd/tools/nixd/test/initialize.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ CHECK-NEXT: "definitionProvider": true,
CHECK-NEXT: "documentHighlightProvider": true,
CHECK-NEXT: "hoverProvider": true,
CHECK-NEXT: "referencesProvider": true,
CHECK-NEXT: "renameProvider": {
CHECK-NEXT: "prepareProvider": true
CHECK-NEXT: },
CHECK-NEXT: "textDocumentSync": {
CHECK-NEXT: "change": 2,
CHECK-NEXT: "openClose": true,
Expand Down
72 changes: 72 additions & 0 deletions nixd/tools/nixd/test/rename-duplicated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# RUN: nixd --lit-test < %s | FileCheck %s

// https://github.com/nix-community/nixd/issues/255

<-- 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:///basic.nix",
"languageId":"nix",
"version":1,
"text":"rec { bar = 1; a = 1; a = bar; b = a; }"
}
}
}
```

<-- textDocument/rename(2)


```json
{
"jsonrpc":"2.0",
"id":2,
"method":"textDocument/rename",
"params":{
"textDocument":{
"uri":"file:///basic.nix"
},
"position":{
"line": 0,
"character":28
},
"newName": "b"
}
}
```

```
CHECK: "error": {
CHECK-NEXT: "code": -32001,
CHECK-NEXT: "message": "this varaible is not used in var lookup (duplicated attr?)"
CHECK-NEXT: },
CHECK-NEXT: "id": 2,
CHECK-NEXT: "jsonrpc": "2.0"
```

```json
{"jsonrpc":"2.0","method":"exit"}
```
97 changes: 97 additions & 0 deletions nixd/tools/nixd/test/rename-issue-255-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# RUN: nixd --lit-test < %s | FileCheck %s

// https://github.com/nix-community/nixd/issues/255

<-- 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:///basic.nix",
"languageId":"nix",
"version":1,
"text":"{a ? 1}: { x = a; }"
}
}
}
```

<-- textDocument/rename(2)


```json
{
"jsonrpc":"2.0",
"id":2,
"method":"textDocument/rename",
"params":{
"textDocument":{
"uri":"file:///basic.nix"
},
"position":{
"line": 0,
"character":1
},
"newName": "b"
}
}
```

```
CHECK: "id": 2,
CHECK-NEXT: "jsonrpc": "2.0",
CHECK-NEXT: "result": {
CHECK-NEXT: "changes": {
CHECK-NEXT: "file:///basic.nix": [
CHECK-NEXT: {
CHECK-NEXT: "newText": "b",
CHECK-NEXT: "range": {
CHECK-NEXT: "end": {
CHECK-NEXT: "character": 16,
CHECK-NEXT: "line": 0
CHECK-NEXT: },
CHECK-NEXT: "start": {
CHECK-NEXT: "character": 15,
CHECK-NEXT: "line": 0
CHECK-NEXT: }
CHECK-NEXT: }
CHECK-NEXT: },
CHECK-NEXT: {
CHECK-NEXT: "newText": "b",
CHECK-NEXT: "range": {
CHECK-NEXT: "end": {
CHECK-NEXT: "character": 2,
CHECK-NEXT: "line": 0
CHECK-NEXT: },
CHECK-NEXT: "start": {
CHECK-NEXT: "character": 1,
CHECK-NEXT: "line": 0
CHECK-NEXT: }
CHECK-NEXT: }
CHECK-NEXT: }
```

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

0 comments on commit 3b6e46f

Please sign in to comment.