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_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
47/// Build a `WorkspaceEdit` that inserts `use FQN;` near the top of the file.
48pub(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
70/// Build a `WorkspaceEdit` that inserts `use function FQN;` near the top of the file.
71pub(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}