use std::collections::HashMap; use aster_core::analysis::{self, SymbolKind}; use aster_core::lexer::Lexer; use aster_core::parser::Parser; use lsp_server::{Connection, Message, Request}; use lsp_types::{ ParameterInformation, SignatureHelp, SignatureHelpParams, SignatureInformation, Uri, }; pub fn handle_signature_help( documents: &HashMap, connection: &Connection, req: Request, ) { let id = req.id; let params: SignatureHelpParams = match serde_json::from_value(req.params) { Ok(p) => p, Err(e) => { eprintln!("Invalid signature help params: {}", e); return; } }; let uri = params.text_document_position_params.text_document.uri; let position = params.text_document_position_params.position; let text = documents.get(&uri).map(|s| s.as_str()).unwrap_or(""); // Find the function being called at cursor let (func_name, arg_index) = find_call_at_position(text, position); if func_name.is_empty() { let resp = lsp_server::Response::new_ok::>(id, None); let _ = connection.sender.send(Message::Response(resp)); return; } // Look up the function in user symbols let (tokens, _) = Lexer::new(text).tokenize(); let mut parser = Parser::new(tokens); let (stmts, _) = parser.parse(); let symbols = analysis::collect_symbols(&stmts); for sym in &symbols { if sym.name == func_name { if let SymbolKind::Function { params } = &sym.kind { let param_labels: Vec = params.iter().map(|p| p.clone()).collect(); let sig = SignatureInformation { label: format!("{}({})", func_name, param_labels.join(", ")), documentation: None, parameters: Some( param_labels .iter() .map(|p| ParameterInformation { label: lsp_types::ParameterLabel::Simple(p.clone()), documentation: None, }) .collect(), ), active_parameter: None, }; let help = SignatureHelp { signatures: vec![sig], active_signature: Some(0), active_parameter: Some(arg_index.min(param_labels.len().saturating_sub(1)) as u32), }; let resp = lsp_server::Response::new_ok(id, help); let _ = connection.sender.send(Message::Response(resp)); return; } } } let resp = lsp_server::Response::new_ok::>(id, None); let _ = connection.sender.send(Message::Response(resp)); } /// Naive text-based detection of a function call at cursor position. /// Returns (function_name, argument_index). fn find_call_at_position(text: &str, position: lsp_types::Position) -> (String, usize) { let line = match text.lines().nth(position.line as usize) { Some(l) => l, None => return (String::new(), 0), }; let col = position.character as usize; let before_cursor = if col > line.len() { line } else { &line[..col] }; // Walk backwards from cursor counting parentheses depth let mut depth = 0; let bytes = before_cursor.as_bytes(); let mut i = bytes.len(); while i > 0 { i -= 1; let ch = bytes[i] as char; match ch { ')' => depth += 1, '(' => { if depth > 0 { depth -= 1; } else { // Found the opening paren of our call // Count commas between here and cursor to get arg index let args_slice = &before_cursor[i + 1..]; let arg_index = args_slice.bytes().filter(|&b| b == b',').count(); // Find the function name before '(' let before_paren = before_cursor[..i].trim_end(); return (extract_last_identifier(before_paren), arg_index); } } _ => {} } } (String::new(), 0) } fn extract_last_identifier(s: &str) -> String { let bytes = s.as_bytes(); let mut end = bytes.len(); while end > 0 { let ch = bytes[end - 1] as char; if ch.is_alphanumeric() || ch == '_' || ch == '.' { end -= 1; } else { break; } } // If there's a dot, take what's after it (method call) let ident = &s[end..]; if let Some(dot_pos) = ident.rfind('.') { ident[dot_pos + 1..].to_string() } else { ident.to_string() } }