use crate::error::{McpError, McpResult};
use crate::protocol::{Position, ToolCallResult};
use crate::tools::text_response;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use windjammer::lexer::Lexer;
use windjammer::parser::Parser;
use windjammer_lsp::database::WindjammerDatabase;
#[derive(Debug, Serialize, Deserialize)]
pub struct RenameSymbolRequest {
pub code: String,
pub position: Position,
pub new_name: String,
pub old_name: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RenameSymbolResponse {
pub success: bool,
pub refactored_code: Option<String>,
pub occurrences_renamed: Option<usize>,
pub old_name: Option<String>,
pub files_affected: Option<Vec<String>>,
pub error: Option<String>,
}
pub async fn handle(
_db: Arc<Mutex<WindjammerDatabase>>,
arguments: Value,
) -> McpResult<ToolCallResult> {
let request: RenameSymbolRequest =
serde_json::from_value(arguments).map_err(|e| McpError::ValidationError {
field: "arguments".to_string(),
message: e.to_string(),
})?;
if !is_valid_identifier(&request.new_name) {
let response = RenameSymbolResponse {
success: false,
refactored_code: None,
occurrences_renamed: None,
old_name: None,
files_affected: None,
error: Some(format!("Invalid identifier: {}", request.new_name)),
};
return Ok(text_response(&serde_json::to_string(&response)?));
}
let mut lexer = Lexer::new(&request.code);
let tokens = lexer.tokenize_with_locations();
let mut parser = Parser::new(tokens);
let parse_result = parser.parse();
let program = match parse_result {
Ok(prog) => prog,
Err(e) => {
let response = RenameSymbolResponse {
success: false,
refactored_code: None,
occurrences_renamed: None,
old_name: None,
files_affected: None,
error: Some(format!("Parse error: {}", e)),
};
return Ok(text_response(&serde_json::to_string(&response)?));
}
};
let symbol_info = find_symbol_at_position(&program, &request.position);
let (old_name, symbol_kind) = match symbol_info {
Some((name, kind)) => (name, kind),
None => {
let response = RenameSymbolResponse {
success: false,
refactored_code: None,
occurrences_renamed: None,
old_name: None,
files_affected: None,
error: Some("No symbol found at position".to_string()),
};
return Ok(text_response(&serde_json::to_string(&response)?));
}
};
if has_naming_conflict(&program, &request.new_name, &symbol_kind) {
let response = RenameSymbolResponse {
success: false,
refactored_code: None,
occurrences_renamed: None,
old_name: Some(old_name),
files_affected: None,
error: Some(format!(
"Naming conflict: {} already exists in this scope",
request.new_name
)),
};
return Ok(text_response(&serde_json::to_string(&response)?));
}
let occurrences = find_all_occurrences(&program, &old_name, &symbol_kind);
let refactored_code = format!(
"// TODO: Implement full AST manipulation\n// Would rename {} '{}' to '{}' ({} occurrences)",
symbol_kind.name(),
old_name,
request.new_name,
occurrences
);
let response = RenameSymbolResponse {
success: true,
refactored_code: Some(refactored_code),
occurrences_renamed: Some(occurrences),
old_name: Some(old_name),
files_affected: Some(vec!["current_file.wj".to_string()]),
error: None,
};
Ok(text_response(&serde_json::to_string(&response)?))
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)]
enum SymbolKind {
Function,
Variable,
Struct,
Enum,
Trait,
TypeParameter,
Field,
}
impl SymbolKind {
fn name(&self) -> &str {
match self {
SymbolKind::Function => "function",
SymbolKind::Variable => "variable",
SymbolKind::Struct => "struct",
SymbolKind::Enum => "enum",
SymbolKind::Trait => "trait",
SymbolKind::TypeParameter => "type parameter",
SymbolKind::Field => "field",
}
}
}
fn find_symbol_at_position(
_program: &windjammer::parser::Program,
_position: &Position,
) -> Option<(String, SymbolKind)> {
None
}
fn has_naming_conflict(
_program: &windjammer::parser::Program,
_new_name: &str,
_symbol_kind: &SymbolKind,
) -> bool {
false
}
fn find_all_occurrences(
_program: &windjammer::parser::Program,
_symbol_name: &str,
_symbol_kind: &SymbolKind,
) -> usize {
0
}
fn is_valid_identifier(name: &str) -> bool {
if name.is_empty() {
return false;
}
const RESERVED_KEYWORDS: &[&str] = &[
"fn", "let", "const", "static", "if", "else", "match", "for", "while", "loop", "return",
"break", "continue", "struct", "enum", "trait", "impl", "pub", "use", "mod", "type", "as",
"in", "mut", "ref", "true", "false", "Self", "self", "super", "crate", "async", "await",
"go", "defer",
];
if RESERVED_KEYWORDS.contains(&name) {
return false;
}
let first = name.chars().next().unwrap();
if !first.is_alphabetic() && first != '_' {
return false;
}
name.chars().all(|c| c.is_alphanumeric() || c == '_')
}
#[allow(dead_code)]
struct RenameVisitor {
old_name: String,
new_name: String,
symbol_kind: SymbolKind,
occurrences: usize,
scope_stack: Vec<HashMap<String, SymbolKind>>,
}
#[allow(dead_code)]
impl RenameVisitor {
fn new(old_name: String, new_name: String, symbol_kind: SymbolKind) -> Self {
Self {
old_name,
new_name,
symbol_kind,
occurrences: 0,
scope_stack: vec![HashMap::new()],
}
}
fn enter_scope(&mut self) {
self.scope_stack.push(HashMap::new());
}
fn exit_scope(&mut self) {
self.scope_stack.pop();
}
fn visit_identifier(&mut self, name: &str) -> bool {
if name == self.old_name {
for scope in self.scope_stack.iter().rev() {
if let Some(kind) = scope.get(name) {
if kind != &self.symbol_kind {
return false;
}
break;
}
}
self.occurrences += 1;
return true;
}
false
}
fn declare_symbol(&mut self, name: String, kind: SymbolKind) {
if let Some(scope) = self.scope_stack.last_mut() {
scope.insert(name, kind);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_rename_symbol_basic() {
let db = Arc::new(Mutex::new(WindjammerDatabase::new()));
let args = serde_json::json!({
"code": "fn main() {\n let x = 42;\n println!(\"{}\", x);\n}",
"position": { "line": 1, "column": 8 },
"new_name": "value"
});
let result = handle(db, args).await;
assert!(result.is_ok());
}
#[test]
fn test_is_valid_identifier() {
assert!(is_valid_identifier("foo"));
assert!(is_valid_identifier("_bar"));
assert!(is_valid_identifier("value42"));
assert!(is_valid_identifier("_"));
assert!(!is_valid_identifier(""));
assert!(!is_valid_identifier("42foo"));
assert!(!is_valid_identifier("foo-bar"));
assert!(!is_valid_identifier("foo bar"));
assert!(!is_valid_identifier("fn")); }
#[test]
fn test_symbol_kind() {
assert_eq!(SymbolKind::Function.name(), "function");
assert_eq!(SymbolKind::Variable.name(), "variable");
assert_eq!(SymbolKind::Struct.name(), "struct");
}
}