@@ 1,467 0,0 @@
-//! Functions that compute a new char index.
-
-use std::ops::Range;
-use ropey::{Rope, RopeSlice};
-use cursor::{self, Cursor};
-use unicode_segmentation::UnicodeSegmentation;
-
-pub fn right(text: &Rope, char_idx: usize) -> usize {
- (char_idx + 1).min(text.len_chars())
-}
-
-pub fn left(char_idx: usize) -> usize {
- char_idx.saturating_sub(1)
-}
-
-pub fn vertical(text: &Rope, char_idx: usize, num_lines: isize) -> usize {
- // > If char_idx is one-past-the-end, then one-past-the-end line index is returned.
- // Circumvent this behavior by clipping to the last valid line index.
- let line_idx = text.char_to_line(char_idx)
- .min(text.len_lines().saturating_sub(1));
- if num_lines < 0 && line_idx > 0 || num_lines > 0 && line_idx < text.len_lines() - 1 {
- let x = char_idx - text.line_to_char(line_idx);
- let next_line_idx = (
- if num_lines > 0 {
- line_idx + num_lines as usize
- } else if let (value, false) = line_idx.overflowing_sub(num_lines.abs() as usize) {
- value
- } else { 0 }
- ).min(
- if text.len_lines() > 0 {
- text.len_lines() - 1
- } else { 0 }
- );
- let line = text.line(next_line_idx);
- let line_len = line.len_chars();
-
- text.line_to_char(next_line_idx) +
- x.min(line_len.saturating_sub(
- if line_len > 0 && line.char(line_len - 1) == '\n' { 1 } else { 0 }
- ))
- } else {
- char_idx
- }
-}
-
-pub fn line_start(text: &Rope, char_idx: usize) -> usize {
- let line_idx = text.char_to_line(
- char_idx.min(text.len_chars().saturating_sub(1))
- );
- text.line_to_char(line_idx)
-}
-
-pub fn line_end(text: &Rope, char_idx: usize) -> usize {
- let line_idx = text.char_to_line(char_idx);
- let len_lines = text.len_lines();
-
- text.line_to_char(line_idx) +
- if line_idx < len_lines {
- let line = text.line(line_idx);
- let line_len = line.len_chars();
-
- if line_len > 0 {
- line_len -
- if line_idx == len_lines - 1 && // last line
- text.char(text.len_chars() - 1) != '\n' // no trailing newline
- {
- 0 // End of line is end of text, so we go one-past-the-end.
- } else {
- 1 // Step onto the previous line break.
- }
- } else { 0 }
- } else { 0 }
-}
-
-pub fn word_left(text: &Rope, char_idx: usize) -> usize {
- word(text, char_idx, -1)
-}
-
-pub fn word_right(text: &Rope, char_idx: usize) -> usize {
- word(text, char_idx, 1)
-}
-
-fn word(text: &Rope, char_idx: usize, direction: i8) -> usize {
- if direction.is_negative() && char_idx == 0 ||
- direction.is_positive() && char_idx == text.len_chars()
- { return char_idx }
-
- enum Next {
- Whitespace,
- Word
- }
-
- impl Next {
- fn matches(&self, char: char) -> bool {
- match self {
- &Next::Whitespace => char.is_whitespace(),
- &Next::Word => char.is_alphanumeric()
- }
- }
-
- fn next(self, char: char) -> Option<Self> {
- if self.matches(char) {
- Some(self)
- } else {
- match self {
- Next::Whitespace => Some(Next::Word),
- Next::Word => None
- }
- }
- }
- }
-
- let len_chars = text.len_chars();
- let text = if direction.is_negative() {
- text.slice(..char_idx)
- } else if direction.is_positive() {
- text.slice(char_idx + 1..)
- } else {
- return char_idx
- };
- let mut next = Next::Whitespace;
- let mut current_idx = char_idx;
- /* We need to create both iterators for them to live long enough
- * even though we don't know whether we want to reverse yet.
- * XXX could overcome this by replacing the generalized for loop below with two specialized loops */
- let mut chars_enumerate = Chars::from_slice(text).enumerate();
- let mut chars_rev_enumerate = Chars::from_slice(text).rev().enumerate();
- let chars: &mut Iterator<Item=(usize, char)> = if direction.is_negative() {
- &mut chars_rev_enumerate
- } else {
- &mut chars_enumerate
- };
- let mut terminated = false;
- for (count, char) in chars {
- next = match next.next(char) {
- Some(next) => {
- current_idx = if direction.is_negative() {
- char_idx - count - 1
- } else {
- char_idx + count + 1
- };
- next
- },
- None => {
- if direction.is_positive() {
- current_idx = char_idx + count + 1;
- }
- terminated = true;
- break
- }
- };
- }
- /* Go to first or one-past-the-end char index
- * if we just stopped because there was no char left to handle. */
- if !terminated {
- if current_idx == len_chars - 1 {
- current_idx = len_chars;
- } else if current_idx == 1 {
- current_idx = 0
- }
- }
- current_idx
-}
-
-pub fn spawn(cur: &Cursor, text: &Rope) -> Option<Vec<cursor::Position>> {
- if let Some((end, Some(start))) = cur.positions().rev().next() {
- let found = {
- let mut found = None;
-
- let sel = cursor::select((start, end));
- let sel_text = text.slice(sel.clone());
-
- let mut sel_candidate_idx = 0; // expected char
- for (search_idx, char) in text.slice(sel.end..).chars().enumerate() {
- if sel_text.char(sel_candidate_idx) == char {
- // Found match, expect the next char.
- sel_candidate_idx += 1;
- } else {
- // We expected another char, reset the progress.
- sel_candidate_idx = 0;
- }
-
- if sel_candidate_idx == sel_text.len_chars() {
- // We found all chars of the selection.
- let found_start = sel.end + search_idx + 1 - sel_text.len_chars();
- let found_end = sel.end + search_idx + 1;
- found = Some(
- if end > start {(
- found_start,
- Some(found_end)
- )} else {(
- found_end,
- Some(found_start)
- )}
- );
- break;
- }
- }
-
- found
- };
-
- found.map(|found| {
- let found_iter = [found];
- let found_iter = found_iter.iter().map(|idx| *idx);
- cur.positions().chain(found_iter).collect::<Vec<_>>()
- })
- } else {
- None
- }
-}
-
-pub fn die(cur: &Cursor) -> Option<Vec<cursor::Position>> {
- if cur.positions().len() > 1 {
- let positions = {
- let mut positions = cur.positions();
- positions.next_back();
- positions.collect()
- };
- Some(positions)
- } else {
- None
- }
-}
-
-pub fn skip(cur: &Cursor, text: &Rope) -> Option<Vec<cursor::Position>> {
- spawn(cur, text).map(|mut positions| {
- let len = positions.len();
- positions.remove(len - 2);
- positions
- })
-}
-
-pub fn select_words(cur: &Cursor, text: &Rope) -> Vec<cursor::Position> {
- // XXX allocate each line only once for all cursors on it or avoid allocation altogether
- cur.positions()
- .map(|pos| match pos {
- (char_idx, None) => select_word(text, char_idx),
- (_, Some(sel_start)) => select_word(text, sel_start)
- })
- .map(|sel| (sel.end, Some(sel.start)))
- .collect()
-}
-
-fn select_word(text: &Rope, char_idx: usize) -> Range<usize> {
- let line_idx = text.char_to_line(char_idx);
- if line_idx == text.len_lines() {
- return char_idx..char_idx
- }
-
- let line_char_idx = text.line_to_char(line_idx);
- let line = text.line(line_idx);
- let line_string = line.to_string();
- let x = char_idx - line_char_idx;
-
- let mut sel = x..line.len_chars();
- for (idx, _) in line_string.split_word_bound_indices() {
- let idx = line_string[..idx].chars().count();
- if idx > x {
- sel.end = idx;
- break;
- } else {
- sel.start = idx;
- }
- }
- sel.start += line_char_idx;
- sel.end += line_char_idx;
- sel
-}
-
-pub fn select_lines(cur: &Cursor, text: &Rope) -> Vec<cursor::Position> {
- cur.positions()
- .map(|(char_idx, _)| select_line(text, char_idx))
- .map(|sel| (sel.end, Some(sel.start)))
- .collect()
-}
-
-fn select_line(text: &Rope, char_idx: usize) -> Range<usize> {
- let line_idx = text.char_to_line(char_idx);
- if line_idx < text.len_lines() {
- text.line_to_char(line_idx)..text.line_to_char(line_idx + 1)
- } else {
- char_idx..char_idx
- }
-}
-
-struct Chars<'a> {
- text: RopeSlice<'a>,
- char_idx: usize,
- popped: usize
-}
-
-impl<'a> Chars<'a> {
- #[allow(dead_code)]
- pub fn from_rope(text: &'a Rope) -> Self {
- Self {
- text: text.slice(..),
- char_idx: 0,
- popped: 0
- }
- }
-
- pub fn from_slice(text: RopeSlice<'a>) -> Self {
- Self {
- text: text,
- char_idx: 0,
- popped: 0
- }
- }
-}
-
-impl<'a> Iterator for Chars<'a> {
- type Item = char;
-
- fn next(&mut self) -> Option<Self::Item> {
- let idx = self.char_idx;
- if idx < self.text.len_chars() - self.popped {
- let char = self.text.char(idx);
- self.char_idx += 1;
- Some(char)
- } else {
- None
- }
- }
-}
-
-impl<'a> DoubleEndedIterator for Chars<'a> {
- fn next_back(&mut self) -> Option<Self::Item> {
- let len_chars = self.text.len_chars();
-
- if self.popped < len_chars {
- let idx = len_chars - 1 - self.popped;
- if idx > self.char_idx {
- let char = self.text.char(idx);
- self.popped += 1;
- Some(char)
- } else {
- None
- }
- } else {
- None
- }
- }
-}
-
-impl<'a> ExactSizeIterator for Chars<'a> {
- fn len(&self) -> usize {
- self.text.len_chars()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use ropey::Rope;
-
- const TEXT: &str = concat!(
- "1st\n",
- "2nd line\n",
- "3rd line"
- );
-
- #[test]
- fn right() {{
- let text = Rope::from_str(TEXT);
- let len_chars = text.len_chars();
- assert_eq!(1, super::right(&text, 0));
- assert_eq!(len_chars - 1, super::right(&text, len_chars - 2));
- assert_eq!(len_chars, super::right(&text, len_chars - 1));
- assert_eq!(len_chars, super::right(&text, len_chars));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::right(&text, 0));
- }}
-
- #[test]
- fn left() {
- assert_eq!(0, super::left(0));
- assert_eq!(0, super::left(1));
- assert_eq!(1, super::left(2));
- }
-
- #[test]
- fn down() {{
- let text = Rope::from_str(TEXT);
- assert_eq!(text.line_to_char(1), super::vertical(&text, text.line_to_char(0), 1));
- assert_eq!(text.line_to_char(2), super::vertical(&text, text.line_to_char(1), 1));
- assert_eq!(text.line_to_char(2), super::vertical(&text, text.line_to_char(2), 1));
- assert_eq!(text.line_to_char(2) + 3, super::vertical(&text, text.line_to_char(1) + 3, 1));
- assert_eq!(text.len_chars(), super::vertical(&text, text.line_to_char(2) - 1, 1));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::vertical(&text, 0, 1));
- }}
-
- #[test]
- fn up() {{
- let text = Rope::from_str(TEXT);
- assert_eq!(text.line_to_char(1), super::vertical(&text, text.line_to_char(2), -1));
- assert_eq!(text.line_to_char(0), super::vertical(&text, text.line_to_char(1), -1));
- assert_eq!(text.line_to_char(0), super::vertical(&text, text.line_to_char(0), -1));
- assert_eq!(text.line_to_char(0) + 3, super::vertical(&text, text.line_to_char(1) + 3, -1));
- assert_eq!(text.line_to_char(1) - 1, super::vertical(&text, text.line_to_char(2) - 2, -1));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::vertical(&text, 0, -1));
- }}
-
- #[test]
- fn line_start() {{
- let text = Rope::from_str(TEXT);
- assert_eq!(text.line_to_char(1), super::line_start(&text, 12));
- assert_eq!(text.line_to_char(1), super::line_start(&text, text.line_to_char(1)));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::line_start(&text, 0));
- }}
-
- #[test]
- fn line_end() {{
- let text = Rope::from_str(TEXT);
- assert_eq!(text.line_to_char(2) - 1, super::line_end(&text, 12));
- assert_eq!(text.line_to_char(2) - 1, super::line_end(&text, text.line_to_char(2) - 1));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::line_end(&text, 0));
- }}
-
- #[test]
- fn word_left() {{
- let text = Rope::from_str(TEXT);
- let len_chars = text.len_chars();
- assert_eq!(len_chars - 4, super::word_left(&text, len_chars));
- assert_eq!(len_chars - 8, super::word_left(&text, len_chars - 4));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::word_left(&text, 0));
- }}
-
- #[test]
- fn word_right() {{
- let text = Rope::from_str(TEXT);
- let len_chars = text.len_chars();
- assert_eq!(len_chars - 5, super::word_right(&text, len_chars - 8));
- assert_eq!(len_chars, super::word_right(&text, len_chars - 5));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::word_right(&text, 0));
- }}
-
- #[test]
- fn select_word() {{
- let text = Rope::from_str("\none two three");
- assert_eq!(5..8, super::select_word(&text, 5));
- assert_eq!(5..8, super::select_word(&text, 6));
- assert_eq!(5..8, super::select_word(&text, 7));
- assert_ne!(5..8, super::select_word(&text, 8));
- assert_eq!(9..14, super::select_word(&text, 9));
- assert_eq!(9..14, super::select_word(&text, 10));
- assert_eq!(9..14, super::select_word(&text, 11));
- assert_eq!(9..14, super::select_word(&text, 12));
- assert_eq!(9..14, super::select_word(&text, 13));
- assert_ne!(9..14, super::select_word(&text, 14));
- } {
- let text = Rope::from_str("");
- assert_eq!(0..0, super::select_word(&text, 0));
- }}
-}