auto_lsp_default/db/
mod.rs

1/*
2This file is part of auto-lsp.
3Copyright (C) 2025 CLAUZEL Adrien
4
5auto-lsp is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program.  If not, see <http://www.gnu.org/licenses/>
17*/
18
19/// All structs and traits present in this module serve a minimal database implementation with basic file management.
20///
21/// Depending on your needs, you might want to create your own database and inputs.
22pub mod lexer;
23pub mod tracked;
24
25use auto_lsp_core::document::Document;
26use auto_lsp_core::errors::{DataBaseError, TreeSitterError};
27use auto_lsp_core::parsers::Parsers;
28use dashmap::{DashMap, Entry};
29use lsp_types::Url;
30use salsa::Setter;
31use salsa::{Database, Storage};
32use std::{hash::Hash, sync::Arc};
33use texter::core::text::Text;
34
35/// Input that represents a file in the database.
36///
37/// An [`lsp_types::Url`] is used as the unique identifier of the file.
38#[salsa::input]
39pub struct File {
40    #[id]
41    pub url: Url,
42    pub parsers: &'static Parsers,
43    #[return_ref]
44    pub document: Arc<Document>,
45}
46
47/// Base database that stores files.
48///
49/// Files are stored in a [`DashMap`] for concurrent access.
50///
51/// This also allows to lazily compute the AST of a file when it is first queried.
52///
53/// Logs are also stored when running in debug mode.
54#[salsa::db]
55#[derive(Default, Clone)]
56pub struct BaseDb {
57    storage: Storage<Self>,
58    pub(crate) files: DashMap<Url, File>,
59}
60
61impl BaseDb {
62    /// Create a new database with a logger.
63    pub fn with_logger(
64        event_callback: Option<Box<dyn Fn(salsa::Event) + Send + Sync + 'static>>,
65    ) -> Self {
66        Self {
67            storage: Storage::new(event_callback),
68            files: DashMap::default(),
69        }
70    }
71}
72
73/// Base trait for a database that stores files.
74///
75/// This trait is implemented for [`BaseDb`] and can be implemented for your own database.
76#[salsa::db]
77pub trait BaseDatabase: Database {
78    fn get_files(&self) -> &DashMap<Url, File>;
79
80    fn get_file(&self, url: &Url) -> Option<File> {
81        self.get_files().get(url).map(|file| *file)
82    }
83}
84
85/// Implementation of [`salsa::Database`] for [`BaseDb`].
86#[salsa::db]
87impl salsa::Database for BaseDb {}
88impl std::panic::RefUnwindSafe for BaseDb {}
89
90/// Implementation of [`BaseDatabase`] for [`BaseDb`].
91#[salsa::db]
92impl BaseDatabase for BaseDb {
93    fn get_files(&self) -> &DashMap<Url, File> {
94        &self.files
95    }
96}
97
98/// Trait for managing files in the database.
99///
100/// This trait is implemented for any database that implements [`BaseDatabase`].
101pub trait FileManager: BaseDatabase + salsa::Database {
102    fn add_file_from_texter(
103        &mut self,
104        parsers: &'static Parsers,
105        url: &Url,
106        texter: Text,
107    ) -> Result<(), DataBaseError> {
108        let tree = parsers
109            .parser
110            .write()
111            .parse(texter.text.as_bytes(), None)
112            .ok_or_else(|| DataBaseError::from((url, TreeSitterError::TreeSitterParser)))?;
113
114        let document = Document { texter, tree };
115        let file = File::new(self, url.clone(), parsers, Arc::new(document));
116
117        match self.get_files().entry(url.clone()) {
118            Entry::Occupied(_) => Err(DataBaseError::FileAlreadyExists { uri: url.clone() }),
119            Entry::Vacant(entry) => {
120                entry.insert(file);
121                Ok(())
122            }
123        }
124    }
125
126    fn update(
127        &mut self,
128        url: &Url,
129        changes: &[lsp_types::TextDocumentContentChangeEvent],
130    ) -> Result<(), DataBaseError> {
131        let file = *self
132            .get_files()
133            .get_mut(url)
134            .ok_or_else(|| DataBaseError::FileNotFound { uri: url.clone() })?;
135
136        let mut doc = (*file.document(self)).clone();
137
138        doc.update(&mut file.parsers(self).parser.write(), changes)
139            .map_err(|e| DataBaseError::from((url, e)))?;
140
141        file.set_document(self).to(Arc::new(doc));
142        Ok(())
143    }
144
145    fn remove_file(&mut self, url: &Url) -> Result<(), DataBaseError> {
146        match self.get_files().remove(url) {
147            None => Err(DataBaseError::FileNotFound { uri: url.clone() }),
148            Some(_) => Ok(()),
149        }
150    }
151}
152
153impl<T> FileManager for T where T: BaseDatabase + salsa::Database {}