1use std::fmt;
8use std::ops::Range;
9
10use ropey::Rope;
11
12use crate::position::{ByteOffset, CharIdx};
13
14#[derive(Clone, Default)]
15pub struct Document {
16 rope: Rope,
17}
18
19impl Document {
20 pub fn new(text: &str) -> Self {
21 Self {
22 rope: Rope::from_str(text),
23 }
24 }
25
26 pub fn chunks(&self) -> impl Iterator<Item = &str> + '_ {
28 self.rope.chunks()
29 }
30
31 pub fn len(&self) -> ByteOffset {
33 ByteOffset(self.rope.len_bytes())
34 }
35
36 pub fn is_empty(&self) -> bool {
37 self.rope.len_bytes() == 0
38 }
39
40 pub fn line_count(&self) -> usize {
43 self.rope.len_lines()
44 }
45
46 pub fn line_of(&self, pos: ByteOffset) -> usize {
48 self.rope.byte_to_line(pos.0)
49 }
50
51 pub fn line_start(&self, line: usize) -> ByteOffset {
52 ByteOffset(self.rope.line_to_byte(line))
53 }
54
55 pub fn line_end(&self, line: usize) -> ByteOffset {
57 if line + 1 < self.line_count() {
58 ByteOffset(self.rope.line_to_byte(line + 1) - 1)
59 } else {
60 self.len()
61 }
62 }
63
64 pub fn line_text(&self, line: usize) -> String {
66 self.slice(self.line_start(line)..self.line_end(line))
67 }
68
69 pub fn slice(&self, range: Range<ByteOffset>) -> String {
70 self.rope.byte_slice(range.start.0..range.end.0).to_string()
71 }
72
73 pub fn is_char_boundary(&self, pos: ByteOffset) -> bool {
74 if pos.0 > self.rope.len_bytes() {
75 return false;
76 }
77 let (chunk, chunk_start, _, _) = self.rope.chunk_at_byte(pos.0);
78 chunk.is_char_boundary(pos.0 - chunk_start)
79 }
80
81 pub fn chunk_at(&self, pos: ByteOffset) -> (&str, ByteOffset) {
85 let (chunk, start, _, _) = self.rope.chunk_at_byte(pos.0);
86 (chunk, ByteOffset(start))
87 }
88
89 pub fn char_at(&self, pos: ByteOffset) -> Option<char> {
91 if pos >= self.len() {
92 return None;
93 }
94 let (chunk, start, _, _) = self.rope.chunk_at_byte(pos.0);
95 chunk[pos.0 - start..].chars().next()
96 }
97
98 pub fn to_char_idx(&self, pos: ByteOffset) -> CharIdx {
99 CharIdx(self.rope.byte_to_char(pos.0))
100 }
101
102 pub fn replace(&mut self, range: Range<ByteOffset>, text: &str) {
104 debug_assert!(self.is_char_boundary(range.start) && self.is_char_boundary(range.end));
105 let start = self.to_char_idx(range.start);
106 if range.start < range.end {
107 let end = self.to_char_idx(range.end);
108 self.rope.remove(start.0..end.0);
109 }
110 if !text.is_empty() {
111 self.rope.insert(start.0, text);
112 }
113 }
114}
115
116impl fmt::Display for Document {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 for chunk in self.rope.chunks() {
119 f.write_str(chunk)?;
120 }
121 Ok(())
122 }
123}
124
125impl fmt::Debug for Document {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 write!(f, "Document({:?})", self.to_string())
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn lines_split_only_on_lf() {
137 let d = Document::new("a\r\nb\rc\u{2028}d\n");
138 assert_eq!(d.line_count(), 3);
139 assert_eq!(d.line_text(0), "a\r");
140 assert_eq!(d.line_text(1), "b\rc\u{2028}d");
141 assert_eq!(d.line_text(2), "");
142 assert_eq!(d.line_end(0), ByteOffset(2));
143 }
144
145 #[test]
146 fn replace_by_bytes() {
147 let mut d = Document::new("héllo wörld");
148 d.replace(ByteOffset(1)..ByteOffset(3), "e");
149 assert_eq!(d.to_string(), "hello wörld");
150 d.replace(ByteOffset(5)..ByteOffset(5), ",");
151 assert_eq!(d.to_string(), "hello, wörld");
152 assert!(!d.is_char_boundary(ByteOffset(9)));
153 }
154}