1use php_ast::{ClassMemberKind, EnumMemberKind, NamespaceBody, Stmt, StmtKind};
2use tower_lsp_server::ls_types::{FoldingRange, FoldingRangeKind};
3
4use crate::document::ast::{ParsedDoc, SourceView};
5
6pub fn folding_ranges(_source: &str, doc: &ParsedDoc) -> Vec<FoldingRange> {
7 let sv = doc.view();
8 let mut ranges = Vec::new();
9 fold_stmts(&doc.program().stmts, sv, &mut ranges);
10 fold_use_groups(&doc.program().stmts, sv, &mut ranges);
11 fold_comments(sv, &mut ranges);
12 fold_regions(sv.source(), &mut ranges);
13 ranges
14}
15
16fn fold_stmts(stmts: &[Stmt<'_, '_>], sv: SourceView<'_>, out: &mut Vec<FoldingRange>) {
17 for stmt in stmts {
18 fold_stmt(stmt, sv, out);
19 }
20}
21
22fn fold_body(body: &Stmt<'_, '_>, sv: SourceView<'_>, out: &mut Vec<FoldingRange>) {
26 if let StmtKind::Block(stmts) = &body.kind {
27 fold_stmts(&stmts.stmts, sv, out);
28 }
29}
30
31fn fold_stmt(stmt: &Stmt<'_, '_>, sv: SourceView<'_>, out: &mut Vec<FoldingRange>) {
32 match &stmt.kind {
33 StmtKind::Function(f) => {
34 let start_line = sv.line_of(stmt.span.start);
35 let end_line = sv.line_of(stmt.span.end);
36 push(out, start_line, end_line, None);
37 fold_stmts(&f.body.stmts, sv, out);
38 }
39 StmtKind::Class(c) => {
40 let start_line = sv.line_of(stmt.span.start);
41 let end_line = sv.line_of(stmt.span.end);
42 push(out, start_line, end_line, None);
43 for member in c.body.members.iter() {
44 if let ClassMemberKind::Method(m) = &member.kind {
45 let m_start = sv.line_of(member.span.start);
46 let m_end = sv.line_of(member.span.end.saturating_sub(1));
49 push(out, m_start, m_end, None);
50 if let Some(body) = &m.body {
51 fold_stmts(&body.stmts, sv, out);
52 }
53 }
54 }
55 }
56 StmtKind::Interface(i) => {
57 let start_line = sv.line_of(stmt.span.start);
58 let end_line = sv.line_of(stmt.span.end);
59 push(out, start_line, end_line, None);
60 for member in i.body.members.iter() {
62 if let ClassMemberKind::Method(m) = &member.kind
63 && let Some(body) = &m.body
64 {
65 let m_start = sv.line_of(member.span.start);
66 let m_end = sv.line_of(member.span.end.saturating_sub(1));
67 push(out, m_start, m_end, None);
68 fold_stmts(&body.stmts, sv, out);
69 }
70 }
71 }
72 StmtKind::Trait(t) => {
73 let start_line = sv.line_of(stmt.span.start);
74 let end_line = sv.line_of(stmt.span.end);
75 push(out, start_line, end_line, None);
76 for member in t.body.members.iter() {
77 if let ClassMemberKind::Method(m) = &member.kind {
78 let m_start = sv.line_of(member.span.start);
79 let m_end = sv.line_of(member.span.end.saturating_sub(1));
80 push(out, m_start, m_end, None);
81 if let Some(body) = &m.body {
82 fold_stmts(&body.stmts, sv, out);
83 }
84 }
85 }
86 }
87 StmtKind::Enum(e) => {
88 let start_line = sv.line_of(stmt.span.start);
89 let end_line = sv.line_of(stmt.span.end);
90 push(out, start_line, end_line, None);
91 for member in e.body.members.iter() {
92 if let EnumMemberKind::Method(m) = &member.kind {
93 let m_start = sv.line_of(member.span.start);
94 let m_end = sv.line_of(member.span.end.saturating_sub(1));
95 push(out, m_start, m_end, None);
96 if let Some(body) = &m.body {
97 fold_stmts(&body.stmts, sv, out);
98 }
99 }
100 }
101 }
102 StmtKind::If(i) => {
103 let start_line = sv.line_of(stmt.span.start);
104 let end_line = sv.line_of(stmt.span.end);
105 push(out, start_line, end_line, None);
106 fold_body(i.then_branch, sv, out);
107 for ei in i.elseif_branches.iter() {
108 fold_body(&ei.body, sv, out);
109 }
110 if let Some(e) = &i.else_branch {
111 fold_body(e, sv, out);
112 }
113 }
114 StmtKind::While(w) => {
115 let start_line = sv.line_of(stmt.span.start);
116 let end_line = sv.line_of(stmt.span.end);
117 push(out, start_line, end_line, None);
118 fold_body(w.body, sv, out);
119 }
120 StmtKind::For(f) => {
121 let start_line = sv.line_of(stmt.span.start);
122 let end_line = sv.line_of(stmt.span.end);
123 push(out, start_line, end_line, None);
124 fold_body(f.body, sv, out);
125 }
126 StmtKind::Foreach(f) => {
127 let start_line = sv.line_of(stmt.span.start);
128 let end_line = sv.line_of(stmt.span.end);
129 push(out, start_line, end_line, None);
130 fold_body(f.body, sv, out);
131 }
132 StmtKind::DoWhile(d) => {
133 let start_line = sv.line_of(stmt.span.start);
134 let end_line = sv.line_of(stmt.span.end);
135 push(out, start_line, end_line, None);
136 fold_body(d.body, sv, out);
137 }
138 StmtKind::TryCatch(t) => {
139 let start_line = sv.line_of(stmt.span.start);
140 let end_line = sv.line_of(stmt.span.end);
141 push(out, start_line, end_line, None);
142 fold_stmts(&t.body.stmts, sv, out);
143 for catch in t.catches.iter() {
144 fold_stmts(&catch.body.stmts, sv, out);
145 }
146 if let Some(finally) = &t.finally {
147 fold_stmts(&finally.stmts, sv, out);
148 }
149 }
150 StmtKind::Switch(s) => {
151 let start_line = sv.line_of(stmt.span.start);
152 let end_line = sv.line_of(stmt.span.end);
153 push(out, start_line, end_line, None);
154 for case in s.body.cases.iter() {
155 fold_stmts(&case.body, sv, out);
156 }
157 }
158 StmtKind::Block(stmts) => {
159 let start_line = sv.line_of(stmt.span.start);
160 let end_line = sv.line_of(stmt.span.end);
161 push(out, start_line, end_line, None);
162 fold_stmts(&stmts.stmts, sv, out);
163 }
164 StmtKind::Namespace(ns) => {
165 let start_line = sv.line_of(stmt.span.start);
166 let end_line = sv.line_of(stmt.span.end);
167 push(out, start_line, end_line, None);
168 if let NamespaceBody::Braced(inner) = &ns.body {
169 fold_stmts(&inner.stmts, sv, out);
170 }
171 }
172 _ => {}
173 }
174}
175
176fn fold_use_groups(stmts: &[Stmt<'_, '_>], sv: SourceView<'_>, out: &mut Vec<FoldingRange>) {
178 let mut group_start: Option<u32> = None;
179 let mut group_end: u32 = 0;
180 for stmt in stmts {
181 if matches!(stmt.kind, StmtKind::Use(_)) {
182 let line = sv.line_of(stmt.span.start);
183 if group_start.is_none() {
184 group_start = Some(line);
185 }
186 group_end = sv.line_of(stmt.span.end);
187 } else {
188 if let Some(start) = group_start.take() {
189 push(out, start, group_end, Some(FoldingRangeKind::Imports));
190 }
191 }
192 }
193 if let Some(start) = group_start {
194 push(out, start, group_end, Some(FoldingRangeKind::Imports));
195 }
196}
197
198fn fold_comments(sv: SourceView<'_>, out: &mut Vec<FoldingRange>) {
200 let bytes = sv.source().as_bytes();
201 let len = bytes.len();
202 let mut i = 0;
203 while i + 1 < len {
204 if bytes[i] == b'/' && bytes[i + 1] == b'*' {
205 let start_line = line_at(sv, i);
206 let mut j = i + 2;
208 while j + 1 < len {
209 if bytes[j] == b'*' && bytes[j + 1] == b'/' {
210 let end_line = line_at(sv, j + 1);
211 push(out, start_line, end_line, Some(FoldingRangeKind::Comment));
212 i = j + 2;
213 break;
214 }
215 j += 1;
216 }
217 if j + 1 >= len {
218 break;
219 }
220 } else {
221 i += 1;
222 }
223 }
224}
225
226fn fold_regions(source: &str, out: &mut Vec<FoldingRange>) {
228 let mut stack: Vec<u32> = Vec::new();
229 for (line_no, line) in source.lines().enumerate() {
230 let trimmed = line.trim();
231 if trimmed.starts_with("// #region") || trimmed.starts_with("//region") {
232 stack.push(line_no as u32);
233 } else if (trimmed.starts_with("// #endregion") || trimmed.starts_with("//endregion"))
234 && let Some(start) = stack.pop()
235 {
236 push(out, start, line_no as u32, Some(FoldingRangeKind::Region));
237 }
238 }
239}
240
241fn line_at(sv: SourceView<'_>, byte_offset: usize) -> u32 {
242 sv.line_of(byte_offset as u32)
243}
244
245fn push(
246 out: &mut Vec<FoldingRange>,
247 start_line: u32,
248 end_line: u32,
249 kind: Option<FoldingRangeKind>,
250) {
251 if end_line > start_line {
252 out.push(FoldingRange {
253 start_line,
254 start_character: None,
255 end_line,
256 end_character: None,
257 kind,
258 collapsed_text: None,
259 });
260 }
261}