@@ -24,6 +24,7 @@ use log::{debug, info};
2424use crate :: file_cache:: TextDocumentCache ;
2525use crate :: init_options:: ClientInitializationOptions ;
2626use crate :: lsp_logger;
27+ use crate :: prefetch:: { self , PrefetchHandle } ;
2728
2829const SOURCE_NAME : & str = "Codebook" ;
2930
@@ -62,6 +63,23 @@ fn compute_relative_path(
6263}
6364
6465pub struct Backend {
66+ state : Arc < BackendState > ,
67+ }
68+
69+ /// `Backend` is a pointer newtype over the shared state; Deref keeps every
70+ /// handler spelling `self.field` instead of `self.state.field`.
71+ impl std:: ops:: Deref for Backend {
72+ type Target = BackendState ;
73+ fn deref ( & self ) -> & Self :: Target {
74+ & self . state
75+ }
76+ }
77+
78+ /// Shared server state, `Arc`'d so tasks outside the LSP dispatch loop (the
79+ /// prefetch worker's recheck listener) can reach the same document cache,
80+ /// config, and client. Public only because `Deref<Target = BackendState>`
81+ /// requires it; all fields are private.
82+ pub struct BackendState {
6583 client : Client ,
6684 workspace_dir : PathBuf ,
6785 /// Cached canonicalized workspace directory for efficient relative path computation
@@ -72,6 +90,9 @@ pub struct Backend {
7290 initialize_options : RwLock < Arc < ClientInitializationOptions > > ,
7391 /// When the config files were last polled for changes (None = never)
7492 last_config_poll : Mutex < Option < Instant > > ,
93+ /// Handle to the background dictionary prefetch worker (None until
94+ /// `initialized`, or forever when NO_NETWORK is set)
95+ prefetch : OnceLock < PrefetchHandle > ,
7596}
7697
7798enum CodebookCommand {
@@ -169,6 +190,7 @@ impl LanguageServer for Backend {
169190 "Global config: {}" ,
170191 config. global_config_path( ) . unwrap_or_default( ) . display( )
171192 ) ;
193+ self . spawn_prefetch ( ) ;
172194 }
173195
174196 async fn shutdown ( & self ) -> RpcResult < ( ) > {
@@ -388,17 +410,45 @@ impl Backend {
388410 pub fn new ( client : Client , workspace_dir : & Path ) -> Self {
389411 let workspace_dir_canonical = workspace_dir. canonicalize ( ) . ok ( ) ;
390412 Self {
391- client,
392- workspace_dir : workspace_dir. to_path_buf ( ) ,
393- workspace_dir_canonical,
394- codebook : OnceLock :: new ( ) ,
395- config : OnceLock :: new ( ) ,
396- document_cache : TextDocumentCache :: default ( ) ,
397- initialize_options : RwLock :: new ( Arc :: new ( ClientInitializationOptions :: default ( ) ) ) ,
398- last_config_poll : Mutex :: new ( None ) ,
413+ state : Arc :: new ( BackendState {
414+ client,
415+ workspace_dir : workspace_dir. to_path_buf ( ) ,
416+ workspace_dir_canonical,
417+ codebook : OnceLock :: new ( ) ,
418+ config : OnceLock :: new ( ) ,
419+ document_cache : TextDocumentCache :: default ( ) ,
420+ initialize_options : RwLock :: new ( Arc :: new ( ClientInitializationOptions :: default ( ) ) ) ,
421+ last_config_poll : Mutex :: new ( None ) ,
422+ prefetch : OnceLock :: new ( ) ,
423+ } ) ,
424+ }
425+ }
426+
427+ /// Start the background dictionary prefetch worker and the task that
428+ /// re-checks open documents when a new dictionary lands.
429+ fn spawn_prefetch ( & self ) {
430+ if self . prefetch . get ( ) . is_some ( ) {
431+ return ;
399432 }
433+ if codebook:: network_disabled ( ) {
434+ info ! ( "NO_NETWORK set; dictionary downloads disabled" ) ;
435+ return ;
436+ }
437+ let ( tx, mut rx) = tokio:: sync:: mpsc:: unbounded_channel ( ) ;
438+ let state = self . state . clone ( ) ;
439+ tokio:: spawn ( async move {
440+ while rx. recv ( ) . await . is_some ( ) {
441+ // Coalesce a burst of completions into one recheck
442+ while rx. try_recv ( ) . is_ok ( ) { }
443+ state. recheck_all ( ) . await ;
444+ }
445+ } ) ;
446+ let handle = prefetch:: spawn ( self . codebook_handle ( ) , tx) ;
447+ let _ = self . prefetch . set ( handle) ;
400448 }
449+ }
401450
451+ impl BackendState {
402452 fn config_handle ( & self ) -> Arc < CodebookConfigFile > {
403453 self . config
404454 . get_or_init ( || {
@@ -556,6 +606,11 @@ impl Backend {
556606
557607 if did_reload {
558608 debug ! ( "Config reloaded, rechecking all files." ) ;
609+ // The change may add dictionaries: wake the prefetch worker and
610+ // reset its backoff so they download immediately.
611+ if let Some ( handle) = self . prefetch . get ( ) {
612+ handle. kick ( ) ;
613+ }
559614 self . recheck_all ( ) . await ;
560615 } else {
561616 debug ! ( "Checking file: {uri:?}" ) ;
0 commit comments