use crate::ast::{Node, NodeKind};
use crate::position::Position;
use lsp_types::*;
use perl_lexer::PARSER_LSP_KEYWORDS;
use std::collections::HashMap;
use url::Url;
#[derive(Debug, Clone)]
pub struct CompletionProvider {
workspace_symbols: HashMap<String, CompletionItem>,
builtin_symbols: HashMap<String, CompletionItem>,
}
impl CompletionProvider {
pub fn new() -> Self {
let mut provider = Self {
workspace_symbols: HashMap::new(),
builtin_symbols: HashMap::new(),
};
provider.load_builtin_symbols();
provider
}
fn load_builtin_symbols(&mut self) {
let builtin_functions = [
"print", "say", "printf", "sprintf", "chomp", "chop",
"push", "pop", "shift", "unshift", "splice", "map", "grep",
"sort", "reverse", "keys", "values", "each", "exists", "delete",
"defined", "undef", "wantarray", "caller", "die", "warn",
"eval", "require", "use", "package", "sub", "my", "our", "local",
];
for func in builtin_functions {
self.builtin_symbols.insert(
func.to_string(),
CompletionItem {
label: func.to_string(),
kind: Some(CompletionItemKind::FUNCTION),
detail: Some(format!("built-in function: {}", func)),
documentation: Some(Documentation::String(format!("Perl built-in function: {}", func))),
insert_text: Some(format!("{} ", func)),
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
..Default::default()
},
);
}
for &keyword in PARSER_LSP_KEYWORDS {
self.builtin_symbols.insert(
keyword.to_string(),
CompletionItem {
label: keyword.to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some(format!("keyword: {}", keyword)),
documentation: Some(Documentation::String(format!("Perl keyword: {}", keyword))),
insert_text: Some(format!("{} ", keyword)),
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
..Default::default()
},
);
}
}
pub fn complete(&self, params: CompletionParams) -> Option<Vec<CompletionItem>> {
let position = params.text_document_position.position;
let mut completions = Vec::new();
completions.extend(self.builtin_symbols.values().cloned());
completions.extend(self.workspace_symbols.values().cloned());
Some(completions)
}
pub fn update_workspace_symbols(&mut self, symbols: HashMap<String, CompletionItem>) {
self.workspace_symbols = symbols;
}
}
impl Default for CompletionProvider {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use perl_tdd_support::{must, must_some};
#[test]
fn test_completion_provider_creation() {
let provider = CompletionProvider::new();
assert!(!provider.builtin_symbols.is_empty());
assert!(provider.builtin_symbols.contains_key("print"));
assert!(provider.builtin_symbols.contains_key("if"));
}
#[test]
fn test_builtin_symbols_loaded() {
let provider = CompletionProvider::new();
if let Some(item) = provider.builtin_symbols.get("print") {
assert_eq!(item.kind, Some(CompletionItemKind::FUNCTION));
assert!(must_some(item.detail.as_ref()).contains("built-in function"));
} else {
assert!(false, "print function not found in built-ins");
}
if let Some(item) = provider.builtin_symbols.get("if") {
assert_eq!(item.kind, Some(CompletionItemKind::KEYWORD));
assert!(must_some(item.detail.as_ref()).contains("keyword"));
} else {
assert!(false, "if keyword not found in built-ins");
}
}
#[test]
fn test_completion_response() {
let provider = CompletionProvider::new();
let params = CompletionParams {
text_document_position: lsp_types::TextDocumentPositionParams {
text_document: lsp_types::TextDocumentIdentifier {
uri: must(Url::parse("file:///test.pl"))
},
position: Position::new(0, 0),
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
context: None,
};
let results = provider.complete(params);
assert!(results.is_some());
assert!(!must_some(results).is_empty());
}
#[test]
fn test_workspace_symbols_update() {
let mut provider = CompletionProvider::new();
let mut symbols = HashMap::new();
symbols.insert(
"test_function".to_string(),
CompletionItem {
label: "test_function".to_string(),
kind: Some(CompletionItemKind::FUNCTION),
detail: Some("user-defined function".to_string()),
..Default::default()
},
);
provider.update_workspace_symbols(symbols);
assert_eq!(provider.workspace_symbols.len(), 1);
assert!(provider.workspace_symbols.contains_key("test_function"));
}
}