@@ -379,6 +379,7 @@ impl Ghci {
379379 let mut needs_restart = Vec :: new ( ) ;
380380 let mut needs_reload = Vec :: new ( ) ;
381381 let mut needs_add = Vec :: new ( ) ;
382+ let mut needs_remove = Vec :: new ( ) ;
382383 for event in events {
383384 let path = event. as_path ( ) ;
384385 let path = self . relative_path ( path) ?;
@@ -395,7 +396,7 @@ impl Ghci {
395396 ) ;
396397
397398 // Don't restart if we've explicitly ignored this path in a glob.
398- if ( !restart_match. is_ignore ( )
399+ if !restart_match. is_ignore ( )
399400 // Restart on `.cabal` and `.ghci` files.
400401 && ( path
401402 . extension ( )
@@ -406,31 +407,21 @@ impl Ghci {
406407 . map ( |name| name == ".ghci" )
407408 . unwrap_or ( false )
408409 // Restart on explicit restart globs.
409- || restart_match. is_whitelist ( ) ) )
410- // Even if we've explicitly ignored this path in a glob, `ghci` can't cope with
411- // removed modules, so we need to restart when modules are removed or renamed.
412- //
413- // See: https://gitlab.haskell.org/ghc/ghc/-/issues/11596
414- //
415- // TODO: I should investigate if `:unadd` works for some classes of removed
416- // modules.
417- || ( matches ! ( event, FileEvent :: Remove ( _) )
418- && path_is_haskell_source_file
419- && self . targets . contains_source_path ( & path) )
410+ || restart_match. is_whitelist ( ) )
420411 {
421412 // Restart for this path.
422413 tracing:: debug!( %path, "Needs restart" ) ;
423414 needs_restart. push ( path) ;
424- } else if reload_match. is_whitelist ( ) {
425- // Extra extensions are always reloaded, never added.
426- tracing:: debug!( %path, "Needs reload" ) ;
427- needs_reload. push ( path) ;
428- } else if !reload_match. is_ignore ( )
429- // Don't reload if we've explicitly ignored this path in a glob.
430- // Otherwise, reload when Haskell files are modified.
431- && matches ! ( event, FileEvent :: Modify ( _) )
415+ } else if reload_match. is_ignore ( ) {
416+ // Ignoring this path, continue.
417+ } else if matches ! ( event, FileEvent :: Remove ( _) )
432418 && path_is_haskell_source_file
419+ && self . targets . contains_source_path ( & path)
433420 {
421+ tracing:: debug!( %path, "Needs remove" ) ;
422+ needs_remove. push ( path) ;
423+ } else if matches ! ( event, FileEvent :: Modify ( _) ) && path_is_haskell_source_file {
424+ // Otherwise, reload when Haskell files are modified.
434425 if self . targets . contains_source_path ( & path) {
435426 // We can `:reload` paths in the target set.
436427 tracing:: debug!( %path, "Needs reload" ) ;
@@ -440,13 +431,18 @@ impl Ghci {
440431 tracing:: debug!( %path, "Needs add" ) ;
441432 needs_add. push ( path) ;
442433 }
434+ } else if reload_match. is_whitelist ( ) {
435+ // Extra extensions are always reloaded, never added.
436+ tracing:: debug!( %path, "Needs reload" ) ;
437+ needs_reload. push ( path) ;
443438 }
444439 }
445440
446441 Ok ( ReloadActions {
447442 needs_restart,
448443 needs_reload,
449444 needs_add,
445+ needs_remove,
450446 } )
451447 }
452448
@@ -480,20 +476,26 @@ impl Ghci {
480476
481477 let mut log = CompilationLog :: default ( ) ;
482478
483- if actions. needs_add_or_reload ( ) {
479+ if actions. needs_modify ( ) {
484480 self . opts . clear ( ) ;
485481 self . run_hooks ( LifecycleEvent :: Reload ( hooks:: When :: Before ) , & mut log)
486482 . await ?;
487483 }
488484
485+ if !actions. needs_remove . is_empty ( ) {
486+ tracing:: info!(
487+ "Removing modules from ghci:\n {}" ,
488+ format_bulleted_list( & actions. needs_remove)
489+ ) ;
490+ self . remove_modules ( & actions. needs_remove , & mut log) . await ?;
491+ }
492+
489493 if !actions. needs_add . is_empty ( ) {
490494 tracing:: info!(
491495 "Adding modules to ghci:\n {}" ,
492496 format_bulleted_list( & actions. needs_add)
493497 ) ;
494- for path in & actions. needs_add {
495- self . add_module ( path, & mut log) . await ?;
496- }
498+ self . add_modules ( & actions. needs_add , & mut log) . await ?;
497499 }
498500
499501 if !actions. needs_reload . is_empty ( ) {
@@ -506,7 +508,7 @@ impl Ghci {
506508 . await ?;
507509 }
508510
509- if actions. needs_add_or_reload ( ) {
511+ if actions. needs_modify ( ) {
510512 self . finish_compilation (
511513 start_instant,
512514 & mut log,
@@ -641,6 +643,21 @@ impl Ghci {
641643 Ok ( ( ) )
642644 }
643645
646+ /// Remove all `eval_commands` for the given paths.
647+ #[ instrument( skip_all, level = "debug" ) ]
648+ async fn clear_eval_commands_for_paths (
649+ & mut self ,
650+ paths : impl IntoIterator < Item = impl Borrow < NormalPath > > ,
651+ ) {
652+ if !self . opts . enable_eval {
653+ return ;
654+ }
655+
656+ for path in paths {
657+ self . eval_commands . remove ( path. borrow ( ) ) ;
658+ }
659+ }
660+
644661 /// Read and parse eval commands from the given `path`.
645662 #[ instrument( level = "trace" ) ]
646663 async fn parse_eval_commands ( path : & Utf8Path ) -> miette:: Result < Vec < EvalCommand > > {
@@ -653,38 +670,32 @@ impl Ghci {
653670 Ok ( commands)
654671 }
655672
656- /// `:add` a module to the `ghci` session by path.
657- ///
658- /// Optionally returns a compilation result.
673+ /// `:add` a module or modules to the `ghci` session by path.
659674 #[ instrument( skip( self ) , level = "debug" ) ]
660- async fn add_module (
675+ async fn add_modules (
661676 & mut self ,
662- path : & NormalPath ,
677+ paths : & [ NormalPath ] ,
663678 log : & mut CompilationLog ,
664679 ) -> miette:: Result < ( ) > {
665- if self . targets . contains_source_path ( path. absolute ( ) ) {
666- tracing:: debug!( %path, "Skipping `:add`ing already-loaded path" ) ;
667- return Ok ( ( ) ) ;
668- }
680+ let modules = self . targets . format_modules ( & self . search_paths , paths) ?;
669681
670682 self . stdin
671- . add_module ( & mut self . stdout , path . relative ( ) , log)
683+ . add_modules ( & mut self . stdout , & modules , log)
672684 . await ?;
673685
674- self . targets
675- . insert_source_path ( path. clone ( ) , TargetKind :: Path ) ;
686+ for path in paths {
687+ self . targets
688+ . insert_source_path ( path. clone ( ) , TargetKind :: Path ) ;
689+ }
676690
677- self . refresh_eval_commands_for_paths ( std:: iter:: once ( path) )
678- . await ?;
691+ self . refresh_eval_commands_for_paths ( paths) . await ?;
679692
680693 Ok ( ( ) )
681694 }
682695
683696 /// `:add *` a module to the `ghci` session by path.
684697 ///
685698 /// This forces it to be interpreted.
686- ///
687- /// Optionally returns a compilation result.
688699 #[ instrument( skip( self ) , level = "debug" ) ]
689700 async fn interpret_module (
690701 & mut self ,
@@ -707,6 +718,31 @@ impl Ghci {
707718 Ok ( ( ) )
708719 }
709720
721+ /// `:unadd` a module or modules from the `ghci` session by path.
722+ #[ instrument( skip( self ) , level = "debug" ) ]
723+ async fn remove_modules (
724+ & mut self ,
725+ paths : & [ NormalPath ] ,
726+ log : & mut CompilationLog ,
727+ ) -> miette:: Result < ( ) > {
728+ // Each `:unadd` implicitly reloads as well, so we have to `:unadd` all the modules in a
729+ // single command so that GHCi doesn't try to load a bunch of removed modules after each
730+ // one.
731+ let modules = self . targets . format_modules ( & self . search_paths , paths) ?;
732+
733+ self . stdin
734+ . remove_modules ( & mut self . stdout , & modules, log)
735+ . await ?;
736+
737+ for path in paths {
738+ self . targets . remove_source_path ( path) ;
739+ self . clear_eval_commands_for_paths ( std:: iter:: once ( path) )
740+ . await ;
741+ }
742+
743+ Ok ( ( ) )
744+ }
745+
710746 /// Stop this `ghci` session and cancel the async tasks associated with it.
711747 #[ instrument( skip_all, level = "debug" ) ]
712748 async fn stop ( & mut self ) -> miette:: Result < ( ) > {
@@ -851,12 +887,14 @@ struct ReloadActions {
851887 needs_reload : Vec < NormalPath > ,
852888 /// Paths to modules which need an `:add`.
853889 needs_add : Vec < NormalPath > ,
890+ /// Paths to modules which need an `:unadd`.
891+ needs_remove : Vec < NormalPath > ,
854892}
855893
856894impl ReloadActions {
857- /// Do any modules need to be added or reloaded?
858- fn needs_add_or_reload ( & self ) -> bool {
859- !self . needs_add . is_empty ( ) || !self . needs_reload . is_empty ( )
895+ /// Do any modules need to be added, removed, or reloaded?
896+ fn needs_modify ( & self ) -> bool {
897+ !self . needs_add . is_empty ( ) || !self . needs_reload . is_empty ( ) || ! self . needs_remove . is_empty ( )
860898 }
861899
862900 /// Is a session restart needed?
@@ -868,7 +906,7 @@ impl ReloadActions {
868906 fn kind ( & self ) -> GhciReloadKind {
869907 if self . needs_restart ( ) {
870908 GhciReloadKind :: Restart
871- } else if self . needs_add_or_reload ( ) {
909+ } else if self . needs_modify ( ) {
872910 GhciReloadKind :: Reload
873911 } else {
874912 GhciReloadKind :: None
@@ -881,7 +919,7 @@ impl ReloadActions {
881919pub enum GhciReloadKind {
882920 /// Noop. No actions needed.
883921 None ,
884- /// Reload and/or add modules. Can be interrupted.
922+ /// Reload, add, and/or remove modules. Can be interrupted.
885923 Reload ,
886924 /// Restart the whole session. Cannot be interrupted.
887925 Restart ,
0 commit comments