lsp_max/primitives/
document_store.rs1use lsp_types_max::{TextDocumentContentChangeEvent, Uri as Url};
2use parking_lot::RwLock;
3use rustc_hash::FxHashMap;
4use std::sync::Arc;
5
6#[derive(Debug)]
8pub struct VersionedDocument {
9 pub content: String,
11 pub version: i32,
13 pub activations: u64,
18}
19
20#[derive(Clone, Debug, Default)]
25pub struct DocumentStore {
26 inner: Arc<RwLock<FxHashMap<Url, VersionedDocument>>>,
27}
28
29impl DocumentStore {
30 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn open(&self, uri: Url, content: String, version: i32) {
37 self.inner.write().insert(
38 uri,
39 VersionedDocument {
40 content,
41 version,
42 activations: 0,
43 },
44 );
45 }
46
47 pub fn update(&self, uri: &Url, changes: Vec<TextDocumentContentChangeEvent>, version: i32) {
49 let mut map = self.inner.write();
50 if let Some(doc) = map.get_mut(uri) {
51 for change in changes {
52 if change.range.is_none() {
53 doc.content = change.text;
54 } else {
55 doc.content = apply_incremental(&doc.content, &change);
56 }
57 }
58 doc.version = version;
59 doc.activations = doc.activations.saturating_add(1);
60 }
61 }
62
63 pub fn close(&self, uri: &Url) {
65 self.inner.write().remove(uri);
66 }
67
68 pub fn get_content(&self, uri: &Url) -> Option<String> {
70 self.inner.read().get(uri).map(|d| d.content.clone())
71 }
72
73 pub fn version(&self, uri: &Url) -> Option<i32> {
75 self.inner.read().get(uri).map(|d| d.version)
76 }
77
78 pub fn with<F, T>(&self, uri: &Url, f: F) -> Option<T>
80 where
81 F: FnOnce(&VersionedDocument) -> T,
82 {
83 self.inner.read().get(uri).map(f)
84 }
85
86 pub fn is_open(&self, uri: &Url) -> bool {
88 self.inner.read().contains_key(uri)
89 }
90
91 pub fn content_hash(&self, uri: &Url) -> Option<u64> {
96 self.inner
97 .read()
98 .get(uri)
99 .map(|d| fnv1a_64(d.content.as_bytes()))
100 }
101
102 pub fn activation_count(&self, uri: &Url) -> u64 {
107 self.inner
108 .read()
109 .get(uri)
110 .map(|d| d.activations)
111 .unwrap_or(0)
112 }
113}
114
115#[inline]
121pub(crate) fn fnv1a_64(bytes: &[u8]) -> u64 {
122 const OFFSET: u64 = 0xcbf29ce484222325;
123 const PRIME: u64 = 0x100000001b3;
124 bytes
125 .iter()
126 .fold(OFFSET, |h, &b| (h ^ b as u64).wrapping_mul(PRIME))
127}
128
129fn apply_incremental(content: &str, change: &TextDocumentContentChangeEvent) -> String {
135 let Some(range) = change.range else {
136 return change.text.clone();
137 };
138
139 let start = lsp_pos_to_byte(
140 content,
141 range.start.line as usize,
142 range.start.character as usize,
143 );
144 let end = lsp_pos_to_byte(
145 content,
146 range.end.line as usize,
147 range.end.character as usize,
148 );
149
150 let mut out = String::with_capacity(content.len() + change.text.len());
151 out.push_str(&content[..start]);
152 out.push_str(&change.text);
153 out.push_str(&content[end..]);
154 out
155}
156
157fn lsp_pos_to_byte(content: &str, line: usize, character: usize) -> usize {
159 let mut byte = 0;
161 let mut current_line = 0;
162 for ch in content.chars() {
163 if current_line == line {
164 break;
165 }
166 byte += ch.len_utf8();
167 if ch == '\n' {
168 current_line += 1;
169 }
170 }
171 let mut utf16 = 0;
173 for ch in content[byte..].chars() {
174 if utf16 >= character {
175 break;
176 }
177 byte += ch.len_utf8();
178 utf16 += ch.len_utf16();
179 }
180 byte.min(content.len())
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use lsp_types_max::Uri as Url;
187
188 fn test_url() -> Url {
189 "file:///tmp/test.rs".parse().unwrap()
190 }
191
192 fn whole_change(text: &str) -> TextDocumentContentChangeEvent {
193 TextDocumentContentChangeEvent {
194 range: None,
195 range_length: None,
196 text: text.to_string(),
197 }
198 }
199
200 #[test]
201 fn fnv1a_64_empty_returns_offset_basis() {
202 assert_eq!(fnv1a_64(b""), 0xcbf29ce484222325u64);
203 }
204
205 #[test]
206 fn fnv1a_64_deterministic() {
207 assert_eq!(fnv1a_64(b"hello"), fnv1a_64(b"hello"));
208 }
209
210 #[test]
211 fn fnv1a_64_distinct_inputs_produce_different_hashes() {
212 assert_ne!(fnv1a_64(b"hello"), fnv1a_64(b"world"));
213 }
214
215 #[test]
216 fn open_and_get_content_round_trips() {
217 let store = DocumentStore::new();
218 let uri = test_url();
219 store.open(uri.clone(), "hello".to_string(), 1);
220 assert_eq!(store.get_content(&uri).as_deref(), Some("hello"));
221 }
222
223 #[test]
224 fn version_tracks_open_version() {
225 let store = DocumentStore::new();
226 let uri = test_url();
227 store.open(uri.clone(), String::new(), 7);
228 assert_eq!(store.version(&uri), Some(7));
229 }
230
231 #[test]
232 fn update_whole_document_replaces_content_and_version() {
233 let store = DocumentStore::new();
234 let uri = test_url();
235 store.open(uri.clone(), "old".to_string(), 1);
236 store.update(&uri, vec![whole_change("new")], 2);
237 assert_eq!(store.get_content(&uri).as_deref(), Some("new"));
238 assert_eq!(store.version(&uri), Some(2));
239 }
240
241 #[test]
242 fn close_removes_document() {
243 let store = DocumentStore::new();
244 let uri = test_url();
245 store.open(uri.clone(), "x".to_string(), 1);
246 store.close(&uri);
247 assert_eq!(store.get_content(&uri), None);
248 assert!(!store.is_open(&uri));
249 }
250
251 #[test]
252 fn is_open_false_for_never_opened_uri() {
253 let store = DocumentStore::new();
254 assert!(!store.is_open(&test_url()));
255 }
256
257 #[test]
258 fn activation_count_increments_on_each_update() {
259 let store = DocumentStore::new();
260 let uri = test_url();
261 store.open(uri.clone(), "v1".to_string(), 1);
262 assert_eq!(store.activation_count(&uri), 0);
263 store.update(&uri, vec![whole_change("v2")], 2);
264 assert_eq!(store.activation_count(&uri), 1);
265 store.update(&uri, vec![whole_change("v3")], 3);
266 assert_eq!(store.activation_count(&uri), 2);
267 }
268
269 #[test]
270 fn content_hash_changes_when_content_changes() {
271 let store = DocumentStore::new();
272 let uri = test_url();
273 store.open(uri.clone(), "a".to_string(), 1);
274 let h1 = store.content_hash(&uri).unwrap();
275 store.update(&uri, vec![whole_change("b")], 2);
276 let h2 = store.content_hash(&uri).unwrap();
277 assert_ne!(h1, h2);
278 }
279
280 #[test]
281 fn with_applies_function_without_cloning_content() {
282 let store = DocumentStore::new();
283 let uri = test_url();
284 store.open(uri.clone(), "probe".to_string(), 42);
285 let version = store.with(&uri, |doc| doc.version).unwrap();
286 assert_eq!(version, 42);
287 }
288
289 #[test]
290 fn clone_shares_inner_store() {
291 let store = DocumentStore::new();
292 let clone = store.clone();
293 let uri = test_url();
294 store.open(uri.clone(), "shared".to_string(), 1);
295 assert_eq!(clone.get_content(&uri).as_deref(), Some("shared"));
296 }
297}