nixd
Loading...
Searching...
No Matches
AddToFormals.cpp
Go to the documentation of this file.
1/// \file
2/// \brief Implementation of "Add to formals" code action.
3///
4/// Adds undefined variables to the formals of the enclosing lambda expression.
5/// This is useful when a variable is used in a lambda body but not declared
6/// in the formals list.
7
8#include "AddToFormals.h"
9#include "Utils.h"
10
11#include "../Convert.h"
12
15#include <nixf/Sema/ParentMap.h>
17
18namespace nixd {
19
22 const std::string &FileURI, llvm::StringRef Src,
23 std::vector<lspserver::CodeAction> &Actions) {
24 // Find ExprVar node (cursor might be on Identifier child)
25 const nixf::Node *VarNode = PM.upTo(N, nixf::Node::NK_ExprVar);
26 if (!VarNode)
27 return;
28 const auto &Var = static_cast<const nixf::ExprVar &>(*VarNode);
29
30 // Check if variable is undefined using VLA
31 auto Result = VLA.query(Var);
33 return;
34
35 // Find enclosing lambda using PM.upTo()
36 const nixf::Node *LambdaNode = PM.upTo(N, nixf::Node::NK_ExprLambda);
37 if (!LambdaNode)
38 return;
39
40 const auto &Lambda = static_cast<const nixf::ExprLambda &>(*LambdaNode);
41
42 // Check if lambda has formals (not simple `x: body` style)
43 if (!Lambda.arg() || !Lambda.arg()->formals())
44 return;
45
46 const nixf::Formals &Formals = *Lambda.arg()->formals();
47 const std::string &VarName = Var.id().name();
48
49 // Check variable doesn't already exist in formals
50 if (Formals.dedup().contains(VarName))
51 return;
52
53 // Determine insertion point
54 // There are 4 cases to handle:
55 // 1. Empty formals `{ }:` -> insert varName after `{`
56 // 2. Normal `{ a }:` -> insert `, varName` after last formal
57 // 3. Ellipsis only `{ ... }:` -> insert `varName, ` before `...`
58 // 4. With ellipsis `{ a, ... }:` -> insert `, varName` after last
59 // non-ellipsis formal
60
61 std::string NewText;
62 lspserver::Range InsertRange;
63
64 const auto &Members = Formals.members();
65
66 if (Members.empty()) {
67 // Case 1: Empty formals `{ }:`
68 // Insert right after the opening brace `{`
69 size_t BraceOffset = Formals.lCur().offset();
70 size_t InsertOffset = BraceOffset + 1;
71 auto InsertCursor = nixf::LexerCursor::unsafeCreate(
72 Formals.lCur().line(), Formals.lCur().column() + 1, InsertOffset);
73 auto InsertPos = toLSPPosition(Src, InsertCursor);
74 InsertRange = lspserver::Range{InsertPos, InsertPos};
75 NewText = " " + quoteNixAttrKey(VarName) + " ";
76 } else {
77 // Check if the last member is an ellipsis
78 const nixf::Formal *LastMember = Members.back().get();
79 bool HasEllipsis = LastMember && LastMember->isEllipsis();
80
81 if (HasEllipsis) {
82 if (Members.size() == 1) {
83 // Case 3: Ellipsis only `{ ... }:`
84 // Insert `varName, ` before the ellipsis
85 auto InsertPos = toLSPPosition(Src, LastMember->lCur());
86 InsertRange = lspserver::Range{InsertPos, InsertPos};
87 NewText = quoteNixAttrKey(VarName) + ", ";
88 } else {
89 // Case 4: With ellipsis `{ a, ... }:`
90 // Insert `, varName` after the last non-ellipsis formal
91 // Find the last non-ellipsis formal
92 const nixf::Formal *LastNonEllipsis = nullptr;
93 for (auto It = Members.rbegin(); It != Members.rend(); ++It) {
94 if ((*It) && !(*It)->isEllipsis()) {
95 LastNonEllipsis = (*It).get();
96 break;
97 }
98 }
99
100 if (!LastNonEllipsis)
101 return;
102
103 auto InsertPos = toLSPPosition(Src, LastNonEllipsis->rCur());
104 InsertRange = lspserver::Range{InsertPos, InsertPos};
105 NewText = ", " + quoteNixAttrKey(VarName);
106 }
107 } else {
108 // Case 2: Normal `{ a }:` without ellipsis
109 // Insert `, varName` after the last formal
110 auto InsertPos = toLSPPosition(Src, LastMember->rCur());
111 InsertRange = lspserver::Range{InsertPos, InsertPos};
112 NewText = ", " + quoteNixAttrKey(VarName);
113 }
114 }
115
116 // Create CodeAction
117 std::string Title = "add `" + VarName + "` to formals";
118
119 std::vector<lspserver::TextEdit> Edits;
120 Edits.emplace_back(
121 lspserver::TextEdit{.range = InsertRange, .newText = std::move(NewText)});
122
123 using Changes = std::map<std::string, std::vector<lspserver::TextEdit>>;
124 lspserver::WorkspaceEdit WE{.changes = Changes{{FileURI, std::move(Edits)}}};
125
127 Action.title = std::move(Title);
128 Action.kind = std::string(lspserver::CodeAction::QUICKFIX_KIND);
129 Action.isPreferred = true;
130 Action.edit = std::move(WE);
131
132 Actions.emplace_back(std::move(Action));
133}
134
135} // namespace nixd
Code action for adding undefined variables to lambda formals.
Convert between LSP and nixf types.
ParentMap analysis.
Shared utilities for code actions.
Lookup variable names, from it's parent scope.
bool isEllipsis() const
Definition Lambda.h:35
Lambda formal arguments.
Definition Lambda.h:58
const FormalVector & members() const
Definition Lambda.h:71
const std::map< std::string, const Formal * > & dedup()
Deduplicated formals.
Definition Lambda.h:74
int64_t column() const
Column number, starting from 0.
Definition Range.h:96
std::size_t offset() const
Offset in the source file, starting from 0.
Definition Range.h:102
static LexerCursor unsafeCreate(int64_t Line, int64_t Column, std::size_t Offset)
Create a cursor at the given position. (Line, Column, Offset).
Definition Range.h:74
int64_t line() const
Line number, starting from 0.
Definition Range.h:93
LexerCursor lCur() const
Definition Basic.h:37
static const char * name(NodeKind Kind)
Definition Nodes.cpp:35
LexerCursor rCur() const
Definition Basic.h:38
const Node * upTo(const Node &N, Node::NodeKind Kind) const
Search up until some kind of node is found.
Definition ParentMap.cpp:27
LookupResult query(const ExprVar &Var) const
Query the which name/with binds to specific varaible.
lspserver::Position toLSPPosition(llvm::StringRef Code, const nixf::LexerCursor &P)
Definition Convert.cpp:27
void addToFormalsAction(const nixf::Node &N, const nixf::ParentMapAnalysis &PM, const nixf::VariableLookupAnalysis &VLA, const std::string &FileURI, llvm::StringRef Src, std::vector< lspserver::CodeAction > &Actions)
Add code action to add undefined variable to lambda formals.
std::string quoteNixAttrKey(const std::string &Key)
Quote and escape a Nix attribute key if necessary.
Definition Utils.cpp:89
std::string title
A short, human-readable, title for this code action.
std::optional< WorkspaceEdit > edit
The workspace edit this code action performs.
static const llvm::StringLiteral QUICKFIX_KIND