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
128impl Default for DocumentStore {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use perl_tdd_support::must_some;
138
139 #[test]
140 fn test_document_lifecycle() {
141 let store = DocumentStore::new();
142 let uri = "file:///test.pl".to_string();
143
144 store.open(uri.clone(), 1, "print 'hello';".to_string());
146 assert!(store.is_open(&uri));
147 assert_eq!(store.count(), 1);
148
149 let doc = must_some(store.get(&uri));
151 assert_eq!(doc.version, 1);
152 assert_eq!(doc.text, "print 'hello';");
153
154 assert!(store.update(&uri, 2, "print 'world';".to_string()));
156 let doc = must_some(store.get(&uri));
157 assert_eq!(doc.version, 2);
158 assert_eq!(doc.text, "print 'world';");
159
160 assert!(store.close(&uri));
162 assert!(!store.is_open(&uri));
163 assert_eq!(store.count(), 0);
164 }
165
166 #[test]
167 fn test_uri_drive_letter_normalization() {
168 let uri1 = "file:///C:/test.pl";
169 let uri2 = "file:///c:/test.pl";
170 assert_eq!(DocumentStore::uri_key(uri1), DocumentStore::uri_key(uri2));
171 }
172
173 #[test]
174 fn test_drive_letter_lookup() {
175 let store = DocumentStore::new();
176 let uri_upper = "file:///C:/test.pl".to_string();
177 let uri_lower = "file:///c:/test.pl".to_string();
178
179 store.open(uri_upper.clone(), 1, "# test".to_string());
180 assert!(store.is_open(&uri_lower));
181 assert_eq!(store.get_text(&uri_lower), Some("# test".to_string()));
182 assert!(store.close(&uri_lower));
183 assert_eq!(store.count(), 0);
184 }
185
186 #[test]
187 fn test_multiple_documents() {
188 let store = DocumentStore::new();
189
190 let uri1 = "file:///a.pl".to_string();
191 let uri2 = "file:///b.pl".to_string();
192
193 store.open(uri1.clone(), 1, "# file a".to_string());
194 store.open(uri2.clone(), 1, "# file b".to_string());
195
196 assert_eq!(store.count(), 2);
197 assert_eq!(store.get_text(&uri1), Some("# file a".to_string()));
198 assert_eq!(store.get_text(&uri2), Some("# file b".to_string()));
199
200 let all = store.all_documents();
201 assert_eq!(all.len(), 2);
202 }
203
204 #[test]
205 fn test_uri_with_spaces() {
206 let store = DocumentStore::new();
207 let uri = "file:///path%20with%20spaces/test.pl".to_string();
208
209 store.open(uri.clone(), 1, "# test".to_string());
210 assert!(store.is_open(&uri));
211
212 let doc = must_some(store.get(&uri));
213 assert_eq!(doc.text, "# test");
214 }
215}