1use crate::type_hierarchy::TypeHierarchyProvider;
8use crate::util::uri::parse_uri;
9use lsp_types::LocationLink;
10use lsp_types::{Position as LspPosition, Range as LspRange};
11use perl_parser::ast::{Node, NodeKind};
12use perl_parser::workspace_index::WorkspaceIndex;
13use std::collections::HashMap;
14
15pub struct ImplementationProvider {
37 workspace_index: Option<std::sync::Arc<WorkspaceIndex>>,
38}
39
40impl ImplementationProvider {
41 pub fn new(workspace_index: Option<std::sync::Arc<WorkspaceIndex>>) -> Self {
66 Self { workspace_index }
67 }
68
69 pub fn find_implementations(
71 &self,
72 ast: &Node,
73 line: u32,
74 character: u32,
75 uri: &str,
76 documents: &HashMap<String, String>,
77 ) -> Vec<LocationLink> {
78 let source = documents.get(uri);
80 let target_node = match self.find_node_at_position(ast, line, character, source) {
81 Some(node) => node,
82 None => return Vec::new(),
83 };
84
85 let byte_offset = source
89 .map(|src| crate::position::utf16_line_col_to_offset(src, line, character))
90 .unwrap_or(0);
91 let current_package = crate::declaration::current_package_at(ast, byte_offset);
92
93 match self.extract_implementation_target(&target_node, current_package) {
95 Some(ImplementationTarget::Package(name)) => {
96 self.find_package_implementations(&name, documents)
97 }
98 Some(ImplementationTarget::Method { package, method }) => {
99 self.find_method_implementations(&package, &method, documents)
100 }
101 Some(ImplementationTarget::BlessedType(name)) => {
102 self.find_package_implementations(&name, documents)
104 }
105 None => Vec::new(),
106 }
107 }
108
109 fn find_package_implementations(
111 &self,
112 base_package: &str,
113 documents: &HashMap<String, String>,
114 ) -> Vec<LocationLink> {
115 self.find_subclass_locations(base_package, documents)
116 .into_iter()
117 .map(|(_name, link)| link)
118 .collect()
119 }
120
121 fn find_subclass_locations(
125 &self,
126 base_package: &str,
127 documents: &HashMap<String, String>,
128 ) -> Vec<(String, LocationLink)> {
129 let mut results: Vec<(String, LocationLink)> = Vec::new();
130
131 let _hierarchy_provider = TypeHierarchyProvider::new();
132
133 for (uri, content) in documents {
134 if let Ok(ast) = crate::Parser::new(content).parse() {
135 self.find_inheriting_packages_named(&ast, base_package, uri, content, &mut results);
136 }
137 }
138
139 if let Some(ref index) = self.workspace_index {
141 let symbols = index.find_symbols(base_package);
142 for symbol in symbols {
143 if symbol.kind == crate::workspace_index::SymbolKind::Class
144 || symbol.kind == crate::workspace_index::SymbolKind::Package
145 {
146 if let Some(container) = &symbol.container_name {
147 if container.contains(base_package) {
148 let target_uri = parse_uri(&symbol.uri);
149 let lsp_start = LspPosition::new(
151 symbol.range.start.line - 1,
152 symbol.range.start.column - 1,
153 );
154 let lsp_end = LspPosition::new(
155 symbol.range.end.line - 1,
156 symbol.range.end.column - 1,
157 );
158 results.push((
159 symbol.name.clone(),
160 LocationLink {
161 origin_selection_range: None,
162 target_uri,
163 target_range: LspRange::new(lsp_start, lsp_end),
164 target_selection_range: LspRange::new(lsp_start, lsp_end),
165 },
166 ));
167 }
168 }
169 }
170 }
171 }
172
173 results
174 }
175
176 fn find_method_implementations(
178 &self,
179 package: &str,
180 method: &str,
181 documents: &HashMap<String, String>,
182 ) -> Vec<LocationLink> {
183 let mut results = Vec::new();
184
185 let subclasses = self.find_subclass_locations(package, documents);
187
188 for (subclass_name, subclass_link) in &subclasses {
190 if let Some(doc_content) = documents.get(subclass_link.target_uri.as_str()) {
191 if let Ok(ast) = crate::Parser::new(doc_content).parse() {
192 self.find_method_in_package(
193 &ast,
194 method,
195 subclass_name,
196 subclass_link.target_uri.as_str(),
197 doc_content,
198 &mut results,
199 );
200 }
201 }
202 }
203
204 results
205 }
206
207 fn find_inheriting_packages_named(
209 &self,
210 node: &Node,
211 base_package: &str,
212 uri: &str,
213 source: &str,
214 results: &mut Vec<(String, LocationLink)>,
215 ) {
216 let mut current_package = String::new();
217 let mut current_package_range = LspRange::default();
219 self.find_inheriting_packages_recursive(
220 node,
221 base_package,
222 uri,
223 source,
224 &mut current_package,
225 &mut current_package_range,
226 results,
227 );
228 }
229
230 fn find_inheriting_packages_recursive(
231 &self,
232 node: &Node,
233 base_package: &str,
234 uri: &str,
235 source: &str,
236 current_package: &mut String,
237 current_package_range: &mut LspRange,
238 results: &mut Vec<(String, LocationLink)>,
239 ) {
240 match &node.kind {
241 NodeKind::Package { name, .. } => {
242 *current_package = name.clone();
243 *current_package_range = self.node_to_range(node, source);
245 }
246 NodeKind::Use { module, args, .. } if module == "base" || module == "parent" => {
247 for arg in args {
250 let normalized = Self::strip_arg_quotes(arg);
251 if normalized == base_package {
252 let pkg_range = *current_package_range;
254 let target_uri = parse_uri(uri);
255 results.push((
256 current_package.clone(),
257 LocationLink {
258 origin_selection_range: None,
259 target_uri,
260 target_range: pkg_range,
261 target_selection_range: pkg_range,
262 },
263 ));
264 }
265 }
266 }
267 NodeKind::VariableDeclaration { declarator, variable, initializer, .. } => {
268 if declarator == "our" {
269 if let NodeKind::Variable { sigil, name } = &variable.kind {
270 if sigil == "@" && name == "ISA" {
271 if let Some(init) = initializer {
272 if self.contains_parent(init, base_package) {
273 let pkg_range = *current_package_range;
274 let target_uri = parse_uri(uri);
275 results.push((
276 current_package.clone(),
277 LocationLink {
278 origin_selection_range: None,
279 target_uri,
280 target_range: pkg_range,
281 target_selection_range: pkg_range,
282 },
283 ));
284 }
285 }
286 }
287 }
288 }
289 }
290 NodeKind::Program { statements } | NodeKind::Block { statements } => {
291 for stmt in statements {
292 self.find_inheriting_packages_recursive(
293 stmt,
294 base_package,
295 uri,
296 source,
297 current_package,
298 current_package_range,
299 results,
300 );
301 }
302 }
303 _ => {}
304 }
305 }
306
307 #[allow(dead_code)]
309 fn find_method_in_ast(
310 &self,
311 node: &Node,
312 method_name: &str,
313 uri: &str,
314 source: &str,
315 results: &mut Vec<LocationLink>,
316 ) {
317 match &node.kind {
318 NodeKind::Subroutine { name: Some(name), .. } => {
319 let (_, name_bare) = perl_parser::qualified_name::split_qualified_name(name);
320 let (_, method_bare) =
321 perl_parser::qualified_name::split_qualified_name(method_name);
322 if name_bare == method_bare {
323 let target_uri = parse_uri(uri);
324 results.push(LocationLink {
325 origin_selection_range: None,
326 target_uri,
327 target_range: self.node_to_range(node, source),
328 target_selection_range: self.node_to_range(node, source),
329 });
330 }
331 }
332 NodeKind::Program { statements } | NodeKind::Block { statements } => {
333 for stmt in statements {
334 self.find_method_in_ast(stmt, method_name, uri, source, results);
335 }
336 }
337 _ => {}
338 }
339 }
340
341 fn find_method_in_package(
348 &self,
349 node: &Node,
350 method_name: &str,
351 package_name: &str,
352 uri: &str,
353 source: &str,
354 results: &mut Vec<LocationLink>,
355 ) {
356 let mut current_package: Option<String> = None;
357 self.find_method_in_package_with_scope(
358 node,
359 method_name,
360 package_name,
361 uri,
362 source,
363 &mut current_package,
364 results,
365 );
366 }
367
368 fn find_method_in_package_with_scope(
369 &self,
370 node: &Node,
371 method_name: &str,
372 package_name: &str,
373 uri: &str,
374 source: &str,
375 current_package: &mut Option<String>,
376 results: &mut Vec<LocationLink>,
377 ) {
378 if let NodeKind::Program { statements } | NodeKind::Block { statements } = &node.kind {
379 for stmt in statements {
380 match &stmt.kind {
381 NodeKind::Package { name, block: Some(inner), .. } => {
382 let previous_package = current_package.clone();
383 *current_package = Some(name.clone());
384 self.find_method_in_package_with_scope(
385 inner,
386 method_name,
387 package_name,
388 uri,
389 source,
390 current_package,
391 results,
392 );
393 *current_package = previous_package;
394 }
395 NodeKind::Package { name, .. } => {
396 *current_package = Some(name.clone());
397 }
398 NodeKind::Subroutine { name: Some(sub_name), .. } => {
399 if current_package.as_deref() == Some(package_name)
400 && *sub_name == method_name
401 {
402 let target_uri = parse_uri(uri);
403 results.push(LocationLink {
404 origin_selection_range: None,
405 target_uri,
406 target_range: self.node_to_range(stmt, source),
407 target_selection_range: self.node_to_range(stmt, source),
408 });
409 }
410 }
411 _ => {
412 self.find_method_in_package_with_scope(
413 stmt,
414 method_name,
415 package_name,
416 uri,
417 source,
418 current_package,
419 results,
420 );
421 }
422 }
423 }
424 return;
425 }
426
427 if let NodeKind::Package { name, block: Some(inner), .. } = &node.kind {
428 let previous_package = current_package.clone();
429 *current_package = Some(name.clone());
430 self.find_method_in_package_with_scope(
431 inner,
432 method_name,
433 package_name,
434 uri,
435 source,
436 current_package,
437 results,
438 );
439 *current_package = previous_package;
440 }
441 }
442
443 fn extract_implementation_target(
449 &self,
450 node: &Node,
451 current_package: &str,
452 ) -> Option<ImplementationTarget> {
453 match &node.kind {
454 NodeKind::Package { name, .. } => Some(ImplementationTarget::Package(name.clone())),
455 NodeKind::Subroutine { name: Some(method), .. } => Some(ImplementationTarget::Method {
456 package: current_package.to_string(),
457 method: method.clone(),
458 }),
459 NodeKind::Identifier { name } if name.contains("::") => {
460 let parts: Vec<&str> = name.split("::").collect();
461 if parts.len() == 2 {
462 Some(ImplementationTarget::Method {
463 package: parts[0].to_string(),
464 method: parts[1].to_string(),
465 })
466 } else if parts.len() > 2 {
467 Some(ImplementationTarget::Package(parts[..parts.len() - 1].join("::")))
468 } else {
469 None
470 }
471 }
472 _ => None,
473 }
474 }
475
476 fn find_node_at_position(
478 &self,
479 node: &Node,
480 line: u32,
481 character: u32,
482 source: Option<&String>,
483 ) -> Option<Node> {
484 if let Some(src) = source {
485 let (start_line, start_col) =
486 crate::position::offset_to_utf16_line_col(src, node.location.start);
487 let (end_line, end_col) =
488 crate::position::offset_to_utf16_line_col(src, node.location.end);
489
490 if line >= start_line && line <= end_line {
491 if (line == start_line && character >= start_col)
492 || (line == end_line && character <= end_col)
493 || (line > start_line && line < end_line)
494 {
495 match &node.kind {
497 NodeKind::Program { statements } | NodeKind::Block { statements } => {
498 for stmt in statements {
499 if let Some(child) =
500 self.find_node_at_position(stmt, line, character, source)
501 {
502 return Some(child);
503 }
504 }
505 }
506 _ => {}
507 }
508 return Some(node.clone());
509 }
510 }
511 }
512 None
513 }
514
515 fn node_to_range(&self, node: &Node, source: &str) -> LspRange {
517 let (start_line, start_col) =
518 crate::position::offset_to_utf16_line_col(source, node.location.start);
519 let (end_line, end_col) =
520 crate::position::offset_to_utf16_line_col(source, node.location.end);
521
522 LspRange {
523 start: LspPosition::new(start_line, start_col),
524 end: LspPosition::new(end_line, end_col),
525 }
526 }
527
528 fn _extract_parent_name(&self, node: &Node) -> Option<String> {
530 match &node.kind {
531 NodeKind::String { value, .. } => Some(value.clone()),
532 NodeKind::Identifier { name } => Some(name.clone()),
533 _ => None,
534 }
535 }
536
537 fn contains_parent(&self, node: &Node, parent: &str) -> bool {
539 match &node.kind {
540 NodeKind::String { value, .. } => value == parent,
541 NodeKind::ArrayLiteral { elements, .. } => {
542 elements.iter().any(|e| self.contains_parent(e, parent))
543 }
544 _ => false,
545 }
546 }
547
548 fn strip_arg_quotes(raw: &str) -> &str {
553 let s = raw.trim();
554 if s.len() >= 2 {
555 if (s.starts_with('\'') && s.ends_with('\''))
556 || (s.starts_with('"') && s.ends_with('"'))
557 {
558 return &s[1..s.len() - 1];
559 }
560 }
561 s
562 }
563}
564
565enum ImplementationTarget {
566 Package(String),
567 Method {
568 package: String,
569 method: String,
570 },
571 #[allow(dead_code)]
572 BlessedType(String),
573}