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_function(
10 name: &str,
11 get_doc: &dyn Fn(&Uri) -> Option<Arc<ParsedDoc>>,
12 function_candidates: &dyn Fn(&str) -> Vec<Uri>,
13) -> Option<String> {
14 for uri in function_candidates(name) {
15 let Some(doc) = get_doc(&uri) else { continue };
16 let Some(decl) = resolve_declaration(&doc.program().stmts, name, &|decl| {
17 matches!(decl, Declaration::Function { .. })
18 }) else {
19 continue;
20 };
21 if let Declaration::Function { decl, .. } = decl {
22 let ns = doc
23 .program()
24 .stmts
25 .iter()
26 .find_map(|stmt| {
27 if let php_ast::StmtKind::Namespace(ns) = &stmt.kind {
28 ns.name.as_ref().map(|n| n.to_string_repr().to_string())
29 } else {
30 None
31 }
32 })
33 .unwrap_or_default();
34 if ns.is_empty() {
35 continue;
36 }
37 return Some(format!(
38 "{}\\{}",
39 ns.trim_start_matches('\\'),
40 decl.name.or_error()
41 ));
42 }
43 }
44 None
45}
46
47pub(crate) fn build_use_import_edit(source: &str, uri: &Uri, fqn: &str) -> WorkspaceEdit {
49 let insert_line = find_use_insert_line(source);
50 let insert_text = format!("use {fqn};\n");
51 let pos = Position {
52 line: insert_line,
53 character: 0,
54 };
55 let edit = TextEdit {
56 range: Range {
57 start: pos,
58 end: pos,
59 },
60 new_text: insert_text,
61 };
62 let mut changes = HashMap::new();
63 changes.insert(uri.clone(), vec![edit]);
64 WorkspaceEdit {
65 changes: Some(changes),
66 ..Default::default()
67 }
68}
69
70pub(crate) fn build_use_function_import_edit(source: &str, uri: &Uri, fqn: &str) -> WorkspaceEdit {
72 let insert_line = find_use_insert_line(source);
73 let insert_text = format!("use function {fqn};\n");
74 let pos = Position {
75 line: insert_line,
76 character: 0,
77 };
78 let edit = TextEdit {
79 range: Range {
80 start: pos,
81 end: pos,
82 },
83 new_text: insert_text,
84 };
85 let mut changes = HashMap::new();
86 changes.insert(uri.clone(), vec![edit]);
87 WorkspaceEdit {
88 changes: Some(changes),
89 ..Default::default()
90 }
91}
92
93pub(crate) fn find_use_insert_line(source: &str) -> u32 {
94 let mut last_use_or_ns: u32 = 0;
95 for (i, line) in source.lines().enumerate() {
96 let trimmed = line.trim();
97 if trimmed.starts_with("<?php")
98 || trimmed.starts_with("namespace ")
99 || trimmed.starts_with("use ")
100 {
101 last_use_or_ns = i as u32 + 1;
102 }
103 }
104 last_use_or_ns
105}