@@ -746,6 +746,7 @@ pub struct Server {
746746 /// should be mapped through here in case they correspond to a cell.
747747 open_notebook_cells : RwLock < HashMap < Url , PathBuf > > ,
748748 open_files : RwLock < HashMap < PathBuf , Arc < LspFile > > > ,
749+ open_files_with_unsaved_changes : Mutex < HashSet < PathBuf > > ,
749750 /// Last published fingerprint for unversioned file-backed workspace diagnostics.
750751 published_workspace_diagnostics : Mutex < HashMap < Url , u64 > > ,
751752 /// Tracks URIs (including virtual/untitled ones) to synthetic on-disk paths so we can
@@ -2416,6 +2417,7 @@ impl Server {
24162417 state : State :: new ( config_finder, thread_count) ,
24172418 open_notebook_cells : RwLock :: new ( HashMap :: new ( ) ) ,
24182419 open_files : RwLock :: new ( HashMap :: new ( ) ) ,
2420+ open_files_with_unsaved_changes : Mutex :: new ( HashSet :: new ( ) ) ,
24192421 published_workspace_diagnostics : Mutex :: new ( HashMap :: new ( ) ) ,
24202422 unsaved_file_tracker : UnsavedFileTracker :: new ( ) ,
24212423 indexed_configs : Mutex :: new ( HashSet :: new ( ) ) ,
@@ -3285,6 +3287,7 @@ impl Server {
32853287
32863288 fn did_save ( & self , url : Url ) {
32873289 if let Some ( path) = self . path_for_uri ( & url) {
3290+ self . open_files_with_unsaved_changes . lock ( ) . remove ( & path) ;
32883291 self . invalidate ( TelemetryEventKind :: InvalidateDisk , move |t| {
32893292 t. invalidate_disk ( & [ path] )
32903293 } )
@@ -3322,6 +3325,7 @@ impl Server {
33223325 } else {
33233326 None
33243327 } ;
3328+ self . open_files_with_unsaved_changes . lock ( ) . remove ( & path) ;
33253329 self . version_info . lock ( ) . insert ( path. clone ( ) , version) ;
33263330 self . open_files . write ( ) . insert ( path. clone ( ) , contents) ;
33273331 self . queue_source_db_rebuild_and_recheck ( telemetry, telemetry_event, false ) ;
@@ -3387,6 +3391,9 @@ impl Server {
33873391 params. content_changes ,
33883392 ) ) ) ;
33893393 drop ( lock) ;
3394+ self . open_files_with_unsaved_changes
3395+ . lock ( )
3396+ . insert ( file_path. clone ( ) ) ;
33903397 // Update version_info only after the mutation has fully succeeded.
33913398 self . version_info . lock ( ) . insert ( file_path. clone ( ) , version) ;
33923399 if !subsequent_mutation {
@@ -3549,6 +3556,9 @@ impl Server {
35493556 let new_notebook = Arc :: new ( LspNotebook :: new ( ruff_notebook, notebook_document) ) ;
35503557 * original = Arc :: new ( LspFile :: Notebook ( new_notebook) ) ;
35513558 drop ( lock) ;
3559+ self . open_files_with_unsaved_changes
3560+ . lock ( )
3561+ . insert ( file_path. clone ( ) ) ;
35523562 // Update version_info only after the mutation has fully succeeded, so
35533563 // that on error the version stays at the old value and subsequent
35543564 // notifications operate against consistent state.
@@ -3586,6 +3596,28 @@ impl Server {
35863596 config_changed || files_added_or_removed
35873597 }
35883598
3599+ fn refresh_clean_open_files_from_disk ( & self , events : & CategorizedEvents ) {
3600+ let unsaved = self . open_files_with_unsaved_changes . lock ( ) . clone ( ) ;
3601+ let mut open_files = self . open_files . write ( ) ;
3602+ for path in events. modified . iter ( ) {
3603+ if unsaved. contains ( path) {
3604+ continue ;
3605+ }
3606+ let Some ( open_file) = open_files. get_mut ( path) else {
3607+ continue ;
3608+ } ;
3609+ let LspFile :: Source ( current_contents) = open_file. as_ref ( ) else {
3610+ continue ;
3611+ } ;
3612+ let Ok ( updated_contents) = std:: fs:: read_to_string ( path) else {
3613+ continue ;
3614+ } ;
3615+ if current_contents. as_str ( ) != updated_contents {
3616+ * open_file = Arc :: new ( LspFile :: from_source ( updated_contents) ) ;
3617+ }
3618+ }
3619+ }
3620+
35893621 fn did_change_watched_files (
35903622 & self ,
35913623 params : DidChangeWatchedFilesParams ,
@@ -3621,6 +3653,8 @@ impl Server {
36213653
36223654 let should_requery_build_system = should_requery_build_system ( & events) ;
36233655
3656+ self . refresh_clean_open_files_from_disk ( & events) ;
3657+
36243658 // Rewatch files if necessary (config changed, files added/removed, etc.)
36253659 if Self :: should_rewatch ( & events) {
36263660 info ! ( "[Pyrefly] Re-registering file watchers" ) ;
@@ -3710,6 +3744,7 @@ impl Server {
37103744 } ,
37113745 }
37123746 drop ( open_files) ;
3747+ self . open_files_with_unsaved_changes . lock ( ) . remove ( & path) ;
37133748 self . unsaved_file_tracker . forget_uri_path ( & url) ;
37143749 self . queue_source_db_rebuild_and_recheck ( telemetry, telemetry_event, false ) ;
37153750 self . recheck_queue . queue_task (
0 commit comments