use cosmic_text::{Affinity, Cursor};
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct ByteCursor {
pub cursor: Cursor,
pub byte_character_start: usize,
}
impl ByteCursor {
pub fn string_start() -> Self {
Self {
cursor: Cursor {
line: 0,
index: 0,
affinity: Default::default(),
},
byte_character_start: 0,
}
}
pub fn before_last_character(string: &str) -> Self {
if string.is_empty() {
Self::string_start()
} else {
let last_byte_offset = string
.char_indices()
.last()
.map(|(byte_idx, _ch)| byte_idx)
.expect("string is not empty, so there must be at least one character");
Self {
cursor: char_byte_offset_to_cursor(string, last_byte_offset)
.expect("the byte offset must be a valid cursor at this point"),
byte_character_start: last_byte_offset,
}
}
}
pub fn after_last_character(string: &str) -> Self {
let mut res = Self::before_last_character(string);
res.cursor.affinity = Affinity::After;
res.byte_character_start = string.len();
res
}
pub fn from_cursor(cursor: Cursor, string: &str) -> Option<ByteCursor> {
let mut res = Self::string_start();
let is_valid_cursor = res.update_cursor(cursor, string);
if is_valid_cursor {
Some(res)
} else {
None
}
}
pub fn char_index(&self, string: &str) -> Option<usize> {
char_byte_offset_to_char_index(string, self.byte_character_start)
}
pub fn update_cursor(&mut self, cursor: Cursor, string: &str) -> bool {
if cursor == self.cursor {
return true;
}
if let Some(byte_offset) = byte_offset_cursor_to_byte_offset(string, cursor) {
self.cursor = cursor;
self.byte_character_start = byte_offset;
true
} else {
false
}
}
pub fn update_byte_offset(&mut self, byte_offset: usize, string: &str) -> bool {
if self.byte_character_start == byte_offset {
return true;
}
if let Some(cursor) = char_byte_offset_to_cursor(string, byte_offset) {
self.cursor = cursor;
self.byte_character_start = byte_offset;
true
} else {
false
}
}
pub fn prev_char_byte_offset(&self, string: &str) -> Option<usize> {
previous_char_byte_offset(string, self.byte_character_start)
}
}
pub fn char_byte_offset_to_cursor(full_text: &str, char_byte_offset: usize) -> Option<Cursor> {
if char_byte_offset == full_text.len() {
let mut last_line_number = 0;
let mut last_line_len = 0;
for (line_number, line) in full_text.lines().enumerate() {
last_line_number = line_number;
last_line_len = line.len();
}
return Some(Cursor {
line: last_line_number,
index: last_line_len,
affinity: Affinity::Before,
});
}
let mut cumulative = 0;
let mut maybe_line = None;
let mut maybe_char = None;
for (line_number, line) in full_text.lines().enumerate() {
let line_len = line.len();
if char_byte_offset <= cumulative + line_len {
maybe_line = Some(line_number);
maybe_char = Some(char_byte_offset.saturating_sub(cumulative));
break;
}
cumulative += line_len + 1;
}
if let (Some(line), Some(index)) = (maybe_line, maybe_char) {
Some(Cursor {
line,
index,
affinity: Default::default(),
})
} else {
None
}
}
pub fn char_byte_offset_to_char_index(text: &str, char_byte_offset: usize) -> Option<usize> {
if char_byte_offset > text.len() {
return None;
}
if char_byte_offset == text.len() {
return Some(text.chars().count());
}
for (char_index, (byte_offset, _)) in text.char_indices().enumerate() {
if byte_offset == char_byte_offset {
return Some(char_index);
}
if byte_offset > char_byte_offset {
return None;
}
}
None
}
fn previous_char_byte_offset(text: &str, current: usize) -> Option<usize> {
if current == 0 {
return None;
}
if current > text.len() {
return None;
}
text[..current]
.char_indices()
.last()
.map(|(byte_idx, _ch)| byte_idx)
}
pub fn byte_offset_cursor_to_byte_offset(string: &str, cursor: Cursor) -> Option<usize> {
let mut char_byte_offset = 0;
for (line_number, line) in string.lines().enumerate() {
if line_number == cursor.line {
return if cursor.index <= line.len() {
char_byte_offset += cursor.index;
Some(char_byte_offset)
} else {
None
};
}
char_byte_offset += line.len() + 1;
}
None
}