php_lsp/editing/
use_import.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use tower_lsp_server::ls_types::{Position, Range, TextEdit, Uri, WorkspaceEdit};
5
6use crate::document::ast::ParsedDoc;
7use crate::types::resolve::{Declaration, resolve_declaration};
8
9pub(crate) fn find_fqn_for_class(
10 name: &str,
11 class_candidates_by_short_name: &dyn Fn(&str) -> Vec<crate::db::workspace_index::ClassRef>,
12 resolve_class_fqn: &dyn Fn(crate::db::workspace_index::ClassRef) -> Option<String>,
13) -> Option<String> {
14 class_candidates_by_short_name(name)
15 .into_iter()
16 .find_map(resolve_class_fqn)
17}
18
19pub(crate) fn find_fqn_for_function(
20 name: &str,
21 get_doc: &dyn Fn(&Uri) -> Option<Arc<ParsedDoc>>,
22 function_candidates: &dyn Fn(&str) -> Vec<Uri>,
23) -> Option<String> {
24 for uri in function_candidates(name) {
25 let Some(doc) = get_doc(&uri) else { continue };
26 let Some(decl) = resolve_declaration(&doc.program().stmts, name, &|decl| {
27 matches!(decl, Declaration::Function { .. })
28 }) else {
29 continue;
30 };
31 if let Declaration::Function { decl, .. } = decl {
32 let ns = doc
33 .program()
34 .stmts
35 .iter()
36 .find_map(|stmt| {
37 if let php_ast::StmtKind::Namespace(ns) = &stmt.kind {
38 ns.name.as_ref().map(|n| n.to_string_repr().to_string())
39 } else {
40 None
41 }
42 })
43 .unwrap_or_default();
44 if ns.is_empty() {
45 continue;
46 }
47 return Some(format!(
48 "{}\\{}",
49 ns.trim_start_matches('\\'),
50 decl.name.or_error()
51 ));
52 }
53 }
54 None
55}
56
57pub(crate) fn build_use_import_edit(source: &str, uri: &Uri, fqn: &str) -> WorkspaceEdit {
59 let insert_line = find_use_insert_line(source);
60 let insert_text = format!("use {fqn};\n");
61 let pos = Position {
62 line: insert_line,
63 character: 0,
64 };
65 let edit = TextEdit {
66 range: Range {
67 start: pos,
68 end: pos,
69 },
70 new_text: insert_text,
71 };
72 let mut changes = HashMap::new();
73 changes.insert(uri.clone(), vec![edit]);
74 WorkspaceEdit {
75 changes: Some(changes),
76 ..Default::default()
77 }
78}
79
80pub(crate) fn build_use_function_import_edit(source: &str, uri: &Uri, 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}