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);
158std::vector<const nixf::Binding *>
159collectSiblingBindings(
const nixf::Binds &Binds,
const std::string &FirstSeg) {
160 std::vector<const nixf::Binding *> Result;
161 for (
const auto &Sibling : Binds.
bindings()) {
162 if (Sibling->kind() != nixf::Node::NK_Binding)
165 const auto &SibBind =
static_cast<const nixf::Binding &
>(*Sibling);
166 const auto &SibNames = SibBind.path().names();
168 if (SibNames.empty() || !SibNames[0]->isStatic())
171 if (SibNames[0]->staticName() == FirstSeg)
172 Result.push_back(&SibBind);
180 const std::string &FileURI, llvm::StringRef Src,
181 std::vector<lspserver::CodeAction> &Actions) {
183 const nixf::Node *BindingNode = PM.
upTo(N, nixf::Node::NK_Binding);
187 const auto &Bind =
static_cast<const nixf::Binding &
>(*BindingNode);
191 if (Names.size() < 2)
195 for (
const auto &Name : Names) {
196 if (!Name->isStatic())
202 if (!BindsNode || BindsNode->
kind() != nixf::Node::NK_Binds)
206 if (!AttrsNode || AttrsNode->
kind() != nixf::Node::NK_ExprAttrs)
209 const auto &ParentAttrs =
static_cast<const nixf::ExprAttrs &
>(*AttrsNode);
213 const std::string &FirstSeg = Names[0]->staticName();
214 size_t SiblingCount = getSiblingCount(Bind, ParentAttrs);
216 if (SiblingCount == 0)
220 auto GeneratePackOneText = [&]() -> std::string {
222 const std::string_view FirstName = Names[0]->src(Src);
224 size_t ValueSize = Bind.
value() ? Bind.
value()->src(Src).size() : 0;
225 NewText.reserve(FirstName.size() + Bind.
path().
src(Src).size() + ValueSize +
227 NewText += FirstName;
237 std::string_view RestPath =
244 NewText += Bind.
value()->src(Src);
250 if (SiblingCount == 1) {
252 std::string NewText = GeneratePackOneText();
257 "Pack dotted path to nested set",
268 if (!Attr.
value() || Attr.
value()->
kind() != nixf::Node::NK_ExprAttrs)
271 const auto &NestedAttrs =
277 const auto &ParentBinds =
static_cast<const nixf::Binds &
>(*BindsNode);
278 auto Matches = collectSiblingBindings(ParentBinds, FirstSeg);
283 std::string PackOneText = GeneratePackOneText();
284 if (!PackOneText.empty()) {
286 "Pack dotted path to nested set",
293 auto BuildBulkEdits =
294 [&](std::string PackedText) -> std::vector<lspserver::TextEdit> {
295 std::vector<lspserver::TextEdit> Edits;
296 Edits.reserve(Matches.size());
298 .range =
toLSPRange(Src, Matches.front()->range()),
299 .newText = std::move(PackedText),
301 for (
size_t I = 1; I < Matches.size(); ++I) {
303 .range =
toLSPRange(Src, Matches[I]->range()),
311 std::string ShallowText;
313 ShallowText +=
" = ";
314 generateShallowNestedText(ParentBinds, FirstSeg, Src, ShallowText);
318 "Pack all '" + FirstSeg +
"' bindings to nested set",
320 BuildBulkEdits(std::move(ShallowText))));
323 std::string RecursiveText;
325 RecursiveText +=
" = ";
326 generateNestedText(NestedAttrs.sema(), Src, RecursiveText);
327 RecursiveText +=
";";
330 "Recursively pack all '" + FirstSeg +
"' bindings to nested set",
332 BuildBulkEdits(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)
lspserver::CodeAction createMultiEditAction(const std::string &Title, llvm::StringLiteral Kind, const std::string &FileURI, std::vector< lspserver::TextEdit > Edits)
Create a CodeAction with multiple text edits applied as one.
std::string quoteNixAttrKey(const std::string &Key)
Quote and escape a Nix attribute key if necessary.
static const llvm::StringLiteral REFACTOR_REWRITE_KIND