perl_workspace_index/workspace/
document_store.rs1use crate::line_index::LineIndex;
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9
10#[derive(Debug, Clone)]
12pub struct Document {
13 pub uri: String,
15 pub version: i32,
17 pub text: String,
19 pub line_index: LineIndex,
21}
22
23impl Document {
24 pub fn new(uri: String, version: i32, text: String) -> Self {
26 let line_index = LineIndex::new(text.clone());
27 Self { uri, version, text, line_index }
28 }
29
30 pub fn update(&mut self, version: i32, text: String) {
32 self.version = version;
33 self.text = text.clone();
34 self.line_index = LineIndex::new(text);
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct DocumentStore {
41 documents: Arc<RwLock<HashMap<String, Document>>>,
42}
43
44impl DocumentStore {
45 pub fn new() -> Self {
47 Self { documents: Arc::new(RwLock::new(HashMap::new())) }
48 }
49
50 pub fn uri_key(uri: &str) -> String {
53 perl_uri::uri_key(uri)
54 }
55
56 pub fn open(&self, uri: String, version: i32, text: String) {
58 let key = Self::uri_key(&uri);
59 let doc = Document::new(uri, version, text);
60
61 if let Ok(mut docs) = self.documents.write() {
62 docs.insert(key, doc);
63 }
64 }
65
66 pub fn update(&self, uri: &str, version: i32, text: String) -> bool {
68 let key = Self::uri_key(uri);
69
70 let Ok(mut docs) = self.documents.write() else {
71 return false;
72 };
73 if let Some(doc) = docs.get_mut(&key) {
74 doc.update(version, text);
75 true
76 } else {
77 false
78 }
79 }
80
81 pub fn close(&self, uri: &str) -> bool {
83 let key = Self::uri_key(uri);
84 let Ok(mut docs) = self.documents.write() else {
85 return false;
86 };
87 docs.remove(&key).is_some()
88 }
89
90 pub fn get(&self, uri: &str) -> Option<Document> {
92 let key = Self::uri_key(uri);
93 let docs = self.documents.read().ok()?;
94 docs.get(&key).cloned()
95 }
96
97 pub fn get_text(&self, uri: &str) -> Option<String> {
99 self.get(uri).map(|doc| doc.text)
100 }
101
102 pub fn all_documents(&self) -> Vec<Document> {
104 let Ok(docs) = self.documents.read() else {
105 return Vec::new();
106 };
107 docs.values().cloned().collect()
108 }
109
110 pub fn is_open(&self, uri: &str) -> bool {
112 let key = Self::uri_key(uri);
113 let Ok(docs) = self.documents.read() else {
114 return false;
115 };
116 docs.contains_key(&key)
117 }
118
119 pub fn count(&self) -> usize {
121 let Ok(docs) = self.documents.read() else {
122 return 0;
123 };
124 docs.len()
125 }
126
127 #[cfg(feature = "memory-profiling")]
133 pub fn total_text_bytes(&self) -> usize {
134 let Ok(docs) = self.documents.read() else {
135 return 0;
136 };
137 docs.values().map(|d| d.text.len()).sum()
138 }
139}
140
141impl Default for DocumentStore {
142 fn default() -> Self {
143 Self::new()
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use perl_tdd_support::must_some;
151
152 #[test]
153 fn test_document_lifecycle() {
154 let store = DocumentStore::new();
155 let uri = "file:///test.pl".to_string();
156
157 store.open(uri.clone(), 1, "print 'hello';".to_string());
159 assert!(store.is_open(&uri));
160 assert_eq!(store.count(), 1);
161
162 let doc = must_some(store.get(&uri));
164 assert_eq!(doc.version, 1);
165 assert_eq!(doc.text, "print 'hello';");
166
167 assert!(store.update(&uri, 2, "print 'world';".to_string()));
169 let doc = must_some(store.get(&uri));
170 assert_eq!(doc.version, 2);
171 assert_eq!(doc.text, "print 'world';");
172
173 assert!(store.close(&uri));
175 assert!(!store.is_open(&uri));
176 assert_eq!(store.count(), 0);
177 }
178
179 #[test]
180 fn test_uri_drive_letter_normalization() {
181 let uri1 = "file:///C:/test.pl";
182 let uri2 = "file:///c:/test.pl";
183 assert_eq!(DocumentStore::uri_key(uri1), DocumentStore::uri_key(uri2));
184 }
185
186 #[test]
187 fn test_drive_letter_lookup() {
188 let store = DocumentStore::new();
189 let uri_upper = "file:///C:/test.pl".to_string();
190 let uri_lower = "file:///c:/test.pl".to_string();
191
192 store.open(uri_upper.clone(), 1, "# test".to_string());
193 assert!(store.is_open(&uri_lower));
194 assert_eq!(store.get_text(&uri_lower), Some("# test".to_string()));
195 assert!(store.close(&uri_lower));
196 assert_eq!(store.count(), 0);
197 }
198
199 #[test]
200 fn test_multiple_documents() {
201 let store = DocumentStore::new();
202
203 let uri1 = "file:///a.pl".to_string();
204 let uri2 = "file:///b.pl".to_string();
205
206 store.open(uri1.clone(), 1, "# file a".to_string());
207 store.open(uri2.clone(), 1, "# file b".to_string());
208
209 assert_eq!(store.count(), 2);
210 assert_eq!(store.get_text(&uri1), Some("# file a".to_string()));
211 assert_eq!(store.get_text(&uri2), Some("# file b".to_string()));
212
213 let all = store.all_documents();
214 assert_eq!(all.len(), 2);
215 }
216
217 #[test]
218 fn test_uri_with_spaces() {
219 let store = DocumentStore::new();
220 let uri = "file:///path%20with%20spaces/test.pl".to_string();
221
222 store.open(uri.clone(), 1, "# test".to_string());
223 assert!(store.is_open(&uri));
224
225 let doc = must_some(store.get(&uri));
226 assert_eq!(doc.text, "# test");
227 }
228}