19constexpr size_t MaxNestedDepth = 100;
24size_t getSiblingCount(
const nixf::Binding &Bind,
25 const nixf::ExprAttrs &ParentAttrs) {
27 if (Names.empty() || !Names[0]->isStatic())
30 const std::string &FirstSeg = Names[0]->staticName();
31 const nixf::SemaAttrs &SA = ParentAttrs.
sema();
37 const nixf::Attribute &Attr = It->second;
40 if (!Attr.
value() || Attr.
value()->
kind() != nixf::Node::NK_ExprAttrs)
43 const auto &NestedAttrs =
static_cast<const nixf::ExprAttrs &
>(*Attr.
value());
44 const nixf::SemaAttrs &NestedSA = NestedAttrs.sema();
56void generateNestedText(
const nixf::SemaAttrs &SA, llvm::StringRef Src,
57 std::string &Out,
size_t Depth = 0) {
59 if (Depth > MaxNestedDepth) {
60 Out +=
"{ /* max depth exceeded */ }";
76 if (Attr.
value() && Attr.
value()->
kind() == nixf::Node::NK_ExprAttrs) {
77 const auto &NestedAttrs =
78 static_cast<const nixf::ExprAttrs &
>(*Attr.
value());
79 const nixf::SemaAttrs &NestedSA = NestedAttrs.sema();
84 generateNestedText(NestedSA, Src, Out, Depth + 1);
89 }
else if (Attr.
value()) {
104void generateShallowNestedText(
const nixf::Binds &Binds,
105 const std::string &FirstSeg, llvm::StringRef Src,
110 for (
const auto &Child : Binds.
bindings()) {
111 if (Child->kind() != nixf::Node::NK_Binding)
114 const auto &SibBind =
static_cast<const nixf::Binding &
>(*Child);
115 const auto &Names = SibBind.path().names();
118 if (Names.empty() || !Names[0]->isStatic() ||
119 Names[0]->staticName() != FirstSeg)
126 if (Names.size() == 1) {
131 if (SibBind.value()) {
132 Out += SibBind.value()->src(Src);
137 const nixf::LexerCursor RestStart = Names[1]->range().lCur();
138 const nixf::LexerCursor PathEnd = SibBind.path().range().rCur();
141 std::string_view RestPath = Src.substr(
145 if (SibBind.value()) {
146 Out += SibBind.value()->src(Src);
157std::optional<nixf::LexerCursorRange>
158findSiblingBindingsRange(
const nixf::Binding &Bind,
const nixf::Binds &Binds,
159 const std::string &FirstSeg) {
160 nixf::LexerCursor Start = Bind.
range().
lCur();
161 nixf::LexerCursor End = Bind.
range().
rCur();
163 for (
const auto &Sibling : Binds.
bindings()) {
164 if (Sibling->kind() != nixf::Node::NK_Binding)
167 const auto &SibBind =
static_cast<const nixf::Binding &
>(*Sibling);
168 const auto &SibNames = SibBind.path().names();
170 if (SibNames.empty() || !SibNames[0]->isStatic())
173 if (SibNames[0]->staticName() == FirstSeg) {
175 if (SibBind.range().lCur().offset() < Start.
offset())
176 Start = SibBind.range().lCur();
177 if (SibBind.range().rCur().offset() > End.
offset())
178 End = SibBind.range().rCur();
182 return nixf::LexerCursorRange{Start, End};
188 const std::string &FileURI, llvm::StringRef Src,
189 std::vector<lspserver::CodeAction> &Actions) {
191 const nixf::Node *BindingNode = PM.
upTo(N, nixf::Node::NK_Binding);
195 const auto &Bind =
static_cast<const nixf::Binding &
>(*BindingNode);
199 if (Names.size() < 2)
203 for (
const auto &Name : Names) {
204 if (!Name->isStatic())
210 if (!BindsNode || BindsNode->
kind() != nixf::Node::NK_Binds)
214 if (!AttrsNode || AttrsNode->
kind() != nixf::Node::NK_ExprAttrs)
217 const auto &ParentAttrs =
static_cast<const nixf::ExprAttrs &
>(*AttrsNode);
221 const std::string &FirstSeg = Names[0]->staticName();
222 size_t SiblingCount = getSiblingCount(Bind, ParentAttrs);
224 if (SiblingCount == 0)
228 auto GeneratePackOneText = [&]() -> std::string {
230 const std::string_view FirstName = Names[0]->src(Src);
232 size_t ValueSize = Bind.
value() ? Bind.
value()->src(Src).size() : 0;
233 NewText.reserve(FirstName.size() + Bind.
path().
src(Src).size() + ValueSize +
235 NewText += FirstName;
245 std::string_view RestPath =
252 NewText += Bind.
value()->src(Src);
258 if (SiblingCount == 1) {
260 std::string NewText = GeneratePackOneText();
265 "Pack dotted path to nested set",
276 if (!Attr.
value() || Attr.
value()->
kind() != nixf::Node::NK_ExprAttrs)
279 const auto &NestedAttrs =
283 const auto &ParentBinds =
static_cast<const nixf::Binds &
>(*BindsNode);
284 auto BulkRange = findSiblingBindingsRange(Bind, ParentBinds, FirstSeg);
289 std::string PackOneText = GeneratePackOneText();
290 if (!PackOneText.empty()) {
292 "Pack dotted path to nested set",
298 std::string ShallowText;
300 ShallowText +=
" = ";
301 generateShallowNestedText(ParentBinds, FirstSeg, Src, ShallowText);
305 "Pack all '" + FirstSeg +
"' bindings to nested set",
307 toLSPRange(Src, *BulkRange), std::move(ShallowText)));
310 std::string RecursiveText;
312 RecursiveText +=
" = ";
313 generateNestedText(NestedAttrs.sema(), Src, RecursiveText);
314 RecursiveText +=
";";
317 "Recursively pack all '" + FirstSeg +
"' bindings to nested set",
319 toLSPRange(Src, *BulkRange), std::move(RecursiveText)));
Convert between LSP and nixf types.
Code action for packing dotted attribute paths into nested sets.
Shared utilities for code actions.
const std::vector< std::shared_ptr< AttrName > > & names() const
const AttrPath & path() const
const std::shared_ptr< Expr > & value() const
const std::vector< std::shared_ptr< Node > > & bindings() const
const SemaAttrs & sema() const
A point in the source file.
std::size_t offset() const
Offset in the source file, starting from 0.
std::string_view src(std::string_view Src) const
LexerCursorRange range() const
const Node * upTo(const Node &N, Node::NodeKind Kind) const
Search up until some kind of node is found.
const Node * query(const Node &N) const
Attribute set after deduplication.
const std::vector< Attribute > & dynamicAttrs() const
Dynamic attributes, require evaluation to get the key.
const std::map< std::string, Attribute > & staticAttrs() const
Static attributes, do not require evaluation to get the key.
void addPackAttrsAction(const nixf::Node &N, const nixf::ParentMapAnalysis &PM, const std::string &FileURI, llvm::StringRef Src, std::vector< lspserver::CodeAction > &Actions)
Add pack action for dotted attribute paths.
lspserver::CodeAction createSingleEditAction(const std::string &Title, llvm::StringLiteral Kind, const std::string &FileURI, const lspserver::Range &EditRange, std::string NewText)
Create a CodeAction with a single text edit.
lspserver::Range toLSPRange(llvm::StringRef Code, const nixf::LexerCursorRange &R)
std::string quoteNixAttrKey(const std::string &Key)
Quote and escape a Nix attribute key if necessary.
static const llvm::StringLiteral REFACTOR_REWRITE_KIND