@@ -15,7 +15,10 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) {
1515 const [ loadingDirectories , setLoadingDirectories ] = useState < ReadonlySet < string > > (
1616 ( ) => new Set ( [ "" ] ) ,
1717 ) ;
18+ // Source of truth for `directories`, so a listing can purge folders it no longer holds.
19+ const listings = useRef ( new Map < string , readonly ProjectEntry [ ] > ( ) ) ;
1820 const requests = useRef ( new Map < string , Promise < void > > ( ) ) ;
21+ const detachedRequests = useRef ( new Map < string , Promise < void > > ( ) ) ;
1922 const followUpRefreshes = useRef ( new Map < string , Promise < void > > ( ) ) ;
2023 const loaded = useRef ( new Set < string > ( ) ) ;
2124 const requested = useRef ( new Set < string > ( ) ) ;
@@ -27,7 +30,10 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) {
2730 loaded . current . add ( directoryPath ) ;
2831 requested . current . add ( directoryPath ) ;
2932 const atom = projectEnvironment . listEntries ( { environmentId, input : { cwd, directoryPath } } ) ;
33+ const detached = detachedRequests . current . get ( directoryPath ) ;
3034 const request = ( async ( ) => {
35+ // A shared query still waiting would join the removed folder's read.
36+ if ( detached ) await detached ;
3137 if ( running . current >= 4 ) await new Promise < void > ( ( resolve ) => waiting . current . push ( resolve ) ) ;
3238 else running . current ++ ;
3339 const result = active . current
@@ -43,21 +49,44 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) {
4349 return result ;
4450 } ) ( )
4551 . then ( ( result ) => {
46- if ( ! active . current || ! result ) return ;
52+ // A request is dropped from `requests` when its folder disappears, so its read is stale.
53+ if ( ! active . current || ! result || requests . current . get ( directoryPath ) !== request ) return ;
4754 if ( result . _tag === "Success" ) {
48- setDirectories ( ( previous ) =>
49- new Map ( previous ) . set (
50- directoryPath ,
51- result . value . entries . filter (
52- ( entry ) =>
53- entry . path . slice ( 0 , Math . max ( 0 , entry . path . lastIndexOf ( "/" ) ) ) === directoryPath ,
54- ) ,
55- ) ,
55+ const children = result . value . entries . filter (
56+ ( entry ) =>
57+ entry . path . slice ( 0 , Math . max ( 0 , entry . path . lastIndexOf ( "/" ) ) ) === directoryPath ,
58+ ) ;
59+ const next = new Map ( listings . current ) . set ( directoryPath , children ) ;
60+ const childDirectories = new Set (
61+ children . filter ( ( entry ) => entry . kind === "directory" ) . map ( ( entry ) => entry . path ) ,
5662 ) ;
63+ const prefix = directoryPath === "" ? "" : `${ directoryPath } /` ;
64+ const removed = [ ...requested . current ] . filter ( ( path ) => {
65+ if ( path === directoryPath || ! path . startsWith ( prefix ) ) return false ;
66+ const slash = path . indexOf ( "/" , prefix . length ) ;
67+ return ! childDirectories . has ( slash === - 1 ? path : path . slice ( 0 , slash ) ) ;
68+ } ) ;
69+ for ( const path of removed ) {
70+ next . delete ( path ) ;
71+ loaded . current . delete ( path ) ;
72+ requested . current . delete ( path ) ;
73+ const detachedRequest = requests . current . get ( path ) ;
74+ if ( detachedRequest ) detachedRequests . current . set ( path , detachedRequest ) ;
75+ requests . current . delete ( path ) ;
76+ followUpRefreshes . current . delete ( path ) ;
77+ }
78+ listings . current = next ;
79+ setDirectories ( next ) ;
80+ if ( removed . length > 0 )
81+ setLoadingDirectories ( ( previous ) => {
82+ const remaining = new Set ( previous ) ;
83+ for ( const path of removed ) remaining . delete ( path ) ;
84+ return remaining ;
85+ } ) ;
5786 setErrors ( ( previous ) => {
58- const next = new Map ( previous ) ;
59- next . delete ( directoryPath ) ;
60- return next ;
87+ const remaining = new Map ( previous ) ;
88+ for ( const path of [ directoryPath , ... removed ] ) remaining . delete ( path ) ;
89+ return remaining ;
6190 } ) ;
6291 } else {
6392 loaded . current . delete ( directoryPath ) ;
@@ -71,10 +100,13 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) {
71100 }
72101 } )
73102 . finally ( ( ) => {
74- requests . current . delete ( directoryPath ) ;
103+ if ( detachedRequests . current . get ( directoryPath ) === request )
104+ detachedRequests . current . delete ( directoryPath ) ;
105+ const current = requests . current . get ( directoryPath ) === request ;
106+ if ( current ) requests . current . delete ( directoryPath ) ;
75107 if ( ! active . current ) return ;
76108 setPending ( ( count ) => count - 1 ) ;
77- if ( ! refresh )
109+ if ( ! refresh && current )
78110 setLoadingDirectories ( ( previous ) => {
79111 const next = new Set ( previous ) ;
80112 next . delete ( directoryPath ) ;
@@ -92,8 +124,10 @@ export function useDirectoryEntries(environmentId: EnvironmentId, cwd: string) {
92124 let followUp = followUpRefreshes . current . get ( directoryPath ) ;
93125 if ( ! followUp ) {
94126 followUp = existing . then ( ( ) => {
127+ if ( followUpRefreshes . current . get ( directoryPath ) !== followUp ) return ;
95128 followUpRefreshes . current . delete ( directoryPath ) ;
96- if ( active . current ) return loadDirectory ( directoryPath , true ) ;
129+ if ( active . current && requested . current . has ( directoryPath ) )
130+ return loadDirectory ( directoryPath , true ) ;
97131 } ) ;
98132 followUpRefreshes . current . set ( directoryPath , followUp ) ;
99133 }
0 commit comments