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> {
68 let text = std::fs::read_to_string(path)?;
69 Ok(Self {
70 rope: Rope::from_str(&text),
71 path: Some(path.to_string()),
72 dirty: false,
73 epoch: 0,
74 readonly: false,
75 name: None,
76 })
77 }
78
79 pub fn save(&mut self) -> std::io::Result<()> {
80 if let Some(path) = &self.path {
81 std::fs::write(path, self.rope.to_string())?;
82 self.dirty = false;
83 }
84 Ok(())
85 }
86
87 pub fn len_bytes(&self) -> usize {
88 self.rope.len_bytes()
89 }
90 pub fn len_lines(&self) -> usize {
91 self.rope.len_lines()
92 }
93
94 pub fn last_content_line(&self) -> usize {
97 let mut l = self.len_lines().saturating_sub(1);
98 if self.len_bytes() > 0 && self.byte(self.len_bytes() - 1) == b'\n' && l > 0 {
99 l -= 1;
100 }
101 l
102 }
103
104 pub fn line_start(&self, line: usize) -> usize {
106 self.rope
107 .line_to_byte(line.min(self.len_lines().saturating_sub(1)))
108 }
109
110 pub fn line_end(&self, line: usize) -> usize {
112 let start = self.line_start(line);
113 let mut end = self.line_start((line + 1).min(self.len_lines().saturating_sub(1)));
114 if line + 1 >= self.len_lines() {
115 end = self.len_bytes();
116 }
117 if end > start && self.byte(end - 1) == b'\n' {
119 end -= 1;
120 }
121 end
122 }
123
124 pub fn line_of(&self, offset: usize) -> usize {
125 self.rope.byte_to_line(offset.min(self.len_bytes()))
126 }
127
128 pub fn col_of(&self, offset: usize) -> usize {
130 offset - self.line_start(self.line_of(offset))
131 }
132
133 pub fn byte(&self, offset: usize) -> u8 {
134 self.rope
135 .byte(offset.min(self.len_bytes().saturating_sub(1)))
136 }
137
138 pub fn byte_at(&self, offset: usize) -> Option<u8> {
139 if offset < self.len_bytes() {
140 Some(self.rope.byte(offset))
141 } else {
142 None
143 }
144 }
145
146 pub fn clamp_boundary(&self, mut offset: usize) -> usize {
149 offset = offset.min(self.len_bytes());
150 while offset > 0 && self.rope.try_byte_to_char(offset).is_err() {
151 offset -= 1;
152 }
153 offset
154 }
155
156 pub fn slice_string(&self, range: Range) -> String {
158 self.rope.byte_slice(range.start..range.end).to_string()
159 }
160
161 pub fn replace_all(&mut self, text: &str) {
163 self.rope = Rope::from_str(text);
164 self.epoch += 1;
165 }
166
167 pub fn delete(&mut self, range: Range) -> String {
169 let text = self.slice_string(range);
170 self.rope.remove(range.start..range.end);
171 self.dirty = true;
172 self.epoch += 1;
173 text
174 }
175
176 pub fn insert(&mut self, at: usize, text: &str) {
177 self.rope.insert(self.clamp_boundary(at), text);
178 self.dirty = true;
179 self.epoch += 1;
180 }
181
182 pub fn line_text(&self, line: usize) -> String {
183 let start = self.line_start(line);
184 let end = self.line_end(line);
185 self.rope.byte_slice(start..end).to_string()
186 }
187}