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
18#include <ranges>
19
20namespace nixd {
21
24 const std::string &FileURI, llvm::StringRef Src,
25 std::vector<lspserver::CodeAction> &Actions) {
26 // Find ExprVar node (cursor might be on Identifier child)
27 const nixf::Node *VarNode = PM.upTo(N, nixf::Node::NK_ExprVar);
28 if (!VarNode)
29 return;
30 const auto &Var = static_cast<const nixf::ExprVar &>(*VarNode);
31
32 // Check if variable is undefined using VLA
33 auto Result = VLA.query(Var);
35 return;
36
37 // Find enclosing lambda using PM.upTo()
38 const nixf::Node *LambdaNode = PM.upTo(N, nixf::Node::NK_ExprLambda);
39 if (!LambdaNode)
40 return;
41
42 const auto &Lambda = static_cast<const nixf::ExprLambda &>(*LambdaNode);
43
44 // Check if lambda has formals (not simple `x: body` style)
45 if (!Lambda.arg() || !Lambda.arg()->formals())
46 return;
47
48 const nixf::Formals &Formals = *Lambda.arg()->formals();
49 const std::string &VarName = Var.id().name();
50
51 // Check variable doesn't already exist in formals
52 if (Formals.dedup().contains(VarName))
53 return;
54
55 // Determine insertion point
56 // There are 4 cases to handle:
57 // 1. Empty formals `{ }:` -> insert varName after `{`
58 // 2. Normal `{ a }:` -> insert `, varName` after last formal
59 // 3. Ellipsis only `{ ... }:` -> insert `varName, ` before `...`
60 // 4. With ellipsis `{ a, ... }:` -> insert `, varName` after last
61 // non-ellipsis formal
62
63 std::string NewText;
64 lspserver::Range InsertRange;
65
66 const auto &Members = Formals.members();
67
68 if (Members.empty()) {
69 // Case 1: Empty formals `{ }:`
70 // Insert right after the opening brace `{`
71 size_t BraceOffset = Formals.lCur().offset();
72 size_t InsertOffset = BraceOffset + 1;
73 auto InsertCursor = nixf::LexerCursor::unsafeCreate(
74 Formals.lCur().line(), Formals.lCur().column() + 1, InsertOffset);
75 auto InsertPos = toLSPPosition(Src, InsertCursor);
76 InsertRange = lspserver::Range{InsertPos, InsertPos};
77 NewText = " " + quoteNixAttrKey(VarName) + " ";
78 } else {
79 // Check if the last member is an ellipsis
80 const nixf::Formal *LastMember = Members.back().get();
81 bool HasEllipsis = LastMember && LastMember->isEllipsis();
82
83 if (HasEllipsis) {
84 if (Members.size() == 1) {
85 // Case 3: Ellipsis only `{ ... }:`
86 // Insert `varName, ` before the ellipsis
87 auto InsertPos = toLSPPosition(Src, LastMember->lCur());
88 InsertRange = lspserver::Range{InsertPos, InsertPos};
89 NewText = quoteNixAttrKey(VarName) + ", ";
90 } else {
91 // Case 4: With ellipsis `{ a, ... }:`
92 // Insert `, varName` after the last non-ellipsis formal
93 // Find the last non-ellipsis formal
94 const nixf::Formal *LastNonEllipsis = nullptr;
95 for (auto It = Members.rbegin(); It != Members.rend(); ++It) {
96 if ((*It) && !(*It)->isEllipsis()) {
97 LastNonEllipsis = (*It).get();
98 break;
99 }
100 }
101
102 if (!LastNonEllipsis)
103 return;
104
105 auto InsertPos = toLSPPosition(Src, LastNonEllipsis->rCur());
106 InsertRange = lspserver::Range{InsertPos, InsertPos};
107 NewText = ", " + quoteNixAttrKey(VarName);
108 }
109 } else {
110 // Case 2: Normal `{ a }:` without ellipsis
111 // Insert `, varName` after the last formal with an identifier.
112 const nixf::Formal *LastReal = nullptr;
113 for (const auto &Member : Members | std::views::reverse) {
114 if (Member && Member->id()) {
115 LastReal = Member.get();
116 break;
117 }
118 }
119
120 if (!LastReal)
121 return;
122
123 auto InsertPos = toLSPPosition(Src, LastReal->rCur());
124 InsertRange = lspserver::Range{InsertPos, InsertPos};
125 NewText = ", " + quoteNixAttrKey(VarName);
126 }
127 }
128
129 // Create CodeAction
130 std::string Title = "add `" + VarName + "` to formals";
131
132 std::vector<lspserver::TextEdit> Edits;
133 Edits.emplace_back(
134 lspserver::TextEdit{.range = InsertRange, .newText = std::move(NewText)});
135
136 using Changes = std::map<std::string, std::vector<lspserver::TextEdit>>;
137 lspserver::WorkspaceEdit WE{.changes = Changes{{FileURI, std::move(Edits)}}};
138
140 Action.title = std::move(Title);
141 Action.kind = std::string(lspserver::CodeAction::QUICKFIX_KIND);
142 Action.isPreferred = true;
143 Action.edit = std::move(WE);
144
145 Actions.emplace_back(std::move(Action));
146}
147
148} // 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:102
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