use {
reovim_driver_undo::{UndoKey, UndoProviderRegistry},
reovim_kernel::api::v1::{Edit, Position},
};
use super::{Operator, OperatorContext, OperatorError, Range};
#[cfg_attr(coverage_nightly, coverage(off))]
fn apply_case_transform(
ctx: &mut OperatorContext<'_>,
range: Range,
transform: fn(&str) -> String,
) -> Result<(), OperatorError> {
let buffer_arc = ctx
.kernel
.buffers
.get(ctx.buffer_id)
.ok_or(OperatorError::BufferNotFound(ctx.buffer_id))?;
let mut buffer = buffer_arc.write();
let lines = buffer.lines();
let start = range.start;
let end = range.end;
let mut original = String::new();
if range.is_linewise {
let line_count = lines.len();
let clamped_end = end.line.min(line_count.saturating_sub(1));
for line_idx in start.line..=clamped_end {
if line_idx > start.line {
original.push('\n');
}
if let Some(line) = lines.get(line_idx) {
original.push_str(line);
}
}
} else if start.line == end.line {
if let Some(line) = lines.get(start.line) {
let start_col = start.column.min(line.len());
let end_col = end.column.min(line.len());
if start_col < end_col {
original.push_str(&line[start_col..end_col]);
}
}
} else {
for (line_idx, line) in lines.iter().enumerate().take(end.line + 1).skip(start.line) {
if line_idx == start.line {
let start_col = start.column.min(line.len());
original.push_str(&line[start_col..]);
original.push('\n');
} else if line_idx == end.line {
let end_col = end.column.min(line.len());
original.push_str(&line[..end_col]);
} else {
original.push_str(line);
original.push('\n');
}
}
}
let transformed = transform(&original);
if transformed == original {
ctx.cursor_after = Some(start);
return Ok(());
}
let cursor_before = ctx.cursor_position;
if range.is_linewise {
let line_count = lines.len();
let clamped_end = end.line.min(line_count.saturating_sub(1));
let delete_start = Position::new(start.line, 0);
let last_line_len = lines.get(clamped_end).map_or(0, String::len);
let delete_end = Position::new(clamped_end, last_line_len);
buffer.delete_range(delete_start, delete_end);
buffer.insert_at(delete_start, &transformed);
ctx.cursor_after = Some(Position::new(start.line, 0));
} else {
buffer.delete_range(start, end);
buffer.insert_at(start, &transformed);
ctx.cursor_after = Some(start);
}
if let Some(undo_registry) = ctx.kernel.services.get::<UndoProviderRegistry>()
&& let Some(undo_provider) = undo_registry.get(&UndoKey::Buffer)
{
let delete_edit = Edit::Delete {
position: if range.is_linewise {
Position::new(start.line, 0)
} else {
start
},
text: original,
};
let insert_edit = Edit::Insert {
position: if range.is_linewise {
Position::new(start.line, 0)
} else {
start
},
text: transformed,
};
let cursor_after = ctx.cursor_after.unwrap_or(start);
undo_provider.record(
ctx.buffer_id,
vec![delete_edit, insert_edit],
cursor_before,
cursor_after,
);
}
drop(buffer);
Ok(())
}
#[derive(Debug, Clone, Copy)]
pub struct LowercaseOperator;
impl Operator for LowercaseOperator {
fn id(&self) -> &'static str {
"lowercase"
}
fn execute(&self, ctx: &mut OperatorContext<'_>, range: Range) -> Result<(), OperatorError> {
apply_case_transform(ctx, range, str::to_lowercase)
}
fn is_text_modifying(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy)]
pub struct UppercaseOperator;
impl Operator for UppercaseOperator {
fn id(&self) -> &'static str {
"uppercase"
}
fn execute(&self, ctx: &mut OperatorContext<'_>, range: Range) -> Result<(), OperatorError> {
apply_case_transform(ctx, range, str::to_uppercase)
}
fn is_text_modifying(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy)]
pub struct ToggleCaseOperator;
impl Operator for ToggleCaseOperator {
fn id(&self) -> &'static str {
"toggle-case"
}
fn execute(&self, ctx: &mut OperatorContext<'_>, range: Range) -> Result<(), OperatorError> {
apply_case_transform(ctx, range, toggle_case)
}
fn is_text_modifying(&self) -> bool {
true
}
}
fn toggle_case(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_uppercase() {
c.to_lowercase().next().unwrap_or(c)
} else if c.is_lowercase() {
c.to_uppercase().next().unwrap_or(c)
} else {
c
}
})
.collect()
}