flams_lsp/
documents.rs

1use std::{path::Path, sync::atomic::AtomicBool};
2
3use async_lsp::lsp_types::{Position, Range};
4use flams_ontology::uris::{ArchiveURI, DocumentURI, URIRefTrait};
5use flams_stex::quickparse::stex::{STeXParseData, STeXParseDataI};
6use flams_system::backend::{AnyBackend, Backend, GlobalBackend};
7use flams_utils::PathExt;
8
9use crate::{
10    state::{LSPState, UrlOrFile},
11    LSPStore,
12};
13
14#[derive(Debug, PartialEq, Eq)]
15struct DocumentData {
16    path: Option<std::sync::Arc<Path>>,
17    archive: Option<ArchiveURI>,
18    rel_path: Option<Box<str>>,
19    doc_uri: Option<DocumentURI>,
20}
21
22#[derive(Clone, Debug)]
23pub struct LSPDocument {
24    up_to_date: triomphe::Arc<AtomicBool>,
25    text: triomphe::Arc<parking_lot::Mutex<LSPText>>,
26    pub annotations: STeXParseData,
27    data: triomphe::Arc<DocumentData>,
28}
29impl PartialEq for LSPDocument {
30    #[inline]
31    fn eq(&self, other: &Self) -> bool {
32        self.data == other.data
33    }
34}
35
36#[cfg(windows)]
37const PREFIX: &str = "\\source\\";
38#[cfg(not(windows))]
39const PREFIX: &str = "/source/";
40
41impl LSPDocument {
42    #[allow(clippy::cast_possible_truncation)]
43    #[must_use]
44    pub fn new(text: String, lsp_uri: UrlOrFile) -> Self {
45        let path = if let UrlOrFile::File(p) = lsp_uri {
46            Some(p)
47        } else {
48            None
49        }; //lsp_uri.to_file_path().ok().map(Into::into);
50        let default = || {
51            let path = path.as_ref()?.as_slash_str().into();
52            Some((ArchiveURI::no_archive(), Some(path)))
53        };
54        let ap = path
55            .as_ref()
56            .and_then(|path| {
57                GlobalBackend::get().archive_of(path, |a, rp| {
58                    let uri = a.uri().owned();
59                    let rp = rp.strip_prefix(PREFIX).map(|r| r.into());
60                    (uri, rp)
61                })
62            })
63            .or_else(default);
64        let (archive, rel_path) = ap.map_or((None, None), |(a, p)| (Some(a), p));
65        let r = LSPText {
66            text,
67            html_up_to_date: false,
68        };
69        let doc_uri = archive.as_ref().and_then(|a| {
70            rel_path.as_deref().and_then(|rp: &str| {
71                match DocumentURI::from_archive_relpath(a.clone(), rp) {
72                    Ok(u) => Some(u),
73                    Err(e) => {
74                        tracing::error!("Error in URI {rp} in {a}: {e}");
75                        None
76                    }
77                }
78            })
79        });
80        //tracing::info!("Document: {lsp_uri}\n - {doc_uri:?}\n - [{archive:?}]{{{rel_path:?}}}");
81        let data = DocumentData {
82            path,
83            archive,
84            rel_path,
85            doc_uri,
86        };
87        Self {
88            up_to_date: triomphe::Arc::new(AtomicBool::new(false)),
89            text: triomphe::Arc::new(parking_lot::Mutex::new(r)),
90            data: triomphe::Arc::new(data),
91            annotations: STeXParseData::default(),
92        }
93    }
94
95    #[inline]
96    #[must_use]
97    pub fn path(&self) -> Option<&Path> {
98        self.data.path.as_deref()
99    }
100
101    #[inline]
102    #[must_use]
103    pub fn archive(&self) -> Option<&ArchiveURI> {
104        self.data.archive.as_ref()
105    }
106
107    #[inline]
108    #[must_use]
109    pub fn relative_path(&self) -> Option<&str> {
110        self.data.rel_path.as_deref()
111    }
112
113    #[inline]
114    #[must_use]
115    pub fn document_uri(&self) -> Option<&DocumentURI> {
116        self.data.doc_uri.as_ref()
117    }
118
119    #[inline]
120    pub fn set_text(&self, s: String) -> bool {
121        let mut txt = self.text.lock();
122        if txt.text == s {
123            return false;
124        }
125        txt.text = s;
126        self.up_to_date
127            .store(false, std::sync::atomic::Ordering::SeqCst);
128        true
129    }
130
131    #[inline]
132    pub fn with_text<R>(&self, f: impl FnOnce(&str) -> R) -> R {
133        f(&self.text.lock().text)
134    }
135
136    #[inline]
137    pub fn html_up_to_date(&self) -> bool {
138        self.text.lock().html_up_to_date
139    }
140
141    pub fn set_html_up_to_date(&self) {
142        self.text.lock().html_up_to_date = true
143    }
144
145    #[inline]
146    pub fn delta(&self, text: String, range: Option<Range>) {
147        self.up_to_date
148            .store(false, std::sync::atomic::Ordering::SeqCst);
149        self.text.lock().delta(text, range);
150    }
151    #[inline]
152    #[must_use]
153    pub fn get_range(&self, range: Range) -> (usize, usize) {
154        self.text.lock().get_range(range)
155    }
156    #[inline]
157    #[must_use]
158    pub fn get_position(&self, pos: Position) -> usize {
159        self.text.lock().get_position(pos)
160    }
161
162    #[inline]
163    #[must_use]
164    pub fn has_annots(&self) -> bool {
165        self.data.doc_uri.is_some() && self.data.path.is_some()
166    }
167
168    #[allow(clippy::significant_drop_tightening)]
169    fn load_annotations_and<R>(
170        &self,
171        state: LSPState,
172        f: impl FnOnce(&STeXParseDataI) -> R,
173    ) -> Option<R> {
174        let lock = self.text.lock();
175        let uri = self.data.doc_uri.as_ref()?;
176        let path = self.data.path.as_ref()?;
177
178        let mut docs = state.documents.write();
179        let mut store = LSPStore::<true>::new(&mut *docs);
180        let data =
181    //let (data,t) = measure(||
182      flams_stex::quickparse::stex::quickparse(
183      uri,&lock.text, path,
184      &AnyBackend::Global(GlobalBackend::get()),
185      &mut store);
186        //);
187        data.replace(&self.annotations);
188        self.up_to_date
189            .store(true, std::sync::atomic::Ordering::SeqCst);
190        drop(store);
191        drop(docs);
192        //tracing::info!("quickparse took {t}");
193        drop(lock);
194        /*let path = path.clone();
195        let _ = tokio::task::spawn_blocking(move || {
196          state.relint_dependents(path);
197        });*/
198        let lock = self.annotations.lock();
199        Some(f(&lock))
200    }
201
202    pub fn is_up_to_date(&self) -> bool {
203        self.up_to_date.load(std::sync::atomic::Ordering::SeqCst)
204    }
205
206    #[inline]
207    #[must_use]
208    #[allow(clippy::significant_drop_tightening)]
209    pub async fn with_annots<R: Send + 'static>(
210        self,
211        state: LSPState,
212        f: impl FnOnce(&STeXParseDataI) -> R + Send + 'static,
213    ) -> Option<R> {
214        if !self.has_annots() {
215            return None;
216        }
217        if self.is_up_to_date() {
218            let lock = self.annotations.lock();
219            if lock.is_empty() {
220                return None;
221            }
222            return Some(f(&lock));
223        }
224        match tokio::task::spawn_blocking(move || self.load_annotations_and(state, f)).await {
225            Ok(r) => r,
226            Err(e) => {
227                tracing::error!("Error computing annots: {}", e);
228                None
229            }
230        }
231    }
232
233    #[must_use]
234    #[allow(clippy::significant_drop_tightening)]
235    pub async fn with_annots_block<R: Send + 'static>(
236        self,
237        state: LSPState,
238        f: impl FnOnce(&STeXParseDataI) -> R + Send + 'static,
239    ) -> Option<R> {
240        if !self.has_annots() {
241            return None;
242        }
243        if self.is_up_to_date() {
244            if self.annotations.lock().is_empty() {
245                return None;
246            }
247            let annot = self.annotations.clone();
248            return match tokio::task::spawn_blocking(move || f(&annot.lock())).await {
249                Ok(r) => Some(r),
250                Err(e) => {
251                    tracing::error!("Error computing annots: {}", e);
252                    None
253                }
254            };
255        }
256        match tokio::task::spawn_blocking(move || self.load_annotations_and(state, f)).await {
257            Ok(r) => r,
258            Err(e) => {
259                tracing::error!("Error computing annots: {}", e);
260                None
261            }
262        }
263    }
264
265    #[inline]
266    pub fn compute_annots(&self, state: LSPState) {
267        self.load_annotations_and(state, |_| ());
268    }
269}
270
271#[derive(Debug)]
272struct LSPText {
273    text: String,
274    html_up_to_date: bool,
275}
276
277impl LSPText {
278    fn get_position(
279        &self,
280        Position {
281            mut line,
282            character,
283        }: Position,
284    ) -> usize {
285        let mut rest = self.text.as_str();
286        let mut off = 0;
287        while line > 0 {
288            if let Some(i) = rest.find(['\n', '\r']) {
289                off += i + 1;
290                if rest.as_bytes()[i] == b'\r' && rest.as_bytes().get(i + 1) == Some(&b'\n') {
291                    off += 1;
292                    rest = &rest[i + 2..];
293                } else {
294                    rest = &rest[i + 1..];
295                }
296                line -= 1;
297            } else {
298                off = self.text.len();
299                rest = "";
300                break;
301            }
302        }
303        let next = rest
304            .chars()
305            .take(character as usize)
306            .map(char::len_utf8)
307            .sum::<usize>();
308        off += next;
309        off
310    }
311
312    fn get_range(&self, range: Range) -> (usize, usize) {
313        let Range {
314            start:
315                Position {
316                    line: mut start_line,
317                    character: startc,
318                },
319            end:
320                Position {
321                    line: mut end_line,
322                    character: mut endc,
323                },
324        } = range;
325        if start_line == end_line {
326            endc -= startc;
327        }
328        end_line -= start_line;
329
330        let mut start = 0;
331        let mut rest = self.text.as_str();
332        while start_line > 0 {
333            if let Some(i) = rest.find(['\n', '\r']) {
334                start += i + 1;
335                if rest.as_bytes()[i] == b'\r' && rest.as_bytes().get(i + 1) == Some(&b'\n') {
336                    start += 1;
337                    rest = &rest[i + 2..];
338                } else {
339                    rest = &rest[i + 1..];
340                }
341                start_line -= 1;
342            } else {
343                start = self.text.len();
344                rest = "";
345                end_line = 0;
346                break;
347            }
348        }
349        let next = rest
350            .chars()
351            .take(startc as usize)
352            .map(char::len_utf8)
353            .sum::<usize>();
354        start += next;
355        rest = &rest[next..];
356
357        let mut end = start;
358        while end_line > 0 {
359            if let Some(i) = rest.find(['\n', '\r']) {
360                end += i + 1;
361                if rest.as_bytes()[i] == b'\r' && rest.as_bytes().get(i + 1) == Some(&b'\n') {
362                    end += 1;
363                    rest = &rest[i + 2..];
364                } else {
365                    rest = &rest[i + 1..];
366                }
367                end_line -= 1;
368            } else {
369                end = self.text.len();
370                rest = "";
371                break;
372            }
373        }
374        end += rest
375            .chars()
376            .take(endc as usize)
377            .map(char::len_utf8)
378            .sum::<usize>();
379        (start, end)
380    }
381
382    #[allow(clippy::cast_possible_truncation)]
383    fn delta(&mut self, text: String, range: Option<Range>) {
384        let Some(range) = range else {
385            self.text = text;
386            return;
387        };
388        let (start, end) = self.get_range(range);
389        self.text.replace_range(start..end, &text);
390        self.html_up_to_date = false;
391    }
392}