#![allow(dead_code)]
use super::RefactoringEngine;
use tower_lsp::lsp_types::*;
pub struct BatchRefactoring<'a> {
engine: &'a RefactoringEngine<'a>,
operations: Vec<RefactoringOperation>,
}
#[derive(Debug, Clone)]
pub enum RefactoringOperation {
ExtractFunction {
uri: Url,
range: Range,
function_name: String,
source: String,
},
InlineVariable {
uri: Url,
position: Position,
source: String,
},
IntroduceVariable {
uri: Url,
range: Range,
variable_name: String,
source: String,
},
ChangeSignature {
uri: Url,
position: Position,
changes: Vec<super::change_signature::ParameterChange>,
source: String,
},
MoveItem {
source_uri: Url,
target_uri: Url,
position: Position,
source_content: String,
target_content: String,
},
}
#[derive(Debug, Clone)]
pub struct BatchResult {
pub workspace_edit: WorkspaceEdit,
pub successful: usize,
pub failed: usize,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl<'a> BatchRefactoring<'a> {
pub fn new(engine: &'a RefactoringEngine<'a>) -> Self {
Self {
engine,
operations: vec![],
}
}
pub fn add_operation(&mut self, operation: RefactoringOperation) -> &mut Self {
self.operations.push(operation);
self
}
pub fn execute(&self) -> Result<BatchResult, String> {
let mut combined_changes = std::collections::HashMap::new();
let mut successful = 0;
let mut failed = 0;
let mut errors = vec![];
let mut warnings = vec![];
if let Some(conflict) = self.detect_conflicts() {
warnings.push(format!("Conflict detected: {}", conflict));
}
for (i, operation) in self.operations.iter().enumerate() {
match self.apply_operation(operation) {
Ok(workspace_edit) => {
successful += 1;
if let Some(changes) = workspace_edit.changes {
for (uri, edits) in changes {
combined_changes
.entry(uri)
.or_insert_with(Vec::new)
.extend(edits);
}
}
}
Err(e) => {
failed += 1;
errors.push(format!("Operation {}: {}", i + 1, e));
}
}
}
if failed > 0 && successful == 0 {
return Err(format!(
"All {} operations failed: {}",
failed,
errors.join("; ")
));
}
Ok(BatchResult {
workspace_edit: WorkspaceEdit {
changes: Some(combined_changes),
document_changes: None,
change_annotations: None,
},
successful,
failed,
errors,
warnings,
})
}
fn apply_operation(&self, operation: &RefactoringOperation) -> Result<WorkspaceEdit, String> {
match operation {
RefactoringOperation::ExtractFunction {
uri,
range,
function_name,
source,
} => self
.engine
.execute_extract_function(uri, *range, function_name, source),
RefactoringOperation::InlineVariable {
uri,
position,
source,
} => self.engine.execute_inline_variable(uri, *position, source),
RefactoringOperation::IntroduceVariable {
uri,
range,
variable_name,
source,
} => self
.engine
.execute_introduce_variable(uri, *range, variable_name, source),
RefactoringOperation::ChangeSignature {
uri,
position,
changes,
source,
} => self
.engine
.execute_change_signature(uri, *position, changes, source),
RefactoringOperation::MoveItem {
source_uri,
target_uri,
position,
source_content,
target_content,
} => self.engine.execute_move_item(
source_uri,
target_uri,
*position,
source_content,
target_content,
),
}
}
fn detect_conflicts(&self) -> Option<String> {
let mut file_ranges: std::collections::HashMap<String, Vec<Range>> =
std::collections::HashMap::new();
for operation in &self.operations {
let (uri, range_opt) = match operation {
RefactoringOperation::ExtractFunction { uri, range, .. } => {
(uri.to_string(), Some(*range))
}
RefactoringOperation::InlineVariable { uri, position, .. } => (
uri.to_string(),
Some(Range {
start: *position,
end: *position,
}),
),
RefactoringOperation::IntroduceVariable { uri, range, .. } => {
(uri.to_string(), Some(*range))
}
RefactoringOperation::ChangeSignature { uri, position, .. } => (
uri.to_string(),
Some(Range {
start: *position,
end: *position,
}),
),
RefactoringOperation::MoveItem { source_uri, .. } => (source_uri.to_string(), None),
};
if let Some(range) = range_opt {
file_ranges.entry(uri).or_default().push(range);
}
}
for (uri, ranges) in file_ranges {
for i in 0..ranges.len() {
for j in (i + 1)..ranges.len() {
if Self::ranges_overlap(&ranges[i], &ranges[j]) {
return Some(format!(
"Overlapping operations in {}",
uri.split('/').next_back().unwrap_or("unknown")
));
}
}
}
}
None
}
fn ranges_overlap(a: &Range, b: &Range) -> bool {
!(a.end.line < b.start.line || b.end.line < a.start.line)
}
pub fn operation_count(&self) -> usize {
self.operations.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::database::WindjammerDatabase;
#[test]
fn test_batch_creation() {
let db = WindjammerDatabase::new();
let engine = RefactoringEngine::new(&db);
let batch = BatchRefactoring::new(&engine);
assert_eq!(batch.operation_count(), 0);
}
#[test]
fn test_add_operation() {
let db = WindjammerDatabase::new();
let engine = RefactoringEngine::new(&db);
let mut batch = BatchRefactoring::new(&engine);
let uri = Url::parse("file:///test.wj").unwrap();
batch.add_operation(RefactoringOperation::InlineVariable {
uri,
position: Position {
line: 0,
character: 0,
},
source: "let x = 1".to_string(),
});
assert_eq!(batch.operation_count(), 1);
}
#[test]
fn test_conflict_detection() {
let db = WindjammerDatabase::new();
let engine = RefactoringEngine::new(&db);
let mut batch = BatchRefactoring::new(&engine);
let uri = Url::parse("file:///test.wj").unwrap();
batch.add_operation(RefactoringOperation::ExtractFunction {
uri: uri.clone(),
range: Range {
start: Position {
line: 1,
character: 0,
},
end: Position {
line: 5,
character: 0,
},
},
function_name: "helper".to_string(),
source: String::new(),
});
batch.add_operation(RefactoringOperation::ExtractFunction {
uri,
range: Range {
start: Position {
line: 3,
character: 0,
},
end: Position {
line: 7,
character: 0,
},
},
function_name: "other".to_string(),
source: String::new(),
});
let conflict = batch.detect_conflicts();
assert!(conflict.is_some(), "Should detect overlapping operations");
}
}