use {
reovim_driver_undo::{UndoKey, UndoProviderRegistry},
reovim_kernel::api::v1::{Edit, Position},
};
use super::{Operator, OperatorContext, OperatorError, Range, char_col_to_byte};
#[allow(clippy::too_many_lines)]
#[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 start = range.start;
let end = range.end;
let mut original = String::new();
if range.is_linewise {
let line_count = buffer.line_count();
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) = buffer.line(line_idx) {
original.push_str(line);
}
}
} else if start.line == end.line {
if let Some(line) = buffer.line(start.line) {
let char_len = line.chars().count();
let start_col = start.column.min(char_len);
let end_col = end.column.min(char_len);
if start_col < end_col {
let start_byte = char_col_to_byte(line, start_col);
let end_byte = char_col_to_byte(line, end_col);
original.push_str(&line[start_byte..end_byte]);
}
}
} else {
for line_idx in start.line..=end.line {
if let Some(line) = buffer.line(line_idx) {
if line_idx == start.line {
let char_len = line.chars().count();
let start_col = start.column.min(char_len);
let start_byte = char_col_to_byte(line, start_col);
original.push_str(&line[start_byte..]);
original.push('\n');
} else if line_idx == end.line {
let char_len = line.chars().count();
let end_col = end.column.min(char_len);
let end_byte = char_col_to_byte(line, end_col);
original.push_str(&line[..end_byte]);
} 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 = buffer.line_count();
let clamped_end = end.line.min(line_count.saturating_sub(1));
let delete_start = Position::new(start.line, 0);
let last_line_char_len = buffer.line(clamped_end).map_or(0, |l| l.chars().count());
let delete_end = Position::new(clamped_end, last_line_char_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()
}