use std::ops::Range;
use ropey::{Rope, RopeSlice};
use super::{Cursor, Position};
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 {
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_lines().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 && text.char(text.len_chars() - 1) != '\n' {
0 } else {
1 }
} 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;
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
}
};
}
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<Position>> {
if let Some((end, Some(start))) = cur.positions().rev().next() {
let found = {
let mut found = None;
let sel = super::select((start, end));
let sel_text = text.slice(sel.clone());
let mut sel_candidate_idx = 0; for (search_idx, char) in text.slice(sel.end..).chars().enumerate() {
if sel_text.char(sel_candidate_idx) == char {
sel_candidate_idx += 1;
} else {
sel_candidate_idx = 0;
}
if sel_candidate_idx == sel_text.len_chars() {
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<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<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<Position> {
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<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));
}}
}