1use php_ast::{
2 CallableCreateKind, ClassDecl, ClassMemberKind, EnumMemberKind, Expr, ExprKind, NamespaceBody,
3 Param, PropertyHookBody, Stmt, StmtKind, StringPart,
4};
5use tower_lsp_server::ls_types::{Position, Range, SelectionRange};
6
7use crate::document::ast::{ParsedDoc, SourceView};
8
9pub fn selection_ranges(doc: &ParsedDoc, positions: &[Position]) -> Vec<SelectionRange> {
12 let sv = doc.view();
13 let fr = file_range(sv);
14 positions
15 .iter()
16 .map(|pos| {
17 let byte_off = sv.byte_of_position(*pos);
18 build_chain(sv, &doc.program().stmts, byte_off, fr)
19 })
20 .collect()
21}
22
23fn file_range(sv: SourceView<'_>) -> Range {
29 let source = sv.source();
30 let line_starts = sv.line_starts();
31 if source.is_empty() {
32 return Range {
33 start: Position {
34 line: 0,
35 character: 0,
36 },
37 end: Position {
38 line: 0,
39 character: 0,
40 },
41 };
42 }
43 let last_line_idx = line_starts.len().saturating_sub(1) as u32;
44 let last_line_start = *line_starts.last().unwrap_or(&0) as usize;
45 let raw = &source[last_line_start..];
46 let line = raw.strip_suffix('\n').unwrap_or(raw);
47 let line = line.strip_suffix('\r').unwrap_or(line);
48 let last_char: u32 = line.chars().map(|c| c.len_utf16() as u32).sum();
49 Range {
50 start: Position {
51 line: 0,
52 character: 0,
53 },
54 end: Position {
55 line: last_line_idx,
56 character: last_char,
57 },
58 }
59}
60
61fn build_chain(
63 sv: SourceView<'_>,
64 stmts: &[Stmt<'_, '_>],
65 byte_off: u32,
66 fr: Range,
67) -> SelectionRange {
68 let mut spans: Vec<(u32, u32)> = Vec::new();
69 collect_spans_stmts(stmts, byte_off, &mut spans);
70 spans.sort_by_key(|&(s, e)| e - s);
77 spans.dedup();
78 let ranges: Vec<Range> = spans
79 .into_iter()
80 .map(|(s, e)| span_range(sv, s, e))
81 .collect();
82 let mut ranges = ranges;
83 ranges.dedup();
84
85 if !ranges.contains(&fr) {
87 ranges.push(fr);
88 }
89
90 let mut chain: Option<SelectionRange> = None;
92 for range in ranges.into_iter().rev() {
93 chain = Some(SelectionRange {
94 range,
95 parent: chain.map(Box::new),
96 });
97 }
98
99 chain.unwrap_or(SelectionRange {
100 range: fr,
101 parent: None,
102 })
103}
104
105#[cfg(test)]
106fn contains(range: Range, pos: Position) -> bool {
107 if pos.line < range.start.line || pos.line > range.end.line {
108 return false;
109 }
110 if pos.line == range.start.line && pos.character < range.start.character {
111 return false;
112 }
113 if pos.line == range.end.line && pos.character >= range.end.character {
114 return false;
115 }
116 true
117}
118
119fn span_range(sv: SourceView<'_>, start: u32, end: u32) -> Range {
120 Range {
121 start: sv.position_of(start),
122 end: sv.position_of(end),
123 }
124}
125
126#[inline]
127fn span_contains(start: u32, end: u32, off: u32) -> bool {
128 off >= start && off < end
129}
130
131#[inline]
132fn push_if_contains(s: u32, e: u32, off: u32, out: &mut Vec<(u32, u32)>) -> bool {
133 if span_contains(s, e, off) {
134 out.push((s, e));
135 true
136 } else {
137 false
138 }
139}
140
141fn collect_spans_stmts(stmts: &[Stmt<'_, '_>], off: u32, out: &mut Vec<(u32, u32)>) {
142 for stmt in stmts {
143 collect_spans_stmt(stmt, off, out);
144 }
145}
146
147fn collect_spans_stmt(stmt: &Stmt<'_, '_>, off: u32, out: &mut Vec<(u32, u32)>) {
148 let s = stmt.span.start;
149 let e = stmt.span.end;
150 if !span_contains(s, e, off) {
151 return;
152 }
153 out.push((s, e));
154 match &stmt.kind {
155 StmtKind::Function(f) => {
156 for p in f.params.iter() {
157 collect_spans_param(p, off, out);
158 }
159 collect_spans_stmts(&f.body.stmts, off, out);
160 }
161 StmtKind::Class(c) => collect_class_members(c, off, out),
162 StmtKind::Interface(i) => {
163 for member in i.body.members.iter() {
164 collect_class_member(member, off, out);
165 }
166 }
167 StmtKind::Trait(t) => {
168 for member in t.body.members.iter() {
169 collect_class_member(member, off, out);
170 }
171 }
172 StmtKind::Enum(en) => {
173 for member in en.body.members.iter() {
174 if !push_if_contains(member.span.start, member.span.end, off, out) {
175 continue;
176 }
177 match &member.kind {
178 EnumMemberKind::Method(m) => {
179 for p in m.params.iter() {
180 collect_spans_param(p, off, out);
181 }
182 if let Some(body) = &m.body {
183 collect_spans_stmts(&body.stmts, off, out);
184 }
185 }
186 EnumMemberKind::Case(c) => {
187 if let Some(v) = &c.value {
188 collect_spans_expr(v, off, out);
189 }
190 }
191 EnumMemberKind::ClassConst(c) => {
192 collect_spans_expr(&c.value, off, out);
193 }
194 EnumMemberKind::TraitUse(_) => {}
195 }
196 }
197 }
198 StmtKind::Namespace(ns) => {
199 if let NamespaceBody::Braced(inner) = &ns.body {
200 collect_spans_stmts(&inner.stmts, off, out);
201 }
202 }
203 StmtKind::If(i) => {
204 collect_spans_expr(&i.condition, off, out);
205 collect_spans_stmt(i.then_branch, off, out);
206 for ei in i.elseif_branches.iter() {
207 if !push_if_contains(ei.span.start, ei.span.end, off, out) {
208 continue;
209 }
210 collect_spans_expr(&ei.condition, off, out);
211 collect_spans_stmt(&ei.body, off, out);
212 }
213 if let Some(el) = &i.else_branch {
214 collect_spans_stmt(el, off, out);
215 }
216 }
217 StmtKind::While(w) => {
218 collect_spans_expr(&w.condition, off, out);
219 collect_spans_stmt(w.body, off, out);
220 }
221 StmtKind::For(f) => {
222 for e in f.init.iter() {
223 collect_spans_expr(e, off, out);
224 }
225 for e in f.condition.iter() {
226 collect_spans_expr(e, off, out);
227 }
228 for e in f.update.iter() {
229 collect_spans_expr(e, off, out);
230 }
231 collect_spans_stmt(f.body, off, out);
232 }
233 StmtKind::Foreach(f) => {
234 collect_spans_expr(&f.expr, off, out);
235 if let Some(k) = &f.key {
236 collect_spans_expr(k, off, out);
237 }
238 collect_spans_expr(&f.value, off, out);
239 collect_spans_stmt(f.body, off, out);
240 }
241 StmtKind::DoWhile(d) => {
242 collect_spans_stmt(d.body, off, out);
243 collect_spans_expr(&d.condition, off, out);
244 }
245 StmtKind::Switch(sw) => {
246 collect_spans_expr(&sw.expr, off, out);
247 for case in sw.body.cases.iter() {
248 if !push_if_contains(case.span.start, case.span.end, off, out) {
249 continue;
250 }
251 if let Some(v) = &case.value {
252 collect_spans_expr(v, off, out);
253 }
254 collect_spans_stmts(&case.body, off, out);
255 }
256 }
257 StmtKind::TryCatch(t) => {
258 collect_spans_stmts(&t.body.stmts, off, out);
259 for catch in t.catches.iter() {
260 if !push_if_contains(catch.span.start, catch.span.end, off, out) {
261 continue;
262 }
263 collect_spans_stmts(&catch.body.stmts, off, out);
264 }
265 if let Some(finally) = &t.finally {
266 collect_spans_stmts(&finally.stmts, off, out);
267 }
268 }
269 StmtKind::Block(stmts) => collect_spans_stmts(&stmts.stmts, off, out),
270 StmtKind::Expression(e) => collect_spans_expr(e, off, out),
271 StmtKind::Echo(args) => {
272 for a in args.iter() {
273 collect_spans_expr(a, off, out);
274 }
275 }
276 StmtKind::Return(opt) => {
277 if let Some(e) = opt {
278 collect_spans_expr(e, off, out);
279 }
280 }
281 StmtKind::Break(opt) | StmtKind::Continue(opt) => {
282 if let Some(e) = opt {
283 collect_spans_expr(e, off, out);
284 }
285 }
286 StmtKind::Throw(e) => collect_spans_expr(e, off, out),
287 StmtKind::Unset(args) => {
288 for a in args.iter() {
289 collect_spans_expr(a, off, out);
290 }
291 }
292 StmtKind::Const(items) => {
293 for item in items.iter() {
294 collect_spans_expr(&item.value, off, out);
295 }
296 }
297 StmtKind::StaticVar(items) => {
298 for item in items.iter() {
299 if let Some(d) = &item.default {
300 collect_spans_expr(d, off, out);
301 }
302 }
303 }
304 StmtKind::Declare(d) => {
305 for (_, e) in d.directives.iter() {
306 collect_spans_expr(e, off, out);
307 }
308 if let Some(body) = &d.body {
309 collect_spans_stmt(body, off, out);
310 }
311 }
312 StmtKind::Use(_)
315 | StmtKind::Global(_)
316 | StmtKind::Goto(_)
317 | StmtKind::Label(_)
318 | StmtKind::HaltCompiler(_)
319 | StmtKind::Nop
320 | StmtKind::InlineHtml(_)
321 | StmtKind::Error => {}
322 }
323}
324
325fn collect_class_members(c: &ClassDecl<'_, '_>, off: u32, out: &mut Vec<(u32, u32)>) {
326 for member in c.body.members.iter() {
327 collect_class_member(member, off, out);
328 }
329}
330
331fn collect_class_member(
332 member: &php_ast::ClassMember<'_, '_>,
333 off: u32,
334 out: &mut Vec<(u32, u32)>,
335) {
336 if !push_if_contains(member.span.start, member.span.end, off, out) {
337 return;
338 }
339 match &member.kind {
340 ClassMemberKind::Method(m) => {
341 for p in m.params.iter() {
342 collect_spans_param(p, off, out);
343 }
344 if let Some(body) = &m.body {
345 collect_spans_stmts(&body.stmts, off, out);
346 }
347 }
348 ClassMemberKind::Property(p) => {
349 if let Some(d) = &p.default {
350 collect_spans_expr(d, off, out);
351 }
352 for hook in p.hooks.iter() {
353 if !push_if_contains(hook.span.start, hook.span.end, off, out) {
354 continue;
355 }
356 for hp in hook.params.iter() {
357 collect_spans_param(hp, off, out);
358 }
359 match &hook.body {
360 PropertyHookBody::Block(stmts) => collect_spans_stmts(&stmts.stmts, off, out),
361 PropertyHookBody::Expression(e) => collect_spans_expr(e, off, out),
362 PropertyHookBody::Abstract => {}
363 }
364 }
365 }
366 ClassMemberKind::ClassConst(c) => collect_spans_expr(&c.value, off, out),
367 ClassMemberKind::TraitUse(_) => {}
368 }
369}
370
371fn collect_spans_param(p: &Param<'_, '_>, off: u32, out: &mut Vec<(u32, u32)>) {
372 if !push_if_contains(p.span.start, p.span.end, off, out) {
373 return;
374 }
375 if let Some(d) = &p.default {
376 collect_spans_expr(d, off, out);
377 }
378 for hook in p.hooks.iter() {
379 if !push_if_contains(hook.span.start, hook.span.end, off, out) {
380 continue;
381 }
382 match &hook.body {
383 PropertyHookBody::Block(stmts) => collect_spans_stmts(&stmts.stmts, off, out),
384 PropertyHookBody::Expression(e) => collect_spans_expr(e, off, out),
385 PropertyHookBody::Abstract => {}
386 }
387 }
388}
389
390fn collect_spans_expr(expr: &Expr<'_, '_>, off: u32, out: &mut Vec<(u32, u32)>) {
391 let s = expr.span.start;
392 let e = expr.span.end;
393 if !span_contains(s, e, off) {
394 return;
395 }
396 out.push((s, e));
397 match &expr.kind {
398 ExprKind::Int(_)
400 | ExprKind::Float(_)
401 | ExprKind::String(_)
402 | ExprKind::Bool(_)
403 | ExprKind::Null
404 | ExprKind::Variable(_)
405 | ExprKind::Identifier(_)
406 | ExprKind::MagicConst(_)
407 | ExprKind::Nowdoc { .. }
408 | ExprKind::Error => {}
409
410 ExprKind::InterpolatedString(parts) | ExprKind::ShellExec(parts) => {
411 for p in parts.iter() {
412 if let StringPart::Expr(inner) = p {
413 collect_spans_expr(inner, off, out);
414 }
415 }
416 }
417 ExprKind::Heredoc { parts, .. } => {
418 for p in parts.iter() {
419 if let StringPart::Expr(inner) = p {
420 collect_spans_expr(inner, off, out);
421 }
422 }
423 }
424
425 ExprKind::VariableVariable(inner) => collect_spans_expr(inner, off, out),
426 ExprKind::Assign(a) => {
427 collect_spans_expr(a.target, off, out);
428 collect_spans_expr(a.value, off, out);
429 }
430 ExprKind::Binary(b) => {
431 collect_spans_expr(b.left, off, out);
432 collect_spans_expr(b.right, off, out);
433 }
434 ExprKind::UnaryPrefix(u) => collect_spans_expr(u.operand, off, out),
435 ExprKind::UnaryPostfix(u) => collect_spans_expr(u.operand, off, out),
436 ExprKind::Ternary(t) => {
437 collect_spans_expr(t.condition, off, out);
438 if let Some(then_e) = t.then_expr {
439 collect_spans_expr(then_e, off, out);
440 }
441 collect_spans_expr(t.else_expr, off, out);
442 }
443 ExprKind::NullCoalesce(n) => {
444 collect_spans_expr(n.left, off, out);
445 collect_spans_expr(n.right, off, out);
446 }
447 ExprKind::FunctionCall(f) => {
448 collect_spans_expr(f.name, off, out);
449 for arg in f.args.iter() {
450 if !push_if_contains(arg.span.start, arg.span.end, off, out) {
451 continue;
452 }
453 if let Some(value) = &arg.value {
454 collect_spans_expr(value, off, out);
455 }
456 }
457 }
458 ExprKind::Array(elems) => {
459 for el in elems.iter() {
460 if !push_if_contains(el.span.start, el.span.end, off, out) {
461 continue;
462 }
463 if let Some(k) = &el.key {
464 collect_spans_expr(k, off, out);
465 }
466 collect_spans_expr(&el.value, off, out);
467 }
468 }
469 ExprKind::ArrayAccess(a) => {
470 collect_spans_expr(a.array, off, out);
471 if let Some(idx) = a.index {
472 collect_spans_expr(idx, off, out);
473 }
474 }
475 ExprKind::Print(e) => collect_spans_expr(e, off, out),
476 ExprKind::Parenthesized(e) => collect_spans_expr(e, off, out),
477 ExprKind::Cast(_, e) => collect_spans_expr(e, off, out),
478 ExprKind::ErrorSuppress(e) => collect_spans_expr(e, off, out),
479 ExprKind::Isset(es) => {
480 for e in es.iter() {
481 collect_spans_expr(e, off, out);
482 }
483 }
484 ExprKind::Empty(e) => collect_spans_expr(e, off, out),
485 ExprKind::Include(_, e) => collect_spans_expr(e, off, out),
486 ExprKind::Eval(e) => collect_spans_expr(e, off, out),
487 ExprKind::Exit(opt) => {
488 if let Some(e) = opt {
489 collect_spans_expr(e, off, out);
490 }
491 }
492 ExprKind::Clone(e) => collect_spans_expr(e, off, out),
493 ExprKind::New(n) => {
494 collect_spans_expr(n.class, off, out);
495 for arg in n.args.iter() {
496 if !push_if_contains(arg.span.start, arg.span.end, off, out) {
497 continue;
498 }
499 if let Some(value) = &arg.value {
500 collect_spans_expr(value, off, out);
501 }
502 }
503 }
504 ExprKind::PropertyAccess(p) | ExprKind::NullsafePropertyAccess(p) => {
505 collect_spans_expr(p.object, off, out);
506 collect_spans_expr(p.property, off, out);
507 }
508 ExprKind::MethodCall(m) | ExprKind::NullsafeMethodCall(m) => {
509 collect_spans_expr(m.object, off, out);
510 collect_spans_expr(m.method, off, out);
511 for arg in m.args.iter() {
512 if !push_if_contains(arg.span.start, arg.span.end, off, out) {
513 continue;
514 }
515 if let Some(value) = &arg.value {
516 collect_spans_expr(value, off, out);
517 }
518 }
519 }
520 ExprKind::StaticPropertyAccess(s) | ExprKind::ClassConstAccess(s) => {
521 collect_spans_expr(s.class, off, out);
522 }
523 ExprKind::StaticMethodCall(s) => {
524 collect_spans_expr(s.class, off, out);
525 for arg in s.args.iter() {
526 if !push_if_contains(arg.span.start, arg.span.end, off, out) {
527 continue;
528 }
529 if let Some(value) = &arg.value {
530 collect_spans_expr(value, off, out);
531 }
532 }
533 }
534 ExprKind::ClassConstAccessDynamic { class, member }
535 | ExprKind::StaticPropertyAccessDynamic { class, member } => {
536 collect_spans_expr(class, off, out);
537 collect_spans_expr(member, off, out);
538 }
539 ExprKind::Closure(c) => {
540 for p in c.params.iter() {
541 collect_spans_param(p, off, out);
542 }
543 collect_spans_stmts(&c.body.stmts, off, out);
544 }
545 ExprKind::ArrowFunction(a) => {
546 for p in a.params.iter() {
547 collect_spans_param(p, off, out);
548 }
549 collect_spans_expr(a.body, off, out);
550 }
551 ExprKind::Match(m) => {
552 collect_spans_expr(m.subject, off, out);
553 for arm in m.arms.iter() {
554 if !push_if_contains(arm.span.start, arm.span.end, off, out) {
555 continue;
556 }
557 if let Some(conds) = &arm.conditions {
558 for c in conds.iter() {
559 collect_spans_expr(c, off, out);
560 }
561 }
562 collect_spans_expr(&arm.body, off, out);
563 }
564 }
565 ExprKind::ThrowExpr(e) => collect_spans_expr(e, off, out),
566 ExprKind::Yield(y) => {
567 if let Some(k) = y.key {
568 collect_spans_expr(k, off, out);
569 }
570 if let Some(v) = y.value {
571 collect_spans_expr(v, off, out);
572 }
573 }
574 ExprKind::AnonymousClass(c) => collect_class_members(c, off, out),
575 ExprKind::CallableCreate(c) => match &c.kind {
576 CallableCreateKind::Function(e) => collect_spans_expr(e, off, out),
577 CallableCreateKind::Method { object, .. } => collect_spans_expr(object, off, out),
578 CallableCreateKind::NullsafeMethod { object, .. } => {
579 collect_spans_expr(object, off, out)
580 }
581 CallableCreateKind::StaticMethod { class, .. } => collect_spans_expr(class, off, out),
582 },
583 ExprKind::CloneWith(target, withs) => {
584 collect_spans_expr(target, off, out);
585 collect_spans_expr(withs, off, out);
586 }
587 ExprKind::StaticDynMethodCall(s) => {
588 collect_spans_expr(s.class, off, out);
589 collect_spans_expr(s.method, off, out);
590 for arg in s.args.iter() {
591 if !push_if_contains(arg.span.start, arg.span.end, off, out) {
592 continue;
593 }
594 if let Some(value) = &arg.value {
595 collect_spans_expr(value, off, out);
596 }
597 }
598 }
599 ExprKind::Omit => {}
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606
607 #[test]
610 fn contains_excludes_exact_end_position() {
611 let range = Range {
615 start: Position {
616 line: 0,
617 character: 4,
618 },
619 end: Position {
620 line: 0,
621 character: 9,
622 },
623 };
624 assert!(
625 !contains(
626 range,
627 Position {
628 line: 0,
629 character: 9
630 }
631 ),
632 "exact end position must be outside (half-open range)"
633 );
634 assert!(
635 !contains(
636 range,
637 Position {
638 line: 0,
639 character: 10
640 }
641 ),
642 "position after end must be outside"
643 );
644 assert!(
645 contains(
646 range,
647 Position {
648 line: 0,
649 character: 8
650 }
651 ),
652 "position just before end must be inside"
653 );
654 assert!(
655 contains(
656 range,
657 Position {
658 line: 0,
659 character: 4
660 }
661 ),
662 "start position must be inside"
663 );
664 }
665
666 #[test]
667 fn contains_handles_multiline_range_end() {
668 let range = Range {
669 start: Position {
670 line: 1,
671 character: 0,
672 },
673 end: Position {
674 line: 3,
675 character: 1,
676 },
677 };
678 assert!(!contains(
680 range,
681 Position {
682 line: 3,
683 character: 1
684 }
685 ));
686 assert!(contains(
688 range,
689 Position {
690 line: 3,
691 character: 0
692 }
693 ));
694 assert!(contains(
696 range,
697 Position {
698 line: 2,
699 character: 999
700 }
701 ));
702 }
703}