use ropey::{Rope, RopeSlice};
#[derive(Clone, Debug, Default)]
pub struct RopeWrapper {
rope: Rope,
}
impl RopeWrapper {
#[must_use]
pub fn new() -> Self {
Self { rope: Rope::new() }
}
#[must_use]
pub fn from_str(s: &str) -> Self {
Self {
rope: Rope::from_str(s),
}
}
#[must_use]
pub fn len_bytes(&self) -> usize {
self.rope.len_bytes()
}
#[must_use]
pub fn len_chars(&self) -> usize {
self.rope.len_chars()
}
#[must_use]
pub fn len_lines(&self) -> usize {
self.rope.len_lines()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.rope.len_bytes() == 0
}
#[must_use]
pub fn line(&self, idx: usize) -> Option<RopeSlice<'_>> {
if idx < self.rope.len_lines() {
Some(self.rope.line(idx))
} else {
None
}
}
pub fn lines(&self) -> impl Iterator<Item = RopeSlice<'_>> {
self.rope.lines()
}
#[must_use]
pub fn slice<R>(&self, range: R) -> RopeSlice<'_>
where
R: std::ops::RangeBounds<usize>,
{
self.rope
.get_slice(range)
.unwrap_or_else(|| self.rope.slice(..0))
}
pub fn insert(&mut self, char_idx: usize, text: &str) {
if char_idx <= self.len_chars() {
self.rope.insert(char_idx, text);
}
}
pub fn remove<R>(&mut self, range: R)
where
R: std::ops::RangeBounds<usize>,
{
self.rope.remove(range);
}
pub fn replace(&mut self, text: &str) {
self.rope = Rope::from_str(text);
}
pub fn append(&mut self, text: &str) {
let len = self.len_chars();
self.rope.insert(len, text);
}
pub fn clear(&mut self) {
self.rope = Rope::new();
}
#[must_use]
pub fn to_string(&self) -> String {
self.rope.to_string()
}
#[must_use]
pub fn char_to_byte(&self, char_idx: usize) -> usize {
self.rope.char_to_byte(char_idx.min(self.len_chars()))
}
#[must_use]
pub fn byte_to_char(&self, byte_idx: usize) -> usize {
self.rope.byte_to_char(byte_idx.min(self.len_bytes()))
}
#[must_use]
pub fn char_to_line(&self, char_idx: usize) -> usize {
self.rope.char_to_line(char_idx.min(self.len_chars()))
}
#[must_use]
pub fn line_to_char(&self, line_idx: usize) -> usize {
if line_idx >= self.len_lines() {
self.len_chars()
} else {
self.rope.line_to_char(line_idx)
}
}
#[must_use]
pub fn inner(&self) -> &Rope {
&self.rope
}
}
impl From<&str> for RopeWrapper {
fn from(s: &str) -> Self {
Self::from_str(s)
}
}
impl From<String> for RopeWrapper {
fn from(s: String) -> Self {
Self::from_str(&s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rope_basic() {
let rope = RopeWrapper::from_str("Hello, world!");
assert_eq!(rope.len_chars(), 13);
assert_eq!(rope.len_lines(), 1);
}
#[test]
fn test_rope_multiline() {
let rope = RopeWrapper::from_str("Line 1\nLine 2\nLine 3");
assert_eq!(rope.len_lines(), 3);
assert_eq!(rope.line(0).unwrap().to_string(), "Line 1\n");
assert_eq!(rope.line(2).unwrap().to_string(), "Line 3");
}
#[test]
fn test_rope_insert() {
let mut rope = RopeWrapper::from_str("Hello!");
rope.insert(5, ", world");
assert_eq!(rope.to_string(), "Hello, world!");
}
#[test]
fn test_rope_remove() {
let mut rope = RopeWrapper::from_str("Hello, world!");
rope.remove(5..12);
assert_eq!(rope.to_string(), "Hello!");
}
}