@@ -14,16 +14,11 @@ use std::sync::Arc;
1414use crate :: buffers:: Buffers ;
1515use crate :: paths;
1616
17- /// Trait for file system operations
1817pub trait FileSystem : Send + Sync {
19- /// Read the entire contents of a file
2018 fn read_to_string ( & self , path : & Path ) -> io:: Result < String > ;
21-
22- /// Check if a path exists
2319 fn exists ( & self , path : & Path ) -> bool ;
2420}
2521
26- /// In-memory file system for testing
2722#[ cfg( test) ]
2823pub struct InMemoryFileSystem {
2924 files : HashMap < PathBuf , String > ,
@@ -120,125 +115,216 @@ impl FileSystem for WorkspaceFileSystem {
120115
121116#[ cfg( test) ]
122117mod tests {
123- use url:: Url ;
124-
125118 use super :: * ;
126- use crate :: buffers:: Buffers ;
127- use crate :: document:: TextDocument ;
128- use crate :: language:: LanguageId ;
129-
130- #[ test]
131- fn test_lsp_filesystem_overlay_precedence ( ) {
132- let mut memory_fs = InMemoryFileSystem :: new ( ) ;
133- memory_fs. add_file (
134- std:: path:: PathBuf :: from ( "/test/file.py" ) ,
135- "original content" . to_string ( ) ,
136- ) ;
137-
138- let buffers = Buffers :: new ( ) ;
139- let lsp_fs = WorkspaceFileSystem :: new ( buffers. clone ( ) , Arc :: new ( memory_fs) ) ;
140-
141- // Before adding buffer, should read from fallback
142- let path = std:: path:: Path :: new ( "/test/file.py" ) ;
143- assert_eq ! ( lsp_fs. read_to_string( path) . unwrap( ) , "original content" ) ;
144-
145- // Add buffer - this simulates having an open document with changes
146- let url = Url :: from_file_path ( "/test/file.py" ) . unwrap ( ) ;
147- let document = TextDocument :: new ( "overlay content" . to_string ( ) , 1 , LanguageId :: Python ) ;
148- buffers. open ( url, document) ;
149-
150- // Now should read from buffer
151- assert_eq ! ( lsp_fs. read_to_string( path) . unwrap( ) , "overlay content" ) ;
152- }
153119
154- #[ test]
155- fn test_lsp_filesystem_fallback_when_no_overlay ( ) {
156- let mut memory_fs = InMemoryFileSystem :: new ( ) ;
157- memory_fs. add_file (
158- std:: path:: PathBuf :: from ( "/test/file.py" ) ,
159- "disk content" . to_string ( ) ,
160- ) ;
120+ mod in_memory {
121+ use super :: * ;
161122
162- let buffers = Buffers :: new ( ) ;
163- let lsp_fs = WorkspaceFileSystem :: new ( buffers, Arc :: new ( memory_fs) ) ;
123+ #[ test]
124+ fn test_read_existing_file ( ) {
125+ let mut fs = InMemoryFileSystem :: new ( ) ;
126+ fs. add_file ( "/test.py" . into ( ) , "file content" . to_string ( ) ) ;
164127
165- // Should fall back to disk when no buffer exists
166- let path = std:: path:: Path :: new ( "/test/file.py" ) ;
167- assert_eq ! ( lsp_fs. read_to_string( path) . unwrap( ) , "disk content" ) ;
168- }
128+ assert_eq ! (
129+ fs. read_to_string( Path :: new( "/test.py" ) ) . unwrap( ) ,
130+ "file content"
131+ ) ;
132+ }
169133
170- #[ test]
171- fn test_lsp_filesystem_other_operations_delegate ( ) {
172- let mut memory_fs = InMemoryFileSystem :: new ( ) ;
173- memory_fs. add_file (
174- std:: path:: PathBuf :: from ( "/test/file.py" ) ,
175- "content" . to_string ( ) ,
176- ) ;
134+ #[ test]
135+ fn test_read_nonexistent_file ( ) {
136+ let fs = InMemoryFileSystem :: new ( ) ;
177137
178- let buffers = Buffers :: new ( ) ;
179- let lsp_fs = WorkspaceFileSystem :: new ( buffers, Arc :: new ( memory_fs) ) ;
138+ let result = fs. read_to_string ( Path :: new ( "/missing.py" ) ) ;
139+ assert ! ( result. is_err( ) ) ;
140+ assert_eq ! ( result. unwrap_err( ) . kind( ) , io:: ErrorKind :: NotFound ) ;
141+ }
180142
181- let path = std:: path:: Path :: new ( "/test/file.py" ) ;
143+ #[ test]
144+ fn test_exists_returns_true_for_existing ( ) {
145+ let mut fs = InMemoryFileSystem :: new ( ) ;
146+ fs. add_file ( "/exists.py" . into ( ) , "content" . to_string ( ) ) ;
182147
183- // This should delegate to the fallback filesystem
184- assert ! ( lsp_fs. exists( path) ) ;
148+ assert ! ( fs. exists( Path :: new( "/exists.py" ) ) ) ;
149+ }
150+
151+ #[ test]
152+ fn test_exists_returns_false_for_nonexistent ( ) {
153+ let fs = InMemoryFileSystem :: new ( ) ;
154+
155+ assert ! ( !fs. exists( Path :: new( "/missing.py" ) ) ) ;
156+ }
185157 }
186158
187- #[ test]
188- fn test_overlay_consistency ( ) {
189- // Create an empty filesystem (no files on disk)
190- let memory_fs = InMemoryFileSystem :: new ( ) ;
191- let buffers = Buffers :: new ( ) ;
192- let lsp_fs = WorkspaceFileSystem :: new ( buffers. clone ( ) , Arc :: new ( memory_fs) ) ;
159+ mod workspace {
160+ use url:: Url ;
193161
194- let path = std:: path:: Path :: new ( "/test/overlay_only.py" ) ;
162+ use crate :: buffers:: Buffers ;
163+ use crate :: document:: TextDocument ;
164+ use crate :: language:: LanguageId ;
195165
196- // Before adding to overlay, file doesn't exist
197- assert ! ( !lsp_fs. exists( path) ) ;
166+ use super :: * ;
198167
199- // Add file to overlay only (not on disk)
200- let url = Url :: from_file_path ( "/test/overlay_only.py" ) . unwrap ( ) ;
201- let document = TextDocument :: new ( "overlay content" . to_string ( ) , 1 , LanguageId :: Python ) ;
202- buffers. open ( url, document) ;
168+ #[ test]
169+ fn test_reads_from_buffer_when_present ( ) {
170+ let disk = Arc :: new ( InMemoryFileSystem :: new ( ) ) ;
171+ let buffers = Buffers :: new ( ) ;
172+ let fs = WorkspaceFileSystem :: new ( buffers. clone ( ) , disk) ;
203173
204- // Now file should exist
205- assert ! ( lsp_fs. exists( path) , "Overlay file should exist" ) ;
174+ // Add file to buffer
175+ let url = Url :: from_file_path ( "/test.py" ) . unwrap ( ) ;
176+ let doc = TextDocument :: new ( "buffer content" . to_string ( ) , 1 , LanguageId :: Python ) ;
177+ buffers. open ( url, doc) ;
206178
207- // And we should be able to read its content
208- assert_eq ! (
209- lsp_fs. read_to_string( path) . unwrap( ) ,
210- "overlay content" ,
211- "Should read overlay content"
212- ) ;
213- }
179+ assert_eq ! (
180+ fs. read_to_string( Path :: new( "/test.py" ) ) . unwrap( ) ,
181+ "buffer content"
182+ ) ;
183+ }
184+
185+ #[ test]
186+ fn test_reads_from_disk_when_no_buffer ( ) {
187+ let mut disk_fs = InMemoryFileSystem :: new ( ) ;
188+ disk_fs. add_file ( "/test.py" . into ( ) , "disk content" . to_string ( ) ) ;
189+
190+ let buffers = Buffers :: new ( ) ;
191+ let fs = WorkspaceFileSystem :: new ( buffers, Arc :: new ( disk_fs) ) ;
192+
193+ assert_eq ! (
194+ fs. read_to_string( Path :: new( "/test.py" ) ) . unwrap( ) ,
195+ "disk content"
196+ ) ;
197+ }
198+
199+ #[ test]
200+ fn test_buffer_overrides_disk ( ) {
201+ let mut disk_fs = InMemoryFileSystem :: new ( ) ;
202+ disk_fs. add_file ( "/test.py" . into ( ) , "disk content" . to_string ( ) ) ;
203+
204+ let buffers = Buffers :: new ( ) ;
205+ let fs = WorkspaceFileSystem :: new ( buffers. clone ( ) , Arc :: new ( disk_fs) ) ;
206+
207+ // Add buffer with different content
208+ let url = Url :: from_file_path ( "/test.py" ) . unwrap ( ) ;
209+ let doc = TextDocument :: new ( "buffer content" . to_string ( ) , 1 , LanguageId :: Python ) ;
210+ buffers. open ( url, doc) ;
211+
212+ assert_eq ! (
213+ fs. read_to_string( Path :: new( "/test.py" ) ) . unwrap( ) ,
214+ "buffer content"
215+ ) ;
216+ }
217+
218+ #[ test]
219+ fn test_exists_for_buffer_only_file ( ) {
220+ let disk = Arc :: new ( InMemoryFileSystem :: new ( ) ) ;
221+ let buffers = Buffers :: new ( ) ;
222+ let fs = WorkspaceFileSystem :: new ( buffers. clone ( ) , disk) ;
214223
215- #[ test]
216- fn test_overlay_with_relative_path ( ) {
217- // Create an empty filesystem (no files on disk)
218- let memory_fs = InMemoryFileSystem :: new ( ) ;
219- let buffers = Buffers :: new ( ) ;
220- let lsp_fs = WorkspaceFileSystem :: new ( buffers. clone ( ) , Arc :: new ( memory_fs) ) ;
221-
222- // Use a relative path that doesn't exist on disk
223- let relative_path = std:: path:: Path :: new ( "nonexistent/overlay.py" ) ;
224-
225- // Convert to absolute URL for the buffer (simulating how LSP would provide it)
226- let absolute_path = std:: env:: current_dir ( ) . unwrap ( ) . join ( relative_path) ;
227- let url = Url :: from_file_path ( & absolute_path) . unwrap ( ) ;
228-
229- // Add to overlay
230- let document = TextDocument :: new ( "relative overlay" . to_string ( ) , 1 , LanguageId :: Python ) ;
231- buffers. open ( url, document) ;
232-
233- // The relative path should now work through the overlay
234- assert ! (
235- lsp_fs. exists( relative_path) ,
236- "Relative overlay file should exist"
237- ) ;
238- assert_eq ! (
239- lsp_fs. read_to_string( relative_path) . unwrap( ) ,
240- "relative overlay" ,
241- "Should read relative overlay content"
242- ) ;
224+ // Add file only to buffer
225+ let url = Url :: from_file_path ( "/buffer_only.py" ) . unwrap ( ) ;
226+ let doc = TextDocument :: new ( "content" . to_string ( ) , 1 , LanguageId :: Python ) ;
227+ buffers. open ( url, doc) ;
228+
229+ assert ! ( fs. exists( Path :: new( "/buffer_only.py" ) ) ) ;
230+ }
231+
232+ #[ test]
233+ fn test_exists_for_disk_only_file ( ) {
234+ let mut disk_fs = InMemoryFileSystem :: new ( ) ;
235+ disk_fs. add_file ( "/disk_only.py" . into ( ) , "content" . to_string ( ) ) ;
236+
237+ let buffers = Buffers :: new ( ) ;
238+ let fs = WorkspaceFileSystem :: new ( buffers, Arc :: new ( disk_fs) ) ;
239+
240+ assert ! ( fs. exists( Path :: new( "/disk_only.py" ) ) ) ;
241+ }
242+
243+ #[ test]
244+ fn test_exists_for_both_buffer_and_disk ( ) {
245+ let mut disk_fs = InMemoryFileSystem :: new ( ) ;
246+ disk_fs. add_file ( "/both.py" . into ( ) , "disk" . to_string ( ) ) ;
247+
248+ let buffers = Buffers :: new ( ) ;
249+ let fs = WorkspaceFileSystem :: new ( buffers. clone ( ) , Arc :: new ( disk_fs) ) ;
250+
251+ // Also add to buffer
252+ let url = Url :: from_file_path ( "/both.py" ) . unwrap ( ) ;
253+ let doc = TextDocument :: new ( "buffer" . to_string ( ) , 1 , LanguageId :: Python ) ;
254+ buffers. open ( url, doc) ;
255+
256+ assert ! ( fs. exists( Path :: new( "/both.py" ) ) ) ;
257+ }
258+
259+ #[ test]
260+ fn test_exists_returns_false_when_nowhere ( ) {
261+ let disk = Arc :: new ( InMemoryFileSystem :: new ( ) ) ;
262+ let buffers = Buffers :: new ( ) ;
263+ let fs = WorkspaceFileSystem :: new ( buffers, disk) ;
264+
265+ assert ! ( !fs. exists( Path :: new( "/nowhere.py" ) ) ) ;
266+ }
267+
268+ #[ test]
269+ fn test_read_error_when_file_nowhere ( ) {
270+ let disk = Arc :: new ( InMemoryFileSystem :: new ( ) ) ;
271+ let buffers = Buffers :: new ( ) ;
272+ let fs = WorkspaceFileSystem :: new ( buffers, disk) ;
273+
274+ let result = fs. read_to_string ( Path :: new ( "/missing.py" ) ) ;
275+ assert ! ( result. is_err( ) ) ;
276+ assert_eq ! ( result. unwrap_err( ) . kind( ) , io:: ErrorKind :: NotFound ) ;
277+ }
278+
279+ #[ test]
280+ fn test_reflects_buffer_updates ( ) {
281+ let disk = Arc :: new ( InMemoryFileSystem :: new ( ) ) ;
282+ let buffers = Buffers :: new ( ) ;
283+ let fs = WorkspaceFileSystem :: new ( buffers. clone ( ) , disk) ;
284+
285+ let url = Url :: from_file_path ( "/test.py" ) . unwrap ( ) ;
286+
287+ // Initial buffer content
288+ let doc1 = TextDocument :: new ( "version 1" . to_string ( ) , 1 , LanguageId :: Python ) ;
289+ buffers. open ( url. clone ( ) , doc1) ;
290+ assert_eq ! (
291+ fs. read_to_string( Path :: new( "/test.py" ) ) . unwrap( ) ,
292+ "version 1"
293+ ) ;
294+
295+ // Update buffer content
296+ let doc2 = TextDocument :: new ( "version 2" . to_string ( ) , 2 , LanguageId :: Python ) ;
297+ buffers. update ( url, doc2) ;
298+ assert_eq ! (
299+ fs. read_to_string( Path :: new( "/test.py" ) ) . unwrap( ) ,
300+ "version 2"
301+ ) ;
302+ }
303+
304+ #[ test]
305+ fn test_handles_buffer_removal ( ) {
306+ let mut disk_fs = InMemoryFileSystem :: new ( ) ;
307+ disk_fs. add_file ( "/test.py" . into ( ) , "disk content" . to_string ( ) ) ;
308+
309+ let buffers = Buffers :: new ( ) ;
310+ let fs = WorkspaceFileSystem :: new ( buffers. clone ( ) , Arc :: new ( disk_fs) ) ;
311+
312+ let url = Url :: from_file_path ( "/test.py" ) . unwrap ( ) ;
313+
314+ // Add buffer
315+ let doc = TextDocument :: new ( "buffer content" . to_string ( ) , 1 , LanguageId :: Python ) ;
316+ buffers. open ( url. clone ( ) , doc) ;
317+ assert_eq ! (
318+ fs. read_to_string( Path :: new( "/test.py" ) ) . unwrap( ) ,
319+ "buffer content"
320+ ) ;
321+
322+ // Remove buffer
323+ let _ = buffers. close ( & url) ;
324+ assert_eq ! (
325+ fs. read_to_string( Path :: new( "/test.py" ) ) . unwrap( ) ,
326+ "disk content"
327+ ) ;
328+ }
243329 }
244330}
0 commit comments