nixd
Loading...
Searching...
No Matches
FoldingRange.cpp
Go to the documentation of this file.
1/// \file
2/// \brief Implementation of [Folding Range].
3/// [Folding Range]:
4/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_foldingRange
5
6#include "CheckReturn.h"
7#include "Convert.h"
8
10
11#include <boost/asio/post.hpp>
12#include <lspserver/Logger.h>
13#include <lspserver/Protocol.h>
18
19using namespace nixd;
20using namespace lspserver;
21using namespace nixf;
22
23namespace {
24
25/// Maximum recursion depth to prevent stack overflow on deeply nested ASTs.
26constexpr size_t MaxRecursionDepth = 256;
27
28/// Check if the range spans multiple lines (2+ lines required for folding).
29bool isMultiLine(const lspserver::Range &R) {
30 return R.start.line < R.end.line;
31}
32
33/// Create a FoldingRange from an LSP Range with "region" kind.
34FoldingRange toFoldingRange(const lspserver::Range &R) {
35 FoldingRange FR;
36 FR.startLine = R.start.line;
38 FR.endLine = R.end.line;
41 return FR;
42}
43
44/// Add a folding range if the node spans multiple lines.
45void addFoldingRange(const Node &N, std::vector<FoldingRange> &Ranges,
46 llvm::StringRef Src) {
47 auto R = toLSPRange(Src, N.range());
48 if (isMultiLine(R))
49 Ranges.emplace_back(toFoldingRange(R));
50}
51
52/// Collect folding ranges from AST nodes recursively.
53/// \param AST The AST node to process
54/// \param Ranges Output vector to collect folding ranges
55/// \param Src Source text for position conversion
56/// \param Depth Current recursion depth (for stack overflow protection)
57void collectFoldingRanges(const Node *AST, std::vector<FoldingRange> &Ranges,
58 llvm::StringRef Src, size_t Depth = 0) {
59 if (!AST || Depth >= MaxRecursionDepth)
60 return;
61
62 switch (AST->kind()) {
63 case Node::NK_ExprAttrs: {
64 addFoldingRange(*AST, Ranges, Src);
65 const auto &Attrs = static_cast<const ExprAttrs &>(*AST);
66 if (Attrs.binds()) {
67 for (const Node *Ch : Attrs.binds()->children())
68 collectFoldingRanges(Ch, Ranges, Src, Depth + 1);
69 }
70 break;
71 }
72 case Node::NK_ExprList: {
73 addFoldingRange(*AST, Ranges, Src);
74 for (const Node *Ch : AST->children())
75 collectFoldingRanges(Ch, Ranges, Src, Depth + 1);
76 break;
77 }
78 case Node::NK_ExprLambda: {
79 addFoldingRange(*AST, Ranges, Src);
80 const auto &Lambda = static_cast<const ExprLambda &>(*AST);
81 if (const auto *Body = Lambda.body())
82 collectFoldingRanges(Body, Ranges, Src, Depth + 1);
83 break;
84 }
85 case Node::NK_ExprLet: {
86 addFoldingRange(*AST, Ranges, Src);
87 const auto &Let = static_cast<const ExprLet &>(*AST);
88 if (const auto *Attrs = Let.attrs())
89 collectFoldingRanges(Attrs, Ranges, Src, Depth + 1);
90 if (const auto *E = Let.expr())
91 collectFoldingRanges(E, Ranges, Src, Depth + 1);
92 break;
93 }
94 case Node::NK_ExprWith: {
95 addFoldingRange(*AST, Ranges, Src);
96 const auto &With = static_cast<const ExprWith &>(*AST);
97 if (const auto *W = With.with())
98 collectFoldingRanges(W, Ranges, Src, Depth + 1);
99 if (const auto *E = With.expr())
100 collectFoldingRanges(E, Ranges, Src, Depth + 1);
101 break;
102 }
103 case Node::NK_ExprIf: {
104 addFoldingRange(*AST, Ranges, Src);
105 const auto &If = static_cast<const ExprIf &>(*AST);
106 if (const auto *Cond = If.cond())
107 collectFoldingRanges(Cond, Ranges, Src, Depth + 1);
108 if (const auto *Then = If.then())
109 collectFoldingRanges(Then, Ranges, Src, Depth + 1);
110 if (const auto *Else = If.elseExpr())
111 collectFoldingRanges(Else, Ranges, Src, Depth + 1);
112 break;
113 }
114 case Node::NK_ExprString:
115 // Multiline strings are foldable regions
116 addFoldingRange(*AST, Ranges, Src);
117 break;
118 default:
119 // Other node types may contain foldable children, recurse into them
120 for (const Node *Ch : AST->children())
121 collectFoldingRanges(Ch, Ranges, Src, Depth + 1);
122 break;
123 }
124}
125
126} // namespace
127
128/// \brief Handle textDocument/foldingRange LSP request.
129///
130/// Collects foldable regions from the document's AST including attribute sets,
131/// lists, lambdas, let/with/if expressions, and multiline strings.
132///
133/// \param Params Request parameters containing the document URI
134/// \param Reply Callback to return the list of folding ranges
135void Controller::onFoldingRange(const FoldingRangeParams &Params,
136 Callback<std::vector<FoldingRange>> Reply) {
137 using CheckTy = std::vector<FoldingRange>;
138 auto Action = [Reply = std::move(Reply), URI = Params.textDocument.uri,
139 this]() mutable {
140 return Reply([&]() -> llvm::Expected<CheckTy> {
141 const auto TU = CheckDefault(getTU(URI.file().str()));
142 const auto AST = CheckDefault(getAST(*TU));
143 try {
144 auto Ranges = std::vector<FoldingRange>();
145 collectFoldingRanges(AST.get(), Ranges, TU->src());
146 return Ranges;
147 } catch (std::exception &E) {
148 elog("textDocument/foldingRange failed: {0}", E.what());
149 return CheckTy{};
150 }
151 }());
152 };
153 boost::asio::post(Pool, std::move(Action));
154}
#define CheckDefault(x)
Variant of CheckReturn, but returns default constructed CheckTy.
Definition CheckReturn.h:16
Convert between LSP and nixf types.
NodeKind kind() const
Definition Basic.h:34
std::string_view src(std::string_view Src) const
Definition Basic.h:63
LexerCursorRange range() const
Definition Basic.h:35
virtual ChildVector children() const =0
Whether current platform treats paths case insensitively.
Definition Connection.h:11
llvm::unique_function< void(llvm::Expected< T >)> Callback
Definition Function.h:14
void elog(const char *Fmt, Ts &&...Vals)
Definition Logger.h:52
lspserver::Range toLSPRange(llvm::StringRef Code, const nixf::LexerCursorRange &R)
Definition Convert.cpp:40
Stores information about a region of code that can be folded.
static const llvm::StringLiteral REGION_KIND
int64_t line
Line position in a document (zero-based).
Position start
The range's start position.
Position end
The range's end position.