66//
77
88use std:: collections:: HashMap ;
9+ use std:: collections:: HashSet ;
910use std:: future;
1011use std:: path:: Path ;
1112use std:: path:: PathBuf ;
@@ -15,12 +16,17 @@ use std::sync::atomic::Ordering;
1516use std:: sync:: LazyLock ;
1617use std:: sync:: RwLock ;
1718
19+ use aether_url:: UrlId ;
1820use anyhow:: anyhow;
1921use futures:: stream:: FuturesUnordered ;
2022use futures:: StreamExt ;
2123use oak_db:: OakDatabase ;
2224use oak_scan:: DbExt ;
25+ use oak_scan:: ScanCompleted ;
26+ use oak_scan:: ScanRequest ;
27+ use oak_scan:: ScanScheduler ;
2328use oak_semantic:: library:: Library ;
29+ use stdext:: result:: ResultExt ;
2430use tokio:: sync:: mpsc;
2531use tokio:: sync:: mpsc:: unbounded_channel as tokio_unbounded_channel;
2632use tokio:: task;
@@ -88,6 +94,7 @@ type TaskList<T> = futures::stream::FuturesUnordered<Pin<Box<dyn AnyhowJoinHandl
8894pub ( crate ) enum Event {
8995 Lsp ( LspMessage ) ,
9096 Kernel ( KernelNotification ) ,
97+ OakScanCompleted ( ScanCompleted ) ,
9198}
9299
93100#[ derive( Debug ) ]
@@ -151,8 +158,9 @@ pub(crate) struct GlobalState {
151158 events_rx : TokioUnboundedReceiver < Event > ,
152159}
153160
154- /// Unlike `WorldState`, `ParserState` cannot be cloned and is only accessed by
155- /// exclusive handlers.
161+ /// Non-cloneable, per-session state mutated only by exclusive handlers.
162+ /// Sits alongside [`WorldState`] (which is cloneable for snapshot
163+ /// handlers); state that can't be cloned lives here instead.
156164pub ( crate ) struct LspState {
157165 /// The set of tree-sitter document parsers managed by the `GlobalState`.
158166 pub ( crate ) parsers : HashMap < Url , tree_sitter:: Parser > ,
@@ -162,6 +170,11 @@ pub(crate) struct LspState {
162170
163171 /// Channel for sending notifications to Console (e.g., document changes for DAP)
164172 pub ( crate ) console_notification_tx : TokioUnboundedSender < ConsoleNotification > ,
173+
174+ /// Coordinator for asynchronous workspace scans. Mutated only from
175+ /// main-loop handlers. Must be out of [`WorldState`] because the scheduler
176+ /// is not clonable.
177+ pub ( crate ) oak_scheduler : ScanScheduler ,
165178}
166179
167180/// State for the auxiliary loop
@@ -199,6 +212,7 @@ impl GlobalState {
199212 parsers : HashMap :: new ( ) ,
200213 capabilities : Capabilities :: default ( ) ,
201214 console_notification_tx,
215+ oak_scheduler : ScanScheduler :: new ( ) ,
202216 } ;
203217
204218 // FIXME: We shouldn't call R code in the kernel to figure this out
@@ -299,13 +313,15 @@ impl GlobalState {
299313 handlers:: handle_initialized ( & self . client , & self . lsp_state ) . await ?;
300314 } ,
301315 LspNotification :: DidChangeWorkspaceFolders ( params) => {
302- state_handlers:: did_change_workspace_folders ( params, & mut self . world ) ?;
316+ let pending = state_handlers:: did_change_workspace_folders ( params, & mut self . world , & mut self . lsp_state ) ?;
317+ dispatch_scan_requests ( & self . events_tx , pending) ;
303318 } ,
304319 LspNotification :: DidChangeConfiguration ( params) => {
305320 state_handlers:: did_change_configuration ( params, & self . client , & mut self . world ) . await ?;
306321 } ,
307322 LspNotification :: DidChangeWatchedFiles ( params) => {
308- state_handlers:: did_change_watched_files ( params, & mut self . world ) ?;
323+ let pending = state_handlers:: did_change_watched_files ( params, & mut self . world , & mut self . lsp_state ) ?;
324+ dispatch_scan_requests ( & self . events_tx , pending) ;
309325 } ,
310326 LspNotification :: DidOpenTextDocument ( params) => {
311327 state_handlers:: did_open ( params, & mut self . lsp_state , & mut self . world ) ?;
@@ -336,7 +352,8 @@ impl GlobalState {
336352
337353 match request {
338354 LspRequest :: Initialize ( params) => {
339- respond ( tx, || state_handlers:: initialize ( params, & mut self . lsp_state , & mut self . world ) , LspResponse :: Initialize ) ?;
355+ let pending = respond_with ( tx, || state_handlers:: initialize ( params, & mut self . lsp_state , & mut self . world ) , LspResponse :: Initialize ) ?;
356+ dispatch_scan_requests ( & self . events_tx , pending) ;
340357 } ,
341358 LspRequest :: WorkspaceSymbol ( params) => {
342359 respond ( tx, || handlers:: handle_symbol ( params, & self . world ) , LspResponse :: WorkspaceSymbol ) ?;
@@ -420,6 +437,25 @@ impl GlobalState {
420437 }
421438 }
422439 } ,
440+
441+ Event :: OakScanCompleted ( scan) => {
442+ // Recompute editor-owned files at apply time, not at spawn
443+ // time: a buffer may have opened or closed since the scan
444+ // kicked off. The buffer-drain inside `apply_scan_completed` uses
445+ // this set as its watcher-event `skip` argument.
446+ let editor_owned: HashSet < UrlId > = self . world
447+ . documents
448+ . keys ( )
449+ . map ( |url| UrlId :: from_url ( url. clone ( ) ) )
450+ . collect ( ) ;
451+
452+ let followups = self . lsp_state . oak_scheduler . apply_scan_completed (
453+ & mut self . world . oak ,
454+ scan,
455+ & editor_owned,
456+ ) ;
457+ dispatch_scan_requests ( & self . events_tx , followups) ;
458+ } ,
423459 }
424460
425461 // TODO Make this threshold configurable by the client
@@ -452,6 +488,24 @@ impl GlobalState {
452488 }
453489}
454490
491+ /// Spawn each [`ScanRequest`] on a blocking task. Each task runs the
492+ /// pure-I/O [`ScanRequest::run`] and ships the [`ScanCompleted`] back
493+ /// to the main loop as [`Event::OakScanCompleted`], where the scheduler
494+ /// then applies it.
495+ pub ( super ) fn dispatch_scan_requests (
496+ events_tx : & TokioUnboundedSender < Event > ,
497+ requests : Vec < ScanRequest > ,
498+ ) {
499+ for req in requests {
500+ let tx = events_tx. clone ( ) ;
501+ spawn_blocking ( move || {
502+ let scan = req. run ( ) ;
503+ tx. send ( Event :: OakScanCompleted ( scan) ) . log_err ( ) ;
504+ Ok ( None )
505+ } ) ;
506+ }
507+ }
508+
455509/// Respond to a request from the LSP
456510///
457511/// We receive requests from the LSP client with a response channel. Once we
@@ -475,11 +529,28 @@ fn respond<T>(
475529 response : impl FnOnce ( ) -> LspResult < T > ,
476530 into_lsp_response : impl FnOnce ( T ) -> LspResponse ,
477531) -> anyhow:: Result < ( ) > {
478- let response = match std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( response) ) {
479- Ok ( response) => {
480- let response = response. map ( into_lsp_response) ;
481- RequestResponse :: Result ( response)
482- } ,
532+ respond_with (
533+ response_tx,
534+ || response ( ) . map ( |t| ( t, ( ) ) ) ,
535+ into_lsp_response,
536+ )
537+ }
538+
539+ /// Variant of [`respond`] for handlers that produce a side output alongside
540+ /// their LSP response. The `S` value is returned to the caller on success;
541+ /// on handler error or panic the side output is `S::default()` (the caller
542+ /// gets the "do nothing" value, while the error response still flows to
543+ /// the client through `response_tx`). Used by `Initialize` to ship the
544+ /// scheduler's pending `ScanRequest`s back to the main loop without an
545+ /// out-parameter.
546+ fn respond_with < T , S : Default > (
547+ response_tx : TokioUnboundedSender < RequestResponse > ,
548+ response : impl FnOnce ( ) -> LspResult < ( T , S ) > ,
549+ into_lsp_response : impl FnOnce ( T ) -> LspResponse ,
550+ ) -> anyhow:: Result < S > {
551+ let ( response, side) = match std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( response) ) {
552+ Ok ( Ok ( ( t, s) ) ) => ( RequestResponse :: Result ( Ok ( into_lsp_response ( t) ) ) , s) ,
553+ Ok ( Err ( e) ) => ( RequestResponse :: Result ( Err ( e) ) , S :: default ( ) ) ,
483554 Err ( err) => {
484555 // Set global crash flag to disable the LSP
485556 LSP_HAS_CRASHED . store ( true , Ordering :: Release ) ;
@@ -495,19 +566,22 @@ fn respond<T>(
495566 // This creates an uninformative backtrace that is reported in the
496567 // LSP logs. Note that the relevant backtrace is the one created by
497568 // our panic hook and reported via the _kernel_ logs.
498- RequestResponse :: Crashed ( anyhow ! ( "Panic occurred while handling request: {msg}" ) )
569+ (
570+ RequestResponse :: Crashed ( anyhow ! ( "Panic occurred while handling request: {msg}" ) ) ,
571+ S :: default ( ) ,
572+ )
499573 } ,
500574 } ;
501575
502576 let out = match response {
503- RequestResponse :: Result ( Ok ( _) ) => Ok ( ( ) ) ,
577+ RequestResponse :: Result ( Ok ( _) ) => Ok ( side ) ,
504578 RequestResponse :: Result ( Err ( ref error) ) => {
505579 // The error has already been sent to the client on `response_tx`
506580 // as a jsonrpc error, so the user sees the popup. Log here at
507581 // info level (with `{:?}` for the full debug format including a
508582 // backtrace) so server logs keep diagnostic context.
509583 lsp:: log_info!( "Error while handling request:\n {error:?}" ) ;
510- Ok ( ( ) )
584+ Ok ( side )
511585 } ,
512586 RequestResponse :: Crashed ( ref error) => {
513587 Err ( anyhow ! ( "Crashed while handling request:\n {error:?}" ) )
0 commit comments