use super::types::{EditOp, IndentStyle};
impl super::CodeEditor {
pub(super) fn push_undo(&mut self, op: EditOp) {
self.undo_stack.push(op);
if self.undo_stack.len() > super::types::MAX_UNDO_HISTORY {
self.undo_stack.remove(0);
}
self.redo_stack.clear();
}
pub fn insert_char(&mut self, ch: char) {
if self.read_only {
return;
}
if self.has_selection() {
self.delete_selection();
}
if ch == '\n' {
self.insert_newline();
return;
}
if ch == '\t' {
self.insert_indent();
return;
}
if let Some(line) = self.lines.get_mut(self.cursor.0) {
let col = self.cursor.1.min(line.len());
line.insert(col, ch);
self.push_undo(EditOp::Insert {
line: self.cursor.0,
col,
text: ch.to_string(),
});
self.cursor.1 = col + 1;
}
if self.config.bracket_matching {
let close = match ch {
'(' => Some(')'),
'[' => Some(']'),
'{' => Some('}'),
'"' => Some('"'),
'\'' => Some('\''),
_ => None,
};
if let Some(close_ch) = close {
if let Some(line) = self.lines.get_mut(self.cursor.0) {
let col = self.cursor.1.min(line.len());
line.insert(col, close_ch);
}
}
}
}
pub fn insert_str(&mut self, s: &str) {
if self.read_only {
return;
}
if self.has_selection() {
self.delete_selection();
}
for ch in s.chars() {
if ch == '\n' {
self.insert_newline();
} else if let Some(line) = self.lines.get_mut(self.cursor.0) {
let col = self.cursor.1.min(line.len());
line.insert(col, ch);
self.cursor.1 = col + 1;
}
}
}
pub(super) fn insert_newline(&mut self) {
if self.read_only {
return;
}
let (line_idx, col) = self.cursor;
let indent = if self.config.auto_indent {
let current_line = &self.lines[line_idx];
let leading_ws: String = current_line
.chars()
.take_while(|c| c.is_whitespace())
.collect();
let trimmed = current_line.trim_end();
let extra_indent = trimmed
.chars()
.last()
.is_some_and(|c| matches!(c, '{' | '[' | '(' | ':'));
let base = leading_ws;
if extra_indent {
let indent_str = match self.config.indent_style {
IndentStyle::Spaces => " ".repeat(self.config.indent_size),
IndentStyle::Tabs => "\t".to_string(),
};
format!("{}{}", base, indent_str)
} else {
base
}
} else {
String::new()
};
if let Some(current) = self.lines.get_mut(line_idx) {
let rest: String = current.drain(col.min(current.len())..).collect();
let new_line = format!("{}{}", indent, rest);
self.lines.insert(line_idx + 1, new_line);
self.push_undo(EditOp::SplitLine {
line: line_idx,
col,
});
self.cursor = (line_idx + 1, indent.len());
}
self.ensure_cursor_visible();
}
pub(super) fn insert_indent(&mut self) {
if self.read_only {
return;
}
let indent = match self.config.indent_style {
IndentStyle::Spaces => " ".repeat(self.config.indent_size),
IndentStyle::Tabs => "\t".to_string(),
};
if let Some(line) = self.lines.get_mut(self.cursor.0) {
let col = self.cursor.1.min(line.len());
line.insert_str(col, &indent);
self.push_undo(EditOp::Insert {
line: self.cursor.0,
col,
text: indent.clone(),
});
self.cursor.1 = col + indent.len();
}
}
pub fn delete_char_before(&mut self) {
if self.read_only {
return;
}
if self.has_selection() {
self.delete_selection();
return;
}
let (line_idx, col) = self.cursor;
if col > 0 {
if let Some(line) = self.lines.get_mut(line_idx) {
if col <= line.len() {
let deleted = line.remove(col - 1);
self.push_undo(EditOp::Delete {
line: line_idx,
col: col - 1,
text: deleted.to_string(),
});
self.cursor.1 = col - 1;
}
}
} else if line_idx > 0 {
let current = self.lines.remove(line_idx);
let prev_len = self.lines[line_idx - 1].len();
self.lines[line_idx - 1].push_str(¤t);
self.push_undo(EditOp::MergeLine {
line: line_idx - 1,
col: prev_len,
});
self.cursor = (line_idx - 1, prev_len);
}
self.ensure_cursor_visible();
}
pub fn delete_char_at(&mut self) {
if self.read_only {
return;
}
if self.has_selection() {
self.delete_selection();
return;
}
let (line_idx, col) = self.cursor;
if let Some(line) = self.lines.get_mut(line_idx) {
if col < line.len() {
let deleted = line.remove(col);
self.push_undo(EditOp::Delete {
line: line_idx,
col,
text: deleted.to_string(),
});
} else if line_idx + 1 < self.lines.len() {
let next = self.lines.remove(line_idx + 1);
self.lines[line_idx].push_str(&next);
self.push_undo(EditOp::MergeLine {
line: line_idx,
col,
});
}
}
}
pub fn delete_line(&mut self) {
if self.read_only || self.lines.len() <= 1 {
return;
}
let line_idx = self.cursor.0;
self.lines.remove(line_idx);
self.cursor.0 = line_idx.min(self.lines.len().saturating_sub(1));
self.cursor.1 = 0;
self.ensure_cursor_visible();
}
pub fn duplicate_line(&mut self) {
if self.read_only {
return;
}
let line_idx = self.cursor.0;
let content = self.lines[line_idx].clone();
self.lines.insert(line_idx + 1, content);
self.cursor.0 = line_idx + 1;
self.ensure_cursor_visible();
}
pub fn undo(&mut self) {
if let Some(op) = self.undo_stack.pop() {
match &op {
EditOp::Insert { line, col, text } => {
if let Some(l) = self.lines.get_mut(*line) {
let end = (*col + text.len()).min(l.len());
l.drain(*col..end);
}
self.cursor = (*line, *col);
}
EditOp::Delete { line, col, text } => {
if let Some(l) = self.lines.get_mut(*line) {
l.insert_str(*col, text);
}
self.cursor = (*line, *col + text.len());
}
EditOp::SplitLine { line, col } => {
if *line + 1 < self.lines.len() {
let next = self.lines.remove(*line + 1);
if let Some(l) = self.lines.get_mut(*line) {
l.push_str(&next);
}
}
self.cursor = (*line, *col);
}
EditOp::MergeLine { line, col } => {
if let Some(l) = self.lines.get_mut(*line) {
let rest: String = l.drain(*col..).collect();
self.lines.insert(*line + 1, rest);
}
self.cursor = (*line + 1, 0);
}
}
self.redo_stack.push(op);
self.ensure_cursor_visible();
}
}
pub fn redo(&mut self) {
if let Some(op) = self.redo_stack.pop() {
match &op {
EditOp::Insert { line, col, text } => {
if let Some(l) = self.lines.get_mut(*line) {
l.insert_str(*col, text);
}
self.cursor = (*line, *col + text.len());
}
EditOp::Delete { line, col, text } => {
if let Some(l) = self.lines.get_mut(*line) {
let end = (*col + text.len()).min(l.len());
l.drain(*col..end);
}
self.cursor = (*line, *col);
}
EditOp::SplitLine { line, col } => {
if let Some(l) = self.lines.get_mut(*line) {
let rest: String = l.drain(*col..).collect();
self.lines.insert(*line + 1, rest);
}
self.cursor = (*line + 1, 0);
}
EditOp::MergeLine { line, col } => {
if *line + 1 < self.lines.len() {
let next = self.lines.remove(*line + 1);
if let Some(l) = self.lines.get_mut(*line) {
l.push_str(&next);
}
}
self.cursor = (*line, *col);
}
}
self.undo_stack.push(op);
self.ensure_cursor_visible();
}
}
}