php_lsp/editing/
use_import.rs1use std::collections::HashMap;
2
3use tower_lsp::lsp_types::{Position, Range, TextEdit, Url, WorkspaceEdit};
4
5use crate::document::ast::ParsedDoc;
6use crate::index::file_index::FileIndex;
7
8pub(crate) fn find_fqn_for_class(doc: &ParsedDoc, name: &str) -> Option<String> {
11 use php_ast::{NamespaceBody, StmtKind};
12 for stmt in doc.program().stmts.iter() {
13 match &stmt.kind {
14 StmtKind::Class(c)
15 if c.name.as_ref().map(|n| n.to_string()) == Some(name.to_string()) =>
16 {
17 return Some(name.to_string());
18 }
19 StmtKind::Namespace(ns) => {
20 let ns_name = ns.name.as_ref().map(|n| n.to_string_repr().to_string());
21 if let NamespaceBody::Braced(inner) = &ns.body {
22 for inner_stmt in inner.stmts.iter() {
23 if let StmtKind::Class(c) = &inner_stmt.kind
24 && c.name.as_ref().map(|n| n.to_string()) == Some(name.to_string())
25 {
26 return Some(match ns_name {
27 Some(ref ns) => format!("{ns}\\{name}"),
28 None => name.to_string(),
29 });
30 }
31 }
32 }
33 }
34 _ => {}
35 }
36 }
37 None
38}
39
40pub(crate) fn build_use_import_edit(source: &str, uri: &Url, fqn: &str) -> WorkspaceEdit {
42 let insert_line = find_use_insert_line(source);
44 let insert_text = format!("use {fqn};\n");
45 let pos = Position {
46 line: insert_line,
47 character: 0,
48 };
49 let edit = TextEdit {
50 range: Range {
51 start: pos,
52 end: pos,
53 },
54 new_text: insert_text,
55 };
56 let mut changes = HashMap::new();
57 changes.insert(uri.clone(), vec![edit]);
58 WorkspaceEdit {
59 changes: Some(changes),
60 ..Default::default()
61 }
62}
63
64pub(crate) fn find_fqn_for_function(
67 name: &str,
68 indexes: &[(Url, std::sync::Arc<FileIndex>)],
69) -> Option<String> {
70 for (_uri, idx) in indexes {
71 for func in &idx.functions {
72 if func.name.as_ref() == name && func.fqn.contains('\\') {
73 return Some(func.fqn.trim_start_matches('\\').to_string());
74 }
75 }
76 }
77 None
78}
79
80pub(crate) fn build_use_function_import_edit(source: &str, uri: &Url, fqn: &str) -> WorkspaceEdit {
82 let insert_line = find_use_insert_line(source);
83 let insert_text = format!("use function {fqn};\n");
84 let pos = Position {
85 line: insert_line,
86 character: 0,
87 };
88 let edit = TextEdit {
89 range: Range {
90 start: pos,
91 end: pos,
92 },
93 new_text: insert_text,
94 };
95 let mut changes = HashMap::new();
96 changes.insert(uri.clone(), vec![edit]);
97 WorkspaceEdit {
98 changes: Some(changes),
99 ..Default::default()
100 }
101}
102
103pub(crate) fn find_use_insert_line(source: &str) -> u32 {
104 let mut last_use_or_ns: u32 = 0;
105 for (i, line) in source.lines().enumerate() {
106 let trimmed = line.trim();
107 if trimmed.starts_with("<?php")
108 || trimmed.starts_with("namespace ")
109 || trimmed.starts_with("use ")
110 {
111 last_use_or_ns = i as u32 + 1;
112 }
113 }
114 last_use_or_ns
115}