Skip to main content

php_lsp/editing/
use_import.rs

1use 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
57/// Build a `WorkspaceEdit` that inserts `use FQN;` near the top of the file.
58pub(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
80/// Build a `WorkspaceEdit` that inserts `use function FQN;` near the top of the file.
81pub(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}