@@ -4,6 +4,7 @@ use std::io::{BufWriter, Write};
44use std:: path:: { Path , PathBuf } ;
55use std:: sync:: Arc ;
66use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
7+ use std:: time:: Duration ;
78
89use crate :: config:: Config ;
910use crate :: document_state:: DocumentState ;
@@ -36,7 +37,7 @@ use tower_lsp_server::lsp_types::notification::{Progress, PublishDiagnostics};
3637use tower_lsp_server:: lsp_types:: request:: WorkDoneProgressCreate ;
3738use tower_lsp_server:: lsp_types:: {
3839 CodeActionOrCommand , CodeActionParams , CodeActionProviderCapability , CodeActionResponse ,
39- Diagnostic , DidChangeConfigurationParams , DidChangeTextDocumentParams ,
40+ ConfigurationItem , Diagnostic , DidChangeConfigurationParams , DidChangeTextDocumentParams ,
4041 DidChangeWatchedFilesParams , DidCloseTextDocumentParams , DidOpenTextDocumentParams ,
4142 DidSaveTextDocumentParams , ExecuteCommandOptions , ExecuteCommandParams , FileChangeType ,
4243 InitializeParams , InitializeResult , InitializedParams , MessageType , NumberOrString ,
@@ -53,14 +54,16 @@ pub fn ls_version() -> &'static str {
5354 env ! ( "CARGO_PKG_VERSION" )
5455}
5556
57+ #[ derive( Clone ) ]
5658pub struct Backend {
5759 client : Client ,
58- root : RwLock < PathBuf > ,
59- config : RwLock < Config > ,
60- stats : RwLock < Stats > ,
61- doc_state : Mutex < HashMap < Uri , DocumentState > > ,
62- progress_counter : AtomicU64 ,
63- lang_detect : LanguageDetectionRegistry ,
60+ root : Arc < RwLock < PathBuf > > ,
61+ config : Arc < RwLock < Config > > ,
62+ stats : Arc < RwLock < Stats > > ,
63+ doc_state : Arc < Mutex < HashMap < Uri , DocumentState > > > ,
64+ progress_counter : Arc < AtomicU64 > ,
65+ lang_detect : Arc < LanguageDetectionRegistry > ,
66+ pending_diagnostic_tasks : Arc < Mutex < HashMap < Uri , tokio:: task:: JoinHandle < ( ) > > > > ,
6467}
6568
6669const MIN_WORDS_FOR_LANGUAGE_DETECTION : usize = 10 ;
@@ -69,12 +72,13 @@ impl Backend {
6972 pub fn new ( client : Client , config : Config ) -> Self {
7073 Self {
7174 client,
72- root : RwLock :: new ( "." . into ( ) ) ,
73- stats : RwLock :: new ( Stats :: new ( ) ) ,
74- config : RwLock :: new ( config) ,
75- doc_state : Mutex :: new ( HashMap :: new ( ) ) ,
76- progress_counter : AtomicU64 :: new ( 1 ) ,
77- lang_detect : LanguageDetectionRegistry :: new ( ) ,
75+ root : Arc :: new ( RwLock :: new ( "." . into ( ) ) ) ,
76+ stats : Arc :: new ( RwLock :: new ( Stats :: new ( ) ) ) ,
77+ config : Arc :: new ( RwLock :: new ( config) ) ,
78+ doc_state : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
79+ progress_counter : Arc :: new ( AtomicU64 :: new ( 1 ) ) ,
80+ lang_detect : Arc :: new ( LanguageDetectionRegistry :: new ( ) ) ,
81+ pending_diagnostic_tasks : Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ,
7882 }
7983 }
8084
@@ -759,6 +763,13 @@ impl Backend {
759763 }
760764 }
761765
766+ /// The async pending diagnostics are discarded and diagnostics are created
767+ /// and published immediately.
768+ async fn publish_diagnostics_immediately ( & self , uri : & Uri ) {
769+ self . abort_pending_diagnostics ( uri) . await ;
770+ self . publish_diagnostics ( uri) . await ;
771+ }
772+
762773 /// Update the configuration of the server and publish document updates that
763774 /// match it.
764775 async fn update_config_from_obj ( & self , json_obj : Value ) {
@@ -795,6 +806,41 @@ impl Backend {
795806 * config = new_config;
796807 }
797808 }
809+ async fn pull_config ( & self ) {
810+ let mut new_config = self
811+ . client
812+ . configuration ( vec ! [ ConfigurationItem {
813+ scope_uri: None ,
814+ section: None ,
815+ } ] )
816+ . await
817+ . unwrap_or ( vec ! [ json!( { "harper-ls" : { } } ) ] ) ;
818+
819+ if let Some ( first) = new_config. pop ( ) {
820+ self . update_config_from_obj ( first) . await ;
821+ }
822+ }
823+
824+ /// Aborts a pending diagnostic run, e.g. because the document was changed.
825+ async fn abort_pending_diagnostics ( & self , uri : & Uri ) {
826+ let mut tasks = self . pending_diagnostic_tasks . lock ( ) . await ;
827+ if let Some ( task) = tasks. remove ( uri) {
828+ task. abort ( ) ;
829+ }
830+ }
831+
832+ /// Schedule diagnostics in `delay_ms` milliseconds
833+ pub async fn schedule_diagnostics ( & self , uri : & Uri , delay_ms : u64 ) {
834+ let task_uri = uri. clone ( ) ;
835+ let backend = self . clone ( ) ;
836+ let handle = tokio:: spawn ( async move {
837+ tokio:: time:: sleep ( Duration :: from_millis ( delay_ms) ) . await ;
838+ backend. publish_diagnostics ( & task_uri) . await ;
839+ } ) ;
840+
841+ let mut tasks = self . pending_diagnostic_tasks . lock ( ) . await ;
842+ tasks. insert ( uri. clone ( ) , handle) ;
843+ }
798844}
799845
800846impl LanguageServer for Backend {
@@ -875,7 +921,8 @@ impl LanguageServer for Backend {
875921 error ! ( "{err}" ) ;
876922 }
877923
878- self . publish_diagnostics ( & params. text_document . uri ) . await ;
924+ self . publish_diagnostics_immediately ( & params. text_document . uri )
925+ . await ;
879926 self . end_progress ( progress, "Analysis complete" ) . await ;
880927 }
881928
@@ -895,15 +942,25 @@ impl LanguageServer for Backend {
895942 error ! ( "{err}" )
896943 }
897944
898- self . publish_diagnostics ( & params. text_document . uri ) . await ;
945+ let delay_ms = self . config . read ( ) . await . diagnostic_delay_ms ;
946+ if delay_ms > 0 {
947+ // Diagnostics delay is configured, abort pending diagnostic task
948+ // and schedule a new diagnostic run in delay_ms milliseconds.
949+ self . abort_pending_diagnostics ( & params. text_document . uri )
950+ . await ;
951+ self . schedule_diagnostics ( & params. text_document . uri , delay_ms)
952+ . await ;
953+ } else {
954+ self . publish_diagnostics ( & params. text_document . uri ) . await ;
955+ }
899956 self . end_progress ( progress, "Diagnostics updated" ) . await ;
900957 }
901958
902959 async fn did_close ( & self , _params : DidCloseTextDocumentParams ) {
903960 let uri = _params. text_document . uri ;
904961 let mut doc_lock = self . doc_state . lock ( ) . await ;
905962 doc_lock. remove ( & uri) ;
906-
963+ self . abort_pending_diagnostics ( & uri ) . await ;
907964 self . client
908965 . send_notification :: < PublishDiagnostics > ( PublishDiagnosticsParams {
909966 uri : uri. clone ( ) ,
@@ -935,6 +992,7 @@ impl LanguageServer for Backend {
935992 }
936993
937994 for uri in & uris_to_clear {
995+ self . abort_pending_diagnostics ( uri) . await ;
938996 self . client
939997 . send_notification :: < PublishDiagnostics > ( PublishDiagnosticsParams {
940998 uri : uri. clone ( ) ,
@@ -989,7 +1047,7 @@ impl LanguageServer for Backend {
9891047 . await
9901048 . map_err ( |err| error ! ( "{err}" ) )
9911049 . err ( ) ;
992- self . publish_diagnostics ( & file_uri) . await ;
1050+ self . publish_diagnostics_immediately ( & file_uri) . await ;
9931051 }
9941052 "HarperAddToWSDict" => {
9951053 let word = & first. chars ( ) . collect :: < Vec < _ > > ( ) ;
@@ -1011,7 +1069,7 @@ impl LanguageServer for Backend {
10111069 . await
10121070 . map_err ( |err| error ! ( "{err}" ) )
10131071 . err ( ) ;
1014- self . publish_diagnostics ( & file_uri) . await ;
1072+ self . publish_diagnostics_immediately ( & file_uri) . await ;
10151073 }
10161074 "HarperAddToFileDict" => {
10171075 let word = & first. chars ( ) . collect :: < Vec < _ > > ( ) ;
@@ -1043,7 +1101,7 @@ impl LanguageServer for Backend {
10431101 . await
10441102 . map_err ( |err| error ! ( "{err}" ) )
10451103 . err ( ) ;
1046- self . publish_diagnostics ( & file_uri) . await ;
1104+ self . publish_diagnostics_immediately ( & file_uri) . await ;
10471105 }
10481106 "HarperOpen" => match open:: that ( & first) {
10491107 Ok ( ( ) ) => {
@@ -1093,7 +1151,7 @@ impl LanguageServer for Backend {
10931151
10941152 drop ( doc_lock) ;
10951153
1096- self . publish_diagnostics ( & uri) . await ;
1154+ self . publish_diagnostics_immediately ( & uri) . await ;
10971155 }
10981156 _ => ( ) ,
10991157 }
@@ -1128,7 +1186,7 @@ impl LanguageServer for Backend {
11281186 . await
11291187 . map_err ( |err| error ! ( "{err}" ) )
11301188 . err ( ) ;
1131- self . publish_diagnostics ( & uri) . await ;
1189+ self . publish_diagnostics_immediately ( & uri) . await ;
11321190 }
11331191 }
11341192
@@ -1155,6 +1213,7 @@ impl LanguageServer for Backend {
11551213
11561214 // Clears the diagnostics for open buffers (without holding lock)
11571215 for uri in & uris {
1216+ self . abort_pending_diagnostics ( uri) . await ;
11581217 let result = PublishDiagnosticsParams {
11591218 uri : uri. clone ( ) ,
11601219 diagnostics : vec ! [ ] ,
0 commit comments