1use ropey::Rope;
2
3#[derive(Debug, Clone)]
8pub struct Document {
9 rope: Rope,
10 version: i32,
11 language_id: String,
12}
13
14impl Document {
15 #[must_use]
17 pub fn new(text: &str, language_id: String) -> Self {
18 Self {
19 rope: Rope::from_str(text),
20 version: 0,
21 language_id,
22 }
23 }
24
25 pub fn apply_edit(&mut self, start_byte: usize, end_byte: usize, new_text: &str) {
31 let start_char = self.rope.byte_to_char(start_byte);
32 let end_char = self.rope.byte_to_char(end_byte);
33 self.rope.remove(start_char..end_char);
34 if !new_text.is_empty() {
35 self.rope.insert(start_char, new_text);
36 }
37 self.version += 1;
38 }
39
40 pub fn apply_edit_lc(
42 &mut self,
43 start_line: usize,
44 start_col: usize,
45 end_line: usize,
46 end_col: usize,
47 new_text: &str,
48 ) {
49 let start_char = self.rope.line_to_char(start_line) + start_col;
50 let end_char = self.rope.line_to_char(end_line) + end_col;
51 self.rope.remove(start_char..end_char);
52 if !new_text.is_empty() {
53 self.rope.insert(start_char, new_text);
54 }
55 self.version += 1;
56 }
57
58 pub fn set_content(&mut self, text: &str) {
60 self.rope = Rope::from_str(text);
61 self.version += 1;
62 }
63
64 #[must_use]
66 pub fn text(&self) -> String {
67 self.rope.to_string()
68 }
69
70 #[must_use]
72 pub fn slice_bytes(&self, start: usize, end: usize) -> String {
73 let start_char = self.rope.byte_to_char(start);
74 let end_char = self.rope.byte_to_char(end);
75 self.rope.slice(start_char..end_char).to_string()
76 }
77
78 #[must_use]
80 pub fn line(&self, idx: usize) -> Option<String> {
81 if idx < self.rope.len_lines() {
82 Some(self.rope.line(idx).to_string())
83 } else {
84 None
85 }
86 }
87
88 #[must_use]
90 pub fn byte_to_line_col(&self, byte_offset: usize) -> (usize, usize) {
91 let char_idx = self.rope.byte_to_char(byte_offset);
92 let line = self.rope.char_to_line(char_idx);
93 let line_start = self.rope.line_to_char(line);
94 let col = char_idx - line_start;
95 (line, col)
96 }
97
98 #[must_use]
100 pub fn line_col_to_byte(&self, line: usize, col: usize) -> usize {
101 let char_idx = self.rope.line_to_char(line) + col;
102 self.rope.char_to_byte(char_idx)
103 }
104
105 #[must_use]
107 pub fn line_count(&self) -> usize {
108 self.rope.len_lines()
109 }
110
111 #[must_use]
113 pub fn len_bytes(&self) -> usize {
114 self.rope.len_bytes()
115 }
116
117 #[must_use]
119 pub fn len_chars(&self) -> usize {
120 self.rope.len_chars()
121 }
122
123 #[must_use]
125 pub fn is_empty(&self) -> bool {
126 self.rope.len_bytes() == 0
127 }
128
129 #[must_use]
131 pub const fn version(&self) -> i32 {
132 self.version
133 }
134
135 #[must_use]
137 pub fn language_id(&self) -> &str {
138 &self.language_id
139 }
140}
141
142#[derive(Debug, Default)]
144pub struct DocumentStore {
145 documents: std::collections::HashMap<String, Document>,
146}
147
148impl DocumentStore {
149 #[must_use]
150 pub fn new() -> Self {
151 Self::default()
152 }
153
154 pub fn open(&mut self, uri: String, text: &str, language_id: String) {
156 self.documents.insert(uri, Document::new(text, language_id));
157 }
158
159 pub fn close(&mut self, uri: &str) -> Option<Document> {
161 self.documents.remove(uri)
162 }
163
164 #[must_use]
166 pub fn get(&self, uri: &str) -> Option<&Document> {
167 self.documents.get(uri)
168 }
169
170 pub fn get_mut(&mut self, uri: &str) -> Option<&mut Document> {
172 self.documents.get_mut(uri)
173 }
174
175 #[must_use]
177 pub fn len(&self) -> usize {
178 self.documents.len()
179 }
180
181 #[must_use]
183 pub fn is_empty(&self) -> bool {
184 self.documents.is_empty()
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn new_document_has_correct_text() {
194 let doc = Document::new("Hello, world!", "markdown".to_string());
195 assert_eq!(doc.text(), "Hello, world!");
196 assert_eq!(doc.version(), 0);
197 assert_eq!(doc.language_id(), "markdown");
198 }
199
200 #[test]
201 fn apply_edit_insertion() {
202 let mut doc = Document::new("Hello world", "markdown".to_string());
203 doc.apply_edit(5, 6, ", ");
205 assert_eq!(doc.text(), "Hello, world");
206 assert_eq!(doc.version(), 1);
207 }
208
209 #[test]
210 fn apply_edit_deletion() {
211 let mut doc = Document::new("Hello, world!", "markdown".to_string());
212 doc.apply_edit(5, 7, "");
214 assert_eq!(doc.text(), "Helloworld!");
215 assert_eq!(doc.version(), 1);
216 }
217
218 #[test]
219 fn apply_edit_replacement() {
220 let mut doc = Document::new("Hello, world!", "markdown".to_string());
221 doc.apply_edit(7, 12, "Rust");
223 assert_eq!(doc.text(), "Hello, Rust!");
224 assert_eq!(doc.version(), 1);
225 }
226
227 #[test]
228 fn apply_edit_lc() {
229 let mut doc = Document::new("line one\nline two\nline three", "markdown".to_string());
230 doc.apply_edit_lc(1, 5, 1, 8, "TWO");
232 assert_eq!(doc.text(), "line one\nline TWO\nline three");
233 }
234
235 #[test]
236 fn set_content_replaces_all() {
237 let mut doc = Document::new("old content", "markdown".to_string());
238 doc.set_content("new content");
239 assert_eq!(doc.text(), "new content");
240 assert_eq!(doc.version(), 1);
241 }
242
243 #[test]
244 fn slice_bytes() {
245 let doc = Document::new("Hello, world!", "markdown".to_string());
246 assert_eq!(doc.slice_bytes(7, 12), "world");
247 }
248
249 #[test]
250 fn line_access() {
251 let doc = Document::new("first\nsecond\nthird", "markdown".to_string());
252 assert_eq!(doc.line(0).unwrap(), "first\n");
253 assert_eq!(doc.line(1).unwrap(), "second\n");
254 assert_eq!(doc.line(2).unwrap(), "third");
255 assert!(doc.line(3).is_none());
256 assert_eq!(doc.line_count(), 3);
257 }
258
259 #[test]
260 fn byte_to_line_col_and_back() {
261 let doc = Document::new("abc\ndef\nghi", "markdown".to_string());
262 let (line, col) = doc.byte_to_line_col(4);
264 assert_eq!((line, col), (1, 0));
265 assert_eq!(doc.line_col_to_byte(1, 0), 4);
266 }
267
268 #[test]
269 fn unicode_handling() {
270 let mut doc = Document::new("Hëllo wörld", "markdown".to_string());
271 assert!(doc.len_bytes() > doc.len_chars());
272 let text = doc.text();
274 let start = text.find("wörld").unwrap();
275 let end = start + "wörld".len();
276 doc.apply_edit(start, end, "rust");
277 assert_eq!(doc.text(), "Hëllo rust");
278 }
279
280 #[test]
281 fn document_store_operations() {
282 let mut store = DocumentStore::new();
283 assert!(store.is_empty());
284
285 store.open(
286 "file:///test.md".to_string(),
287 "Hello",
288 "markdown".to_string(),
289 );
290 assert_eq!(store.len(), 1);
291 assert!(!store.is_empty());
292
293 let doc = store.get("file:///test.md").unwrap();
294 assert_eq!(doc.text(), "Hello");
295
296 let doc_mut = store.get_mut("file:///test.md").unwrap();
298 doc_mut.apply_edit(5, 5, ", world!");
299 assert_eq!(
300 store.get("file:///test.md").unwrap().text(),
301 "Hello, world!"
302 );
303
304 let closed = store.close("file:///test.md");
306 assert!(closed.is_some());
307 assert!(store.is_empty());
308 }
309
310 #[test]
311 fn multiple_sequential_edits() {
312 let mut doc = Document::new("The quick brown fox", "markdown".to_string());
313 doc.apply_edit(4, 9, "slow");
315 assert_eq!(doc.text(), "The slow brown fox");
316 doc.apply_edit(9, 14, "red");
318 assert_eq!(doc.text(), "The slow red fox");
319 assert_eq!(doc.version(), 2);
320 }
321
322 #[test]
323 fn is_empty() {
324 let doc = Document::new("", "markdown".to_string());
325 assert!(doc.is_empty());
326 let doc2 = Document::new("x", "markdown".to_string());
327 assert!(!doc2.is_empty());
328 }
329}