use crate::{
buffer::Buffer,
undo::{Action, UndoTree},
};
use std::iter::once;
impl Buffer {
pub fn set_cursor(&mut self, cursor: usize) {
self.cursor = cursor;
self.clamp_cursor();
}
pub fn clamp_cursor(&mut self) {
self.cursor = usize::min(self.cursor, usize::max(self.rope.len_chars(), 0));
}
pub fn delete_curr_char(&mut self) {
if self.cursor == self.rope.len_chars() {
return;
}
if self.get_curr_char() == '\n' {
self.update_list_remove_current();
}
self.rope.remove(self.cursor..=self.cursor);
}
pub fn replace_curr_char(&mut self, c: char) {
self.rope.remove(self.cursor..=self.cursor);
self.rope.insert(self.cursor, &c.to_string());
self.update_list_use_current_line();
}
pub fn insert_char_at(&mut self, c: char, cursor: usize) {
if c == '\n' {
self.update_list_add_current();
}
self.rope.insert_char(cursor, c);
self.update_list_use_current_line();
}
pub fn insert_char(&mut self, c: char) {
self.insert_char_at(c, self.cursor);
}
pub fn insert_newline(&mut self) -> String {
let first_col = self.get_first_non_whitespace_col().unwrap_or(0);
self.update_list_add_current();
self.rope.insert_char(self.cursor, '\n');
self.set_cursor(self.cursor + 1);
self.insert_n_times(' ', first_col);
self.set_cursor(self.cursor + first_col);
once('\n')
.chain((0..first_col).map(|_| ' '))
.collect::<String>()
}
pub fn insert_n_times_at(&mut self, c: char, n: usize, cursor: usize) {
if c == '\n' {
(0..n).for_each(|_| self.update_list_add_current());
}
(0..n).for_each(|_| self.insert_char_at(c, cursor));
}
pub fn insert_n_times(&mut self, c: char, n: usize) {
self.insert_n_times_at(c, n, self.cursor);
}
pub fn get_curr_char(&self) -> char {
self.rope.char(self.cursor)
}
pub fn get_next_char(&self) -> Option<char> {
if self.cursor + 1 == self.rope.len_chars() {
None
} else {
Some(self.rope.char(self.cursor + 1))
}
}
pub fn next_and_char(&mut self) -> Option<char> {
if self.cursor <= self.rope.len_chars() {
self.cursor += 1;
return Some(self.rope.char(self.cursor));
}
None
}
pub fn next_char(&mut self) {
self.set_cursor(self.cursor + 1);
}
pub fn get_prev_char(&self) -> Option<char> {
if self.cursor == 0 {
None
} else {
Some(self.rope.char(self.cursor - 1))
}
}
pub const fn prev_char(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
}
}
pub fn get_col(&self) -> usize {
let start_idx = self.get_start_of_line();
self.cursor - start_idx
}
pub fn set_col(&mut self, col: usize) {
let start_idx = self.get_start_of_line();
self.set_cursor(start_idx + col);
}
pub fn get_row(&self) -> usize {
self.rope.char_to_line(self.cursor)
}
pub fn set_row(&mut self, row: usize) {
let curr_row = self.get_row();
if curr_row == row || self.rope.len_lines() <= row {
return;
}
let col = self.get_col();
let end_next_row = self.get_end_of_n_line(row);
let start_of_next_row = self.rope.line_to_char(row);
let new_position = usize::min(start_of_next_row + col, end_next_row);
self.set_cursor(new_position);
}
pub fn count_spaces_backwards(&self) -> usize {
let mut space_count = 0;
let mut idx = self.cursor;
while let Some(c) = self.rope.get_char(idx)
&& c == ' '
{
space_count += 1;
if idx == 0 {
break;
}
idx -= 1;
}
space_count
}
pub fn delete_to_4_spaces_alignment(&mut self, space_count: usize) -> String {
let mut deleted = String::with_capacity(4);
let mut leftover = space_count % 4;
if leftover == 0 {
leftover = 4;
}
assert!(space_count >= leftover);
self.cursor -= usize::max(leftover, 1) - 1;
(0..leftover).for_each(|_| {
deleted.push(self.get_curr_char());
self.delete_curr_char();
});
deleted
}
pub fn find_occurences(&self, text: &[char]) -> Vec<usize> {
let mut count = 0;
let mut idxs_of_substitution: Vec<usize> = Vec::with_capacity(4);
for (i, char) in self.rope.chars().enumerate() {
if char == text[count] {
count += 1;
} else {
count = 0;
}
if count == text.len() {
idxs_of_substitution.push(i + 1);
count = 0;
}
}
idxs_of_substitution
}
pub fn replace_text(
&mut self,
new: &str,
original: &str,
idxs_of_substitution: &[usize],
undo_tree: &mut UndoTree,
undoing: bool,
) {
let mut offset = new.len() - original.len();
for (i, end_idx) in idxs_of_substitution.iter().enumerate() {
offset *= i;
let new_start_idx = end_idx - original.len() + offset;
let new_end_idx = end_idx + offset;
self.rope.remove(new_start_idx..new_end_idx);
self.rope.insert(new_start_idx, new);
if undoing {
let action = Action::replace(vec![new_start_idx + new.len()], &original, &new);
undo_tree.new_action_merge(action);
}
}
self.clamp_cursor();
}
pub fn backspace(&mut self, undo_tree: &mut UndoTree) {
if self.cursor == 0 {
return;
}
self.cursor -= 1;
let char = self.get_curr_char();
let space_count = self.count_spaces_backwards();
if space_count > 1 {
let deleted = self.delete_to_4_spaces_alignment(space_count);
self.update_list_use_current_line();
let action = Action::delete(self.cursor, &deleted);
undo_tree.new_action_merge(action);
} else {
self.delete_curr_char();
self.update_list_use_current_line();
let action = Action::delete(self.cursor, &char);
undo_tree.new_action_merge(action);
}
}
pub fn goto_next_string(&mut self, str: &[char]) {
if str.is_empty() {
return;
}
let mut i = 0;
let mut cursor = self.cursor;
let within_bounds = self.rope.len_chars() > cursor + str.len();
if within_bounds
&& self.rope.slice(self.cursor..self.cursor + str.len())
== str.iter().collect::<String>()
{
cursor += 1;
}
while let Some(s) = self.rope.get_char(cursor + i) {
if s == str[i] {
i += 1;
} else {
cursor += 1;
i = 0;
}
if i == str.len() {
self.set_cursor(cursor);
return;
}
}
}
pub fn goto_prev_string(&mut self, str: &[char]) {
if str.is_empty() {
return;
}
let mut i = 0;
let mut cursor = self.cursor;
while let Some(s) = self.rope.get_char(cursor - i) {
if s == str[str.len() - i - 1] {
i += 1;
} else {
cursor -= 1;
i = 0;
}
if cursor == 0 {
return;
}
if i == str.len() {
self.set_cursor(cursor + 1 - str.len());
return;
}
}
}
fn find_gen(
&self,
target: char,
from: usize,
stop_position: usize,
traverse: impl Fn(&mut usize),
) -> Option<usize> {
let mut cursor = from;
if let Some(c) = self.rope.get_char(cursor)
&& c == target
{
traverse(&mut cursor);
}
loop {
match self.rope.get_char(cursor) {
Some(c) if c == target => return Some(cursor),
Some(_) if cursor == stop_position => return None,
Some(_) => traverse(&mut cursor),
None => return None,
}
}
}
fn find_next_gen(&self, target: char, from: usize, stop_position: usize) -> Option<usize> {
self.find_gen(target, from, stop_position, |cursor: &mut usize| {
*cursor += 1;
})
}
pub fn find_next_on_line_from(&self, target: char, from: usize) -> Option<usize> {
self.find_next_gen(target, from, self.get_curr_line().len_chars())
}
pub fn find_next_on_line(&self, target: char) -> Option<usize> {
self.find_next_gen(target, self.cursor, self.get_curr_line().len_chars())
}
pub fn find_next(&self, target: char) -> Option<usize> {
self.find_next_gen(target, self.cursor, self.rope.len_chars() - 1)
}
fn find_prev_gen(&self, target: char, from: usize, stop_position: usize) -> Option<usize> {
self.find_gen(target, from, stop_position, |cursor: &mut usize| {
*cursor -= 1;
})
}
pub fn _find_prev_on_line_from(&self, target: char, from: usize) -> Option<usize> {
self.find_prev_gen(target, from, self.get_curr_line().len_chars())
}
pub fn find_prev_on_line(&self, target: char) -> Option<usize> {
self.find_prev_gen(target, self.cursor, self.get_start_of_line())
}
pub fn find_prev(&self, target: char) -> Option<usize> {
self.find_prev_gen(target, self.cursor, 0)
}
}