1use ropey::Rope;
5
6pub struct Buffer {
8 pub rope: Rope,
9 pub path: Option<String>,
10 pub dirty: bool,
11 pub epoch: u64,
13 pub readonly: bool,
15 pub name: Option<String>,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct Range {
23 pub start: usize,
24 pub end: usize,
25 pub linewise: bool,
28}
29
30impl Range {
31 pub fn charwise(start: usize, end: usize) -> Self {
32 debug_assert!(start <= end);
33 Self {
34 start,
35 end,
36 linewise: false,
37 }
38 }
39 pub fn linewise(start: usize, end: usize) -> Self {
40 debug_assert!(start <= end);
41 Self {
42 start,
43 end,
44 linewise: true,
45 }
46 }
47 pub fn len(&self) -> usize {
48 self.end - self.start
49 }
50 pub fn is_empty(&self) -> bool {
51 self.start == self.end
52 }
53}
54
55impl Buffer {
56 pub fn from_text(text: &str) -> Self {
57 Self {
58 rope: Rope::from_str(text),
59 path: None,
60 dirty: false,
61 epoch: 0,
62 readonly: false,
63 name: None,
64 }
65 }
66
67 pub fn open(path: &str) -> std::io::Result<Self> {
70 let text = match std::fs::read_to_string(path) {
71 Ok(t) => t,
72 Err(e) if e.kind() == std::io::ErrorKind::NotFound => String::new(),
73 Err(e) => return Err(e),
74 };
75 Ok(Self {
76 rope: Rope::from_str(&text),
77 path: Some(path.to_string()),
78 dirty: false,
79 epoch: 0,
80 readonly: false,
81 name: None,
82 })
83 }
84
85 pub fn save(&mut self) -> std::io::Result<()> {
86 if let Some(path) = &self.path {
87 std::fs::write(path, self.rope.to_string())?;
88 self.dirty = false;
89 }
90 Ok(())
91 }
92
93 pub fn len_bytes(&self) -> usize {
94 self.rope.len_bytes()
95 }
96 pub fn len_lines(&self) -> usize {
97 self.rope.len_lines()
98 }
99
100 pub fn last_content_line(&self) -> usize {
103 let mut l = self.len_lines().saturating_sub(1);
104 if self.len_bytes() > 0 && self.byte(self.len_bytes() - 1) == b'\n' && l > 0 {
105 l -= 1;
106 }
107 l
108 }
109
110 pub fn line_start(&self, line: usize) -> usize {
112 self.rope
113 .line_to_byte(line.min(self.len_lines().saturating_sub(1)))
114 }
115
116 pub fn line_end(&self, line: usize) -> usize {
118 let start = self.line_start(line);
119 let mut end = self.line_start((line + 1).min(self.len_lines().saturating_sub(1)));
120 if line + 1 >= self.len_lines() {
121 end = self.len_bytes();
122 }
123 if end > start && self.byte(end - 1) == b'\n' {
125 end -= 1;
126 }
127 end
128 }
129
130 pub fn line_of(&self, offset: usize) -> usize {
131 self.rope.byte_to_line(offset.min(self.len_bytes()))
132 }
133
134 pub fn col_of(&self, offset: usize) -> usize {
136 offset - self.line_start(self.line_of(offset))
137 }
138
139 pub fn byte(&self, offset: usize) -> u8 {
140 self.rope
141 .byte(offset.min(self.len_bytes().saturating_sub(1)))
142 }
143
144 pub fn byte_at(&self, offset: usize) -> Option<u8> {
145 if offset < self.len_bytes() {
146 Some(self.rope.byte(offset))
147 } else {
148 None
149 }
150 }
151
152 pub fn clamp_boundary(&self, mut offset: usize) -> usize {
155 offset = offset.min(self.len_bytes());
156 while offset > 0 && self.rope.try_byte_to_char(offset).is_err() {
157 offset -= 1;
158 }
159 offset
160 }
161
162 pub fn slice_string(&self, range: Range) -> String {
164 self.rope.byte_slice(range.start..range.end).to_string()
165 }
166
167 pub fn replace_all(&mut self, text: &str) {
169 self.rope = Rope::from_str(text);
170 self.epoch += 1;
171 }
172
173 pub fn delete(&mut self, range: Range) -> String {
175 let text = self.slice_string(range);
176 self.rope.remove(range.start..range.end);
177 self.dirty = true;
178 self.epoch += 1;
179 text
180 }
181
182 pub fn insert(&mut self, at: usize, text: &str) {
183 self.rope.insert(self.clamp_boundary(at), text);
184 self.dirty = true;
185 self.epoch += 1;
186 }
187
188 pub fn line_text(&self, line: usize) -> String {
189 let start = self.line_start(line);
190 let end = self.line_end(line);
191 self.rope.byte_slice(start..end).to_string()
192 }
193}