use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use super::{
EditMode, TextCursor,
clip::{copy_selection, paste_clipboard},
keys::is_command,
mutate::{
backspace, delete_forward, delete_range, delete_selection, insert_char,
move_caret,
},
};
use crate::textarea;
pub fn apply_edit_key(
text: &mut String,
cursor: &mut TextCursor,
key: KeyEvent,
mode: EditMode,
max_len: Option<usize>,
) -> bool {
let ctrl = is_command(key);
let shift = key.modifiers.contains(KeyModifiers::SHIFT);
let mut chars: Vec<char> = text.chars().collect();
let len = chars.len();
cursor.pos = cursor.pos.min(len);
if let Some(target) = motion_target(text, cursor.pos, key, mode) {
move_caret(cursor, target, shift);
return true;
}
let consumed = match key.code {
KeyCode::Char('a') if ctrl => {
cursor.select_all(len);
true
}
KeyCode::Char('u') if ctrl => {
let start = line_start(text, cursor.pos, mode);
delete_range(&mut chars, cursor, start, cursor.pos);
true
}
KeyCode::Char('k') if ctrl => {
let end = line_end(text, cursor.pos, mode);
delete_range(&mut chars, cursor, cursor.pos, end);
true
}
KeyCode::Char('c') if ctrl => {
copy_selection(&chars, cursor);
true
}
KeyCode::Char('x') if ctrl => {
copy_selection(&chars, cursor);
delete_selection(&mut chars, cursor);
true
}
KeyCode::Char('v') if ctrl => {
paste_clipboard(&mut chars, cursor, max_len, mode);
true
}
KeyCode::Backspace => {
backspace(&mut chars, cursor);
true
}
KeyCode::Delete => {
delete_forward(&mut chars, cursor);
true
}
KeyCode::Enter if mode.keeps_newlines() => {
insert_char(&mut chars, cursor, '\n', max_len);
true
}
KeyCode::Char(ch) if !ctrl => {
insert_char(&mut chars, cursor, ch, max_len);
true
}
_ => false,
};
if consumed {
*text = chars.into_iter().collect();
}
consumed
}
pub(super) fn motion_target(
text: &str,
pos: usize,
key: KeyEvent,
mode: EditMode,
) -> Option<usize> {
let multiline = mode.is_wrapping();
let target = match key.code {
KeyCode::Left => pos.saturating_sub(1),
KeyCode::Right => (pos + 1).min(text.chars().count()),
KeyCode::Home => line_start(text, pos, mode),
KeyCode::End => line_end(text, pos, mode),
KeyCode::Up if multiline => display_line_target(text, pos, mode, -1),
KeyCode::Down if multiline => display_line_target(text, pos, mode, 1),
KeyCode::PageUp if multiline => {
display_line_target(text, pos, mode, -page(mode))
}
KeyCode::PageDown if multiline => {
display_line_target(text, pos, mode, page(mode))
}
_ => return None,
};
Some(target)
}
pub(super) fn page(mode: EditMode) -> isize {
match mode {
EditMode::Multiline { height, .. }
| EditMode::Wrapped { height, .. } => height.max(1) as isize,
EditMode::SingleLine => 1,
}
}
pub(super) fn line_start(text: &str, pos: usize, mode: EditMode) -> usize {
let Some(width) = mode.wrap_width() else {
return 0;
};
let lines = textarea::wrap_offsets(text, width);
let (line, _) =
textarea::cursor_to_display(&lines, text.chars().count(), pos);
textarea::display_to_cursor(&lines, line, 0)
}
pub(super) fn line_end(text: &str, pos: usize, mode: EditMode) -> usize {
let Some(width) = mode.wrap_width() else {
return text.chars().count();
};
let lines = textarea::wrap_offsets(text, width);
let (line, _) =
textarea::cursor_to_display(&lines, text.chars().count(), pos);
let col = lines[line].0.chars().count();
textarea::display_to_cursor(&lines, line, col)
}
pub(super) fn display_line_target(
text: &str,
pos: usize,
mode: EditMode,
delta: isize,
) -> usize {
let Some(width) = mode.wrap_width() else {
return pos;
};
let lines = textarea::wrap_offsets(text, width);
let (line, col) =
textarea::cursor_to_display(&lines, text.chars().count(), pos);
let target =
(line as isize + delta).clamp(0, lines.len() as isize - 1) as usize;
textarea::display_to_cursor(&lines, target, col)
}