2121//! ```
2222
2323use std:: path:: Path ;
24- use std:: path:: PathBuf ;
2524use std:: sync:: Arc ;
26- #[ cfg( test) ]
27- use std:: sync:: Mutex ;
28-
29- use dashmap:: DashMap ;
30- use salsa:: Setter ;
3125
3226use crate :: FileKind ;
3327use crate :: FileSystem ;
@@ -45,156 +39,6 @@ pub trait Db: salsa::Database {
4539 fn read_file_content ( & self , path : & Path ) -> std:: io:: Result < String > ;
4640}
4741
48- /// Temporary concrete database for workspace.
49- ///
50- /// This will be moved to the server crate in the refactoring.
51- /// For now, it's kept here to avoid breaking existing code.
52- #[ salsa:: db]
53- #[ derive( Clone ) ]
54- pub struct Database {
55- storage : salsa:: Storage < Self > ,
56-
57- /// File system for reading file content (checks buffers first, then disk).
58- fs : Arc < dyn FileSystem > ,
59-
60- /// Maps paths to [`SourceFile`] entities for O(1) lookup.
61- files : Arc < DashMap < PathBuf , SourceFile > > ,
62-
63- // The logs are only used for testing and demonstrating reuse:
64- #[ cfg( test) ]
65- #[ allow( dead_code) ]
66- logs : Arc < Mutex < Option < Vec < String > > > > ,
67- }
68-
69- #[ cfg( test) ]
70- impl Default for Database {
71- fn default ( ) -> Self {
72- use crate :: fs:: InMemoryFileSystem ;
73-
74- let logs = <Arc < Mutex < Option < Vec < String > > > > >:: default ( ) ;
75- Self {
76- storage : salsa:: Storage :: new ( Some ( Box :: new ( {
77- let logs = logs. clone ( ) ;
78- move |event| {
79- eprintln ! ( "Event: {event:?}" ) ;
80- // Log interesting events, if logging is enabled
81- if let Some ( logs) = & mut * logs. lock ( ) . unwrap ( ) {
82- // only log interesting events
83- if let salsa:: EventKind :: WillExecute { .. } = event. kind {
84- logs. push ( format ! ( "Event: {event:?}" ) ) ;
85- }
86- }
87- }
88- } ) ) ) ,
89- fs : Arc :: new ( InMemoryFileSystem :: new ( ) ) ,
90- files : Arc :: new ( DashMap :: new ( ) ) ,
91- logs,
92- }
93- }
94- }
95-
96- impl Database {
97- pub fn new ( file_system : Arc < dyn FileSystem > , files : Arc < DashMap < PathBuf , SourceFile > > ) -> Self {
98- Self {
99- storage : salsa:: Storage :: new ( None ) ,
100- fs : file_system,
101- files,
102- #[ cfg( test) ]
103- logs : Arc :: new ( Mutex :: new ( None ) ) ,
104- }
105- }
106-
107- /// Read file content through the file system.
108- pub fn read_file_content ( & self , path : & Path ) -> std:: io:: Result < String > {
109- self . fs . read_to_string ( path)
110- }
111-
112- /// Get an existing [`SourceFile`] for the given path without creating it.
113- ///
114- /// Returns `Some(SourceFile)` if the file is already tracked, `None` otherwise.
115- /// This method uses an immutable reference and doesn't modify the database.
116- pub fn get_file ( & self , path : & Path ) -> Option < SourceFile > {
117- self . files . get ( path) . map ( |file_ref| * file_ref)
118- }
119-
120- /// Get or create a [`SourceFile`] for the given path.
121- ///
122- /// Files are created with an initial revision of 0 and tracked in the [`Database`]'s
123- /// `DashMap`. The `Arc` ensures cheap cloning while maintaining thread safety.
124- ///
125- /// ## Thread Safety
126- ///
127- /// This method is inherently thread-safe despite the check-then-create pattern because
128- /// it requires `&mut self`, ensuring exclusive access to the Database. Only one thread
129- /// can call this method at a time due to Rust's ownership rules.
130- pub fn get_or_create_file ( & mut self , path : & PathBuf ) -> SourceFile {
131- if let Some ( file_ref) = self . files . get ( path) {
132- // Copy the value (SourceFile is Copy)
133- // The guard drops automatically, no need for explicit drop
134- return * file_ref;
135- }
136-
137- // File doesn't exist, so we need to create it
138- let kind = FileKind :: from_path ( path) ;
139- let file = SourceFile :: new ( self , kind, Arc :: from ( path. to_string_lossy ( ) . as_ref ( ) ) , 0 ) ;
140-
141- self . files . insert ( path. clone ( ) , file) ;
142- file
143- }
144-
145- /// Check if a file is being tracked without creating it.
146- ///
147- /// This is primarily used for testing to verify that files have been
148- /// created without affecting the database state.
149- pub fn has_file ( & self , path : & Path ) -> bool {
150- self . files . contains_key ( path)
151- }
152-
153- /// Touch a file to mark it as modified, triggering re-evaluation of dependent queries.
154- ///
155- /// Similar to Unix `touch`, this updates the file's revision number to signal
156- /// that cached query results depending on this file should be invalidated.
157- ///
158- /// This is typically called when:
159- /// - A file is opened in the editor (if it was previously cached from disk)
160- /// - A file's content is modified
161- /// - A file's buffer is closed (reverting to disk content)
162- pub fn touch_file ( & mut self , path : & Path ) {
163- // Get the file if it exists
164- let Some ( file_ref) = self . files . get ( path) else {
165- tracing:: debug!( "File {} not tracked, skipping touch" , path. display( ) ) ;
166- return ;
167- } ;
168- let file = * file_ref;
169- drop ( file_ref) ; // Explicitly drop to release the lock
170-
171- let current_rev = file. revision ( self ) ;
172- let new_rev = current_rev + 1 ;
173- file. set_revision ( self ) . to ( new_rev) ;
174-
175- tracing:: debug!(
176- "Touched {}: revision {} -> {}" ,
177- path. display( ) ,
178- current_rev,
179- new_rev
180- ) ;
181- }
182- }
183-
184- #[ salsa:: db]
185- impl salsa:: Database for Database { }
186-
187- #[ salsa:: db]
188- impl Db for Database {
189- fn fs ( & self ) -> Arc < dyn FileSystem > {
190- self . fs . clone ( )
191- }
192-
193- fn read_file_content ( & self , path : & Path ) -> std:: io:: Result < String > {
194- self . fs . read_to_string ( path)
195- }
196- }
197-
19842/// Represents a single file without storing its content.
19943///
20044/// [`SourceFile`] is a Salsa input entity that tracks a file's path, revision, and
0 commit comments