1- use std:: sync:: Arc ;
2- use std:: { any:: Any , path:: PathBuf } ;
3-
41use filetime:: FileTime ;
5-
62use ruff_notebook:: { Notebook , NotebookError } ;
3+ use rustc_hash:: FxHashSet ;
4+ use std:: panic:: RefUnwindSafe ;
5+ use std:: sync:: Arc ;
6+ use std:: { any:: Any , path:: PathBuf } ;
77
88use crate :: system:: {
99 DirectoryEntry , FileType , GlobError , GlobErrorKind , Metadata , Result , System , SystemPath ,
@@ -25,6 +25,8 @@ pub struct OsSystem {
2525struct OsSystemInner {
2626 cwd : SystemPathBuf ,
2727
28+ real_case_cache : RealCaseCache ,
29+
2830 /// Overrides the user's configuration directory for testing.
2931 /// This is an `Option<Option<..>>` to allow setting an override of `None`.
3032 #[ cfg( feature = "testing" ) ]
@@ -102,6 +104,27 @@ impl System for OsSystem {
102104 path. as_std_path ( ) . exists ( )
103105 }
104106
107+ fn path_exists_case_sensitive ( & self , path : & SystemPath , prefix : & SystemPath ) -> Result < bool > {
108+ // Decide what to do if prefix isn't a prefix
109+ debug_assert ! (
110+ path. strip_prefix( prefix) . is_ok( ) ,
111+ "prefix `{prefix}` should be a prefix of `{path}`"
112+ ) ;
113+
114+ // Iterate over the sub-paths up to prefix and check if they match the casing as on disk.
115+ for ancestor in path. ancestors ( ) {
116+ if ancestor == prefix {
117+ break ;
118+ }
119+
120+ if !self . inner . real_case_cache . has_name_case ( ancestor) ? {
121+ return Ok ( false ) ;
122+ }
123+ }
124+
125+ Ok ( true )
126+ }
127+
105128 fn current_directory ( & self ) -> & SystemPath {
106129 & self . inner . cwd
107130 }
@@ -201,6 +224,82 @@ impl WritableSystem for OsSystem {
201224 }
202225}
203226
227+ #[ derive( Debug , Default ) ]
228+ struct RealCaseCache {
229+ by_lower_case : dashmap:: DashMap < SystemPathBuf , ListedDirectory > ,
230+ }
231+
232+ impl RealCaseCache {
233+ fn has_name_case ( & self , path : & SystemPath ) -> Result < bool > {
234+ // TODO: Skip if the FS is known to be case-sensitive.
235+ // TODO: Consider using `GetFinalPathNameByHandleW` on windows
236+ // TODO: Consider using `fcntl(F_GETPATH)` on macOS (https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html)
237+
238+ let Some ( parent) = path. parent ( ) else {
239+ // TODO: Decide what to return for the root path
240+ return Ok ( true ) ;
241+ } ;
242+
243+ // TODO: decide what to return for the root path or files ending in `..`
244+ let Some ( file_name) = path. file_name ( ) else {
245+ return Ok ( false ) ;
246+ } ;
247+
248+ let lower_case_path = SystemPathBuf :: from ( parent. as_str ( ) . to_lowercase ( ) ) ;
249+ let last_modification_time =
250+ FileTime :: from_last_modification_time ( & parent. as_std_path ( ) . metadata ( ) ?) ;
251+
252+ let entry = self . by_lower_case . entry ( lower_case_path) ;
253+
254+ if let dashmap:: Entry :: Occupied ( entry) = & entry {
255+ // Only do a cached lookup if the directory hasn't changed.
256+ if entry. get ( ) . last_modification_time == last_modification_time {
257+ tracing:: trace!( "Use cached 'real-case' entry for directory `{}`" , parent) ;
258+ return Ok ( entry. get ( ) . names . contains ( file_name) ) ;
259+ }
260+ }
261+
262+ tracing:: trace!(
263+ "Reading directory `{}` to determine the real casing of paths" ,
264+ parent
265+ ) ;
266+ let start = std:: time:: Instant :: now ( ) ;
267+ let mut names = FxHashSet :: default ( ) ;
268+
269+ for entry in parent. as_std_path ( ) . read_dir ( ) ? {
270+ let Ok ( entry) = entry else {
271+ continue ;
272+ } ;
273+
274+ let Ok ( name) = entry. file_name ( ) . into_string ( ) else {
275+ continue ;
276+ } ;
277+
278+ names. insert ( name) ;
279+ }
280+
281+ let directory = entry. insert ( ListedDirectory {
282+ last_modification_time,
283+ names,
284+ } ) ;
285+
286+ tracing:: trace!(
287+ "Caching the real casing of `{parent}` took {:?}" ,
288+ start. elapsed( )
289+ ) ;
290+
291+ Ok ( directory. names . contains ( file_name) )
292+ }
293+ }
294+
295+ impl RefUnwindSafe for RealCaseCache { }
296+
297+ #[ derive( Debug , Eq , PartialEq ) ]
298+ struct ListedDirectory {
299+ last_modification_time : filetime:: FileTime ,
300+ names : FxHashSet < String > ,
301+ }
302+
204303#[ derive( Debug ) ]
205304struct OsDirectoryWalker ;
206305
0 commit comments