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 len(&self) -> ByteOffset {
28 ByteOffset(self.rope.len_bytes())
29 }
30
31 pub fn is_empty(&self) -> bool {
32 self.rope.len_bytes() == 0
33 }
34
35 pub fn line_count(&self) -> usize {
38 self.rope.len_lines()
39 }
40
41 pub fn line_of(&self, pos: ByteOffset) -> usize {
43 self.rope.byte_to_line(pos.0)
44 }
45
46 pub fn line_start(&self, line: usize) -> ByteOffset {
47 ByteOffset(self.rope.line_to_byte(line))
48 }
49
50 pub fn line_end(&self, line: usize) -> ByteOffset {
52 if line + 1 < self.line_count() {
53 ByteOffset(self.rope.line_to_byte(line + 1) - 1)
54 } else {
55 self.len()
56 }
57 }
58
59 pub fn line_text(&self, line: usize) -> String {
61 self.slice(self.line_start(line)..self.line_end(line))
62 }
63
64 pub fn slice(&self, range: Range<ByteOffset>) -> String {
65 self.rope.byte_slice(range.start.0..range.end.0).to_string()
66 }
67
68 pub fn is_char_boundary(&self, pos: ByteOffset) -> bool {
69 if pos.0 > self.rope.len_bytes() {
70 return false;
71 }
72 let (chunk, chunk_start, _, _) = self.rope.chunk_at_byte(pos.0);
73 chunk.is_char_boundary(pos.0 - chunk_start)
74 }
75
76 pub fn chunk_at(&self, pos: ByteOffset) -> (&str, ByteOffset) {
80 let (chunk, start, _, _) = self.rope.chunk_at_byte(pos.0);
81 (chunk, ByteOffset(start))
82 }
83
84 pub fn char_at(&self, pos: ByteOffset) -> Option<char> {
86 if pos >= self.len() {
87 return None;
88 }
89 let (chunk, start, _, _) = self.rope.chunk_at_byte(pos.0);
90 chunk[pos.0 - start..].chars().next()
91 }
92
93 pub fn to_char_idx(&self, pos: ByteOffset) -> CharIdx {
94 CharIdx(self.rope.byte_to_char(pos.0))
95 }
96
97 pub fn replace(&mut self, range: Range<ByteOffset>, text: &str) {
99 debug_assert!(self.is_char_boundary(range.start) && self.is_char_boundary(range.end));
100 let start = self.to_char_idx(range.start);
101 if range.start < range.end {
102 let end = self.to_char_idx(range.end);
103 self.rope.remove(start.0..end.0);
104 }
105 if !text.is_empty() {
106 self.rope.insert(start.0, text);
107 }
108 }
109}
110
111impl fmt::Display for Document {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 for chunk in self.rope.chunks() {
114 f.write_str(chunk)?;
115 }
116 Ok(())
117 }
118}
119
120impl fmt::Debug for Document {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(f, "Document({:?})", self.to_string())
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn lines_split_only_on_lf() {
132 let d = Document::new("a\r\nb\rc\u{2028}d\n");
133 assert_eq!(d.line_count(), 3);
134 assert_eq!(d.line_text(0), "a\r");
135 assert_eq!(d.line_text(1), "b\rc\u{2028}d");
136 assert_eq!(d.line_text(2), "");
137 assert_eq!(d.line_end(0), ByteOffset(2));
138 }
139
140 #[test]
141 fn replace_by_bytes() {
142 let mut d = Document::new("héllo wörld");
143 d.replace(ByteOffset(1)..ByteOffset(3), "e");
144 assert_eq!(d.to_string(), "hello wörld");
145 d.replace(ByteOffset(5)..ByteOffset(5), ",");
146 assert_eq!(d.to_string(), "hello, wörld");
147 assert!(!d.is_char_boundary(ByteOffset(9)));
148 }
149}