pub fn rope_line_to_str(rope: &ropey::Rope, r: usize) -> String {
let s = rope.line(r).to_string();
if s.ends_with('\n') {
s[..s.len() - 1].to_string()
} else {
s
}
}
pub fn rope_row_range_str(rope: &ropey::Rope, lo: usize, hi: usize) -> String {
let n = rope.len_lines();
let lo = lo.min(n.saturating_sub(1));
let hi = hi.min(n.saturating_sub(1));
if lo > hi {
return String::new();
}
let start_byte = rope.line_to_byte(lo);
let end_byte = if hi + 1 < n {
let step_back = rope.line_to_byte(hi + 1).saturating_sub(1);
hjkl_buffer::floor_char_boundary(rope, step_back)
} else {
rope.len_bytes()
};
rope.byte_slice(start_byte..end_byte).to_string()
}
pub fn rope_to_lines_vec(rope: &ropey::Rope) -> Vec<String> {
let n = rope.len_lines();
(0..n).map(|r| rope_line_to_str(rope, r)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use ropey::Rope;
#[test]
fn line_to_str_strips_newline() {
let rope = Rope::from_str("abc\ndef\n");
assert_eq!(rope_line_to_str(&rope, 0), "abc");
assert_eq!(rope_line_to_str(&rope, 1), "def");
}
#[test]
fn line_to_str_final_line_without_newline() {
let rope = Rope::from_str("abc\ndef");
assert_eq!(rope_line_to_str(&rope, 1), "def");
}
#[test]
fn row_range_str_joins_inclusive() {
let rope = Rope::from_str("a\nb\nc\n");
assert_eq!(rope_row_range_str(&rope, 0, 1), "a\nb");
assert_eq!(rope_row_range_str(&rope, 1, 2), "b\nc");
}
#[test]
fn row_range_str_single_row() {
let rope = Rope::from_str("a\nb\nc");
assert_eq!(rope_row_range_str(&rope, 1, 1), "b");
}
#[test]
fn row_range_str_clamps_out_of_bounds() {
let rope = Rope::from_str("a\nb");
assert_eq!(rope_row_range_str(&rope, 0, 99), "a\nb");
}
#[test]
fn row_range_str_empty_when_lo_gt_hi() {
let rope = Rope::from_str("a\nb\nc");
assert_eq!(rope_row_range_str(&rope, 2, 1), "");
}
#[test]
fn to_lines_vec_snapshots_all_rows() {
let rope = Rope::from_str("a\nb\nc");
assert_eq!(rope_to_lines_vec(&rope), vec!["a", "b", "c"]);
}
#[test]
fn row_range_str_multibyte_line_separators_do_not_panic() {
for sep in ["\u{2028}", "\u{2029}", "\u{85}", "\u{0B}", "\u{0C}"] {
let text = format!("abcdef{sep}ghijkl{sep}mnopqr");
let rope = Rope::from_str(&text);
for lo in 0..rope.len_lines() {
for hi in lo..rope.len_lines() {
let _ = rope_row_range_str(&rope, lo, hi);
}
}
assert_eq!(
rope_row_range_str(&rope, 0, 0),
"abcdef",
"separator {sep:?} must not leak into the row content"
);
}
}
#[test]
fn row_range_str_crlf_behaviour_unchanged() {
let rope = Rope::from_str("a\r\nb");
assert_eq!(rope_row_range_str(&rope, 0, 0), "a\r");
}
}