99import typing as t
1010
1111from libtmux ._internal .query_list import ObjectDoesNotExist
12+ from libtmux .constants import OptionScope
1213from libtmux .pane import Pane
1314from libtmux .server import Server
1415from libtmux .session import Session
3233SYNCHRONIZE_PANES_OPTION = "synchronize-panes"
3334SYNCHRONIZE_PANES_FINAL_OPTION = "_synchronize_panes"
3435SYNCHRONIZE_PANES_FORCED_OFF_OPTION = "_synchronize_panes_forced_off"
36+ SYNCHRONIZE_PANES_PANE_RESTORE_OPTION = "_synchronize_panes_pane_restore"
3537SYNCHRONIZE_PANES_RESTORE_OPTION = "_synchronize_panes_restore"
3638_MISSING = object ()
3739
@@ -619,17 +621,24 @@ def build(self, session: Session | None = None, append: bool = False) -> None:
619621 self .prepare_window_synchronize_panes (window , window_config )
620622
621623 focus_pane = None
622- for pane , pane_config in self .iter_create_panes (window , window_config ):
623- assert isinstance (pane , Pane )
624- pane = pane
624+ try :
625+ for pane , pane_config in self .iter_create_panes (window , window_config ):
626+ assert isinstance (pane , Pane )
627+ pane = pane
625628
626- if pane_config .get ("focus" ):
627- focus_pane = pane
629+ if pane_config .get ("focus" ):
630+ focus_pane = pane
628631
629- if window_config .get ("focus" ):
630- focus = window
632+ if window_config .get ("focus" ):
633+ focus = window
631634
632- self .config_after_window (window , window_config )
635+ self .config_after_window (window , window_config )
636+ finally :
637+ self .restore_window_synchronize_panes (
638+ window ,
639+ window_config ,
640+ apply_options_after = False ,
641+ )
633642
634643 for plugin in self .plugins :
635644 plugin .after_window_finished (window )
@@ -763,7 +772,7 @@ def prepare_window_synchronize_panes(
763772 Examples
764773 --------
765774 >>> cfg = {}
766- >>> builder = WorkspaceBuilder (
775+ >>> builder = ClassicWorkspaceBuilder (
767776 ... session_config={"session_name": session.name, "windows": []},
768777 ... server=session.server,
769778 ... )
@@ -788,17 +797,38 @@ def prepare_window_synchronize_panes(
788797 window .set_option (SYNCHRONIZE_PANES_OPTION , False )
789798 window_config [SYNCHRONIZE_PANES_FORCED_OFF_OPTION ] = True
790799
800+ pane_restores = []
801+ for pane in window .panes :
802+ local_pane_sync = pane .show_option (
803+ SYNCHRONIZE_PANES_OPTION ,
804+ scope = OptionScope .Pane ,
805+ )
806+ if local_pane_sync is True :
807+ pane_restores .append ((pane , local_pane_sync ))
808+ pane .set_option (
809+ SYNCHRONIZE_PANES_OPTION ,
810+ False ,
811+ scope = OptionScope .Pane ,
812+ ignore_errors = True ,
813+ )
814+
815+ if pane_restores :
816+ window_config [SYNCHRONIZE_PANES_PANE_RESTORE_OPTION ] = pane_restores
817+ window_config [SYNCHRONIZE_PANES_FORCED_OFF_OPTION ] = True
818+
791819 def restore_window_synchronize_panes (
792820 self ,
793821 window : Window ,
794822 window_config : dict [str , t .Any ],
823+ * ,
824+ apply_options_after : bool = True ,
795825 ) -> None :
796826 """Restore the configured final tmux pane synchronization state.
797827
798828 Examples
799829 --------
800830 >>> cfg = {"options_after": {"synchronize-panes": "off"}}
801- >>> builder = WorkspaceBuilder (
831+ >>> builder = ClassicWorkspaceBuilder (
802832 ... session_config={"session_name": session.name, "windows": []},
803833 ... server=session.server,
804834 ... )
@@ -809,26 +839,49 @@ def restore_window_synchronize_panes(
809839 False
810840 >>> _ = scratch.kill()
811841 """
812- after_sync = _get_window_option_value (
813- window_config ,
814- "options_after" ,
815- SYNCHRONIZE_PANES_OPTION ,
816- )
817- if after_sync is not _MISSING :
818- window .set_option (SYNCHRONIZE_PANES_OPTION , after_sync )
819- return
820-
821- if not window_config .get (SYNCHRONIZE_PANES_FORCED_OFF_OPTION ):
822- return
842+ after_sync = _MISSING
843+ if apply_options_after :
844+ after_sync = _get_window_option_value (
845+ window_config ,
846+ "options_after" ,
847+ SYNCHRONIZE_PANES_OPTION ,
848+ )
823849
824- restore_sync = window_config .get (
850+ pane_restores = window_config .pop (
851+ SYNCHRONIZE_PANES_PANE_RESTORE_OPTION ,
852+ [],
853+ )
854+ forced_off = window_config .pop (SYNCHRONIZE_PANES_FORCED_OFF_OPTION , False )
855+ restore_sync = window_config .pop (
825856 SYNCHRONIZE_PANES_RESTORE_OPTION ,
826857 _MISSING ,
827858 )
828- if restore_sync is _MISSING or restore_sync is None :
829- window .unset_option (SYNCHRONIZE_PANES_OPTION , ignore_errors = True )
830- else :
831- window .set_option (SYNCHRONIZE_PANES_OPTION , restore_sync )
859+
860+ if after_sync is not _MISSING :
861+ window .set_option (SYNCHRONIZE_PANES_OPTION , t .cast (int | str , after_sync ))
862+ elif forced_off and restore_sync is not _MISSING :
863+ if restore_sync is None :
864+ window .unset_option (SYNCHRONIZE_PANES_OPTION , ignore_errors = True )
865+ else :
866+ window .set_option (
867+ SYNCHRONIZE_PANES_OPTION ,
868+ t .cast (int | str , restore_sync ),
869+ )
870+
871+ for pane , restore_pane_sync in pane_restores :
872+ if restore_pane_sync is _MISSING or restore_pane_sync is None :
873+ pane .unset_option (
874+ SYNCHRONIZE_PANES_OPTION ,
875+ scope = OptionScope .Pane ,
876+ ignore_errors = True ,
877+ )
878+ else :
879+ pane .set_option (
880+ SYNCHRONIZE_PANES_OPTION ,
881+ t .cast (int | str , restore_pane_sync ),
882+ scope = OptionScope .Pane ,
883+ ignore_errors = True ,
884+ )
832885
833886 def iter_create_panes (
834887 self ,
0 commit comments