Skip to main content

php_lsp/editing/
use_import.rs

1use 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
8/// Find the fully-qualified name for a class with the given short `name` by
9/// walking the ParsedDoc AST. Returns `Namespace\ClassName` when inside a namespace.
10pub(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
40/// Build a `WorkspaceEdit` that inserts `use FQN;` near the top of the file.
41pub(crate) fn build_use_import_edit(source: &str, uri: &Url, fqn: &str) -> WorkspaceEdit {
42    // Insert after the `<?php` line and any existing `use` / `namespace` lines
43    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
64/// Find a namespaced function FQN matching `name` in the workspace indexes.
65/// Returns `Some(fqn)` only when the FQN is namespaced (contains `\`).
66pub(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
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: &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}