use jagged::index::RowIndex;
use crate::{state::selection::Selection, EditorMode, EditorState, Index2, Lines};
pub fn insert_char(lines: &mut Lines, index: &mut Index2, ch: char, skip_move: bool) {
if lines.len() == index.row {
lines.push(Vec::new());
}
if ch == '\n' {
line_break(lines, index);
} else {
let Some(len_col) = lines.len_col(index.row) else {
return;
};
if index.col > len_col {
index.col = len_col.saturating_sub(1);
}
lines.insert(*index, ch);
if !skip_move {
index.col += 1;
}
}
}
pub fn insert_str(lines: &mut Lines, index: &mut Index2, text: &str) {
for (i, ch) in text.chars().enumerate() {
let is_last = i == text.len().saturating_sub(1);
insert_char(lines, index, ch, is_last);
}
}
pub fn append_str(lines: &mut Lines, index: &mut Index2, text: &str) {
if !lines.is_empty() && lines.len_col(index.row).unwrap_or_default() > 0 {
index.col += 1;
}
for ch in text.chars() {
insert_char(lines, index, ch, false);
}
index.col = index.col.saturating_sub(1);
}
pub(crate) fn line_break(lines: &mut Lines, index: &mut Index2) {
if index.col == 0 {
lines.insert(RowIndex::new(index.row), vec![]);
} else if index.col >= lines.len_col(index.row).unwrap_or_default() {
if index.row == lines.len() {
lines.insert(RowIndex::new(index.row), vec![]);
} else {
lines.insert(RowIndex::new(index.row + 1), vec![]);
}
} else {
let mut rest = lines.split_off(*index);
lines.append(&mut rest);
}
index.row += 1;
index.col = 0;
}
pub(crate) fn max_col(lines: &Lines, index: &Index2, mode: EditorMode) -> usize {
if mode == EditorMode::Normal {
max_col_normal(lines, index)
} else {
max_col_insert(lines, index)
}
}
pub(crate) fn max_col_normal(lines: &Lines, index: &Index2) -> usize {
if lines.is_empty() {
return 0;
}
let Some(len_col) = lines.len_col(index.row) else {
return 0;
};
len_col.saturating_sub(1)
}
pub(crate) fn max_col_insert(lines: &Lines, index: &Index2) -> usize {
if lines.is_empty() {
return 0;
}
lines.len_col(index.row).unwrap_or_default()
}
pub(crate) fn max_row(state: &EditorState) -> usize {
if state.mode == EditorMode::Insert {
state.lines.len()
} else {
state.lines.len().saturating_sub(1)
}
}
pub(crate) fn clamp_column(state: &mut EditorState) {
let max_col = max_col(&state.lines, &state.cursor, state.mode);
state.cursor.col = state.cursor.col.min(max_col);
}
pub(crate) fn set_selection(selection: &mut Option<Selection>, index: Index2) {
if let Some(selection) = selection {
selection.end = index;
}
}
pub(crate) fn skip_whitespace(lines: &Lines, index: &mut Index2) {
if let Some(line) = lines.get(RowIndex::new(index.row)) {
for (i, &ch) in line.iter().enumerate().skip(index.col) {
if !ch.is_ascii_whitespace() {
index.col = i;
break;
}
}
}
}
pub(crate) fn skip_whitespace_rev(lines: &Lines, index: &mut Index2) {
if let Some(line) = lines.get(RowIndex::new(index.row)) {
let skip = line.len().saturating_sub(index.col + 1);
for &ch in line.iter().rev().skip(skip) {
if !ch.is_ascii_whitespace() {
break;
}
index.col = index.col.saturating_sub(1);
}
}
}
#[must_use]
pub(crate) fn len_col(state: &EditorState) -> usize {
state.lines.len_col(state.cursor.row).unwrap_or_default()
}
pub fn is_out_of_bounds(lines: &Lines, index: &Index2) -> bool {
if index.row >= lines.len() {
return true;
}
if index.col >= lines.len_col_unchecked(index.row) {
return true;
}
false
}
#[must_use]
pub fn find_matching_bracket(lines: &Lines, index: Index2) -> Option<Index2> {
let &opening_bracket = lines.get(index)?;
let (closing_bracket, reverse) = match opening_bracket {
'{' => ('}', false),
'}' => ('{', true),
'(' => (')', false),
')' => ('(', true),
'[' => (']', false),
']' => ('[', true),
_ => return None,
};
let mut counter = 0;
let iter: Box<dyn Iterator<Item = (Option<&char>, Index2)>> = if reverse {
Box::new(lines.iter().from(index).rev().skip(1))
} else {
Box::new(lines.iter().from(index).skip(1))
};
for (value, index) in iter {
let Some(&value) = value else { continue };
if value == opening_bracket {
counter += 1;
}
if value == closing_bracket {
if counter == 0 {
return Some(index);
}
counter -= 1;
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn test_lines() -> Lines {
Lines::from("Hello World!\n\n123.")
}
#[test]
fn test_skip_whitespace() {
let lines = Lines::from(" World!");
let mut index = Index2::new(0, 0);
skip_whitespace(&lines, &mut index);
assert_eq!(index.col, 2);
skip_whitespace(&lines, &mut index);
assert_eq!(index.col, 2);
}
#[test]
fn test_skip_whitespace_rev() {
let lines = Lines::from(" x World!");
let mut index = Index2::new(0, 3);
skip_whitespace_rev(&lines, &mut index);
assert_eq!(index.col, 2);
skip_whitespace_rev(&lines, &mut index);
assert_eq!(index.col, 2);
index.col = 1;
skip_whitespace_rev(&lines, &mut index);
assert_eq!(index.col, 0);
}
#[test]
fn test_insert_str() {
let mut lines = test_lines();
let mut index = Index2::new(0, 5);
insert_str(&mut lines, &mut index, ",\n");
assert_eq!(index, Index2::new(1, 0));
assert_eq!(lines, Lines::from("Hello,\n World!\n\n123."));
}
#[test]
fn test_insert_char() {
let mut lines = test_lines();
let mut index = Index2::new(0, 5);
insert_char(&mut lines, &mut index, '?', false);
assert_eq!(index, Index2::new(0, 6));
assert_eq!(lines, Lines::from("Hello? World!\n\n123."));
}
#[test]
fn test_insert_char_out_of_bounds() {
let mut lines = test_lines();
let mut index = Index2::new(99, 0);
insert_char(&mut lines, &mut index, '?', false);
assert_eq!(index, Index2::new(99, 0));
assert_eq!(lines, Lines::from("Hello World!\n\n123."));
}
#[test]
fn test_append_str() {
let mut lines = test_lines();
let mut index = Index2::new(0, 5);
append_str(&mut lines, &mut index, ",\n");
assert_eq!(index, Index2::new(1, 0));
assert_eq!(lines, Lines::from("Hello ,\nWorld!\n\n123."));
let mut lines = test_lines();
let mut index = Index2::new(1, 0);
append_str(&mut lines, &mut index, "abc");
assert_eq!(index, Index2::new(1, 2));
assert_eq!(lines, Lines::from("Hello World!\nabc\n123."));
}
#[test]
fn test_find_matching_bracket() {
let cursor = Index2::new(0, 0);
let lines = Lines::from("{ab\n{{}}c}d");
let closing_bracket = find_matching_bracket(&lines, cursor);
assert_eq!(closing_bracket, Some(Index2::new(1, 5)));
let cursor = Index2::new(1, 5);
let closing_bracket = find_matching_bracket(&lines, cursor);
assert_eq!(closing_bracket, Some(Index2::new(0, 0)));
}
}