1use php_ast::{NamespaceBody, Stmt, StmtKind, UseKind};
2
3use crate::util::fqn_short_name;
4
5pub fn extract_receiver_var_before_cursor(line: &str, cursor_col_utf16: usize) -> Option<String> {
10 let chars: Vec<char> = line.chars().collect();
11
12 let mut utf16 = 0usize;
14 let mut char_idx = 0usize;
15 for ch in &chars {
16 if utf16 >= cursor_col_utf16 {
17 break;
18 }
19 utf16 += ch.len_utf16();
20 char_idx += 1;
21 }
22
23 let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
25 let mut word_start = char_idx;
26 while word_start > 0 && is_word_char(chars[word_start - 1]) {
27 word_start -= 1;
28 }
29
30 let (is_arrow, arrow_end) = if word_start >= 3
32 && chars[word_start - 3] == '?'
33 && chars[word_start - 2] == '-'
34 && chars[word_start - 1] == '>'
35 {
36 (true, word_start - 3)
37 } else if word_start >= 2 && chars[word_start - 2] == '-' && chars[word_start - 1] == '>' {
38 (true, word_start - 2)
39 } else {
40 (false, 0)
41 };
42
43 if !is_arrow {
44 return None;
45 }
46
47 extract_name_from_chars_end(&chars[..arrow_end])
48}
49
50pub(crate) fn extract_static_class_before_cursor(
52 line: &str,
53 cursor_col_utf16: usize,
54) -> Option<String> {
55 let chars: Vec<char> = line.chars().collect();
56
57 let mut utf16 = 0usize;
58 let mut char_idx = 0usize;
59 for ch in &chars {
60 if utf16 >= cursor_col_utf16 {
61 break;
62 }
63 utf16 += ch.len_utf16();
64 char_idx += 1;
65 }
66
67 let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
68 let mut word_start = char_idx;
69 while word_start > 0 && is_word_char(chars[word_start - 1]) {
70 word_start -= 1;
71 }
72
73 if word_start > 0 && chars[word_start - 1] == '$' {
75 word_start -= 1;
76 }
77
78 if word_start < 2 || chars[word_start - 2] != ':' || chars[word_start - 1] != ':' {
79 return None;
80 }
81
82 let before_colons = &chars[..word_start - 2];
83 let is_name_char = |c: char| c.is_alphanumeric() || c == '_' || c == '\\';
85 let end = before_colons.len().saturating_sub(
86 before_colons
87 .iter()
88 .rev()
89 .take_while(|&&c| c == ' ' || c == '\t')
90 .count(),
91 );
92 let mut start = end;
93 while start > 0 && is_name_char(before_colons[start - 1]) {
94 start -= 1;
95 }
96 if start == end {
97 return None;
98 }
99 let full: String = before_colons[start..end].iter().collect();
100 Some(fqn_short_name(&full).to_owned())
102}
103
104pub(crate) fn extract_name_from_chars_end(chars: &[char]) -> Option<String> {
107 let is_var_char = |c: char| c.is_alphanumeric() || c == '_' || c == '$';
108 let end = chars.len()
109 - chars
110 .iter()
111 .rev()
112 .take_while(|&&c| c == ' ' || c == '\t')
113 .count();
114 if end == 0 {
115 return None;
116 }
117 let mut start = end;
118 while start > 0 && is_var_char(chars[start - 1]) {
119 start -= 1;
120 }
121 if start == end {
122 return None;
123 }
124 let name: String = chars[start..end].iter().collect();
125 if name.starts_with('$') && name.len() > 1 {
126 Some(name)
127 } else if !name.is_empty() && !name.starts_with('$') {
128 Some(format!("${}", name))
131 } else {
132 None
133 }
134}
135
136pub fn resolve_use_alias(stmts: &[Stmt<'_, '_>], word: &str) -> Option<String> {
141 for stmt in stmts {
142 match &stmt.kind {
143 StmtKind::Use(u) if u.kind == UseKind::Normal => {
144 for item in u.uses.iter() {
145 if let Some(alias) = item.alias
146 && alias == word
147 {
148 let fqn = item.name.to_string_repr();
149 let short = fqn_short_name(&fqn).to_owned();
150 return Some(short);
151 }
152 }
153 }
154 StmtKind::Namespace(ns) => {
155 if let NamespaceBody::Braced(inner) = &ns.body
156 && let Some(s) = resolve_use_alias(&inner.stmts, word)
157 {
158 return Some(s);
159 }
160 }
161 _ => {}
162 }
163 }
164 None
165}