66
77using Avalonia ;
88using Avalonia . Controls ;
9+ using Avalonia . Data ;
910using Avalonia . Input ;
1011using Avalonia . Interactivity ;
1112using Avalonia . Media ;
@@ -184,11 +185,6 @@ public int CurrentConflictEndLine
184185 TextArea . TextView . LineTransformers . Add ( new MergeDiffIndicatorTransformer ( this ) ) ;
185186 }
186187
187- public ScrollViewer GetScrollViewer ( )
188- {
189- return _scrollViewer ;
190- }
191-
192188 protected override void OnLoaded ( RoutedEventArgs e )
193189 {
194190 base . OnLoaded ( e ) ;
@@ -198,36 +194,17 @@ protected override void OnLoaded(RoutedEventArgs e)
198194 Models . TextMateHelper . SetGrammarByFileName ( _textMate , FileName ) ;
199195
200196 TextArea . TextView . ContextRequested += OnTextViewContextRequested ;
201-
202- _scrollViewer = this . FindDescendantOfType < ScrollViewer > ( ) ;
203- if ( _scrollViewer != null )
204- {
205- _scrollViewer . ScrollChanged += OnScrollViewerScrollChanged ;
206- _scrollViewer . Bind ( ScrollViewer . OffsetProperty , new Avalonia . Data . Binding ( "ScrollOffset" , Avalonia . Data . BindingMode . OneWay ) ) ;
207- }
208197 }
209198
210- private void OnScrollViewerScrollChanged ( object sender , ScrollChangedEventArgs e )
199+ public ScrollViewer GetScrollViewer ( )
211200 {
212- if ( _scrollViewer == null || DataContext is not ViewModels . MergeConflictEditor vm )
213- return ;
214-
215- if ( vm . ScrollOffset . NearlyEquals ( _scrollViewer . Offset ) )
216- return ;
217-
218- if ( IsPointerOver || e . OffsetDelta . SquaredLength > 1.0f )
219- {
220- vm . ScrollOffset = _scrollViewer . Offset ;
221- }
201+ _scrollViewer ??= this . FindDescendantOfType < ScrollViewer > ( ) ;
202+ return _scrollViewer ;
222203 }
223204
224205 protected override void OnUnloaded ( RoutedEventArgs e )
225206 {
226- if ( _scrollViewer != null )
227- {
228- _scrollViewer . ScrollChanged -= OnScrollViewerScrollChanged ;
229- _scrollViewer = null ;
230- }
207+ _scrollViewer = null ;
231208
232209 TextArea . TextView . ContextRequested -= OnTextViewContextRequested ;
233210
@@ -606,12 +583,68 @@ protected override void OnOpened(EventArgs e)
606583 _theirsPresenter = this . FindControl < MergeDiffPresenter > ( "TheirsPresenter" ) ;
607584 _resultPresenter = this . FindControl < MergeDiffPresenter > ( "ResultPresenter" ) ;
608585
586+ // Set up scroll synchronization
587+ SetupScrollSync ( ) ;
588+
609589 if ( DataContext is ViewModels . MergeConflictEditor vm )
610590 {
611591 vm . PropertyChanged += OnViewModelPropertyChanged ;
612592 }
613593 }
614594
595+ private void SetupScrollSync ( )
596+ {
597+ var oursScroll = _oursPresenter ? . GetScrollViewer ( ) ;
598+ var theirsScroll = _theirsPresenter ? . GetScrollViewer ( ) ;
599+ var resultScroll = _resultPresenter ? . GetScrollViewer ( ) ;
600+
601+ if ( oursScroll != null )
602+ oursScroll . ScrollChanged += OnPanelScrollChanged ;
603+ if ( theirsScroll != null )
604+ theirsScroll . ScrollChanged += OnPanelScrollChanged ;
605+ if ( resultScroll != null )
606+ resultScroll . ScrollChanged += OnPanelScrollChanged ;
607+ }
608+
609+ private void OnPanelScrollChanged ( object sender , ScrollChangedEventArgs e )
610+ {
611+ if ( _isSyncingScroll || sender is not ScrollViewer source )
612+ return ;
613+
614+ // Only sync if this panel initiated the scroll (pointer is over it or significant delta)
615+ var presenter = source . FindAncestorOfType < MergeDiffPresenter > ( ) ;
616+ if ( presenter == null )
617+ return ;
618+
619+ if ( ! presenter . IsPointerOver && e . OffsetDelta . SquaredLength <= 1.0f )
620+ return ;
621+
622+ _isSyncingScroll = true ;
623+ try
624+ {
625+ var offset = source . Offset ;
626+
627+ var oursScroll = _oursPresenter ? . GetScrollViewer ( ) ;
628+ var theirsScroll = _theirsPresenter ? . GetScrollViewer ( ) ;
629+ var resultScroll = _resultPresenter ? . GetScrollViewer ( ) ;
630+
631+ if ( oursScroll != null && oursScroll != source )
632+ oursScroll . Offset = offset ;
633+ if ( theirsScroll != null && theirsScroll != source )
634+ theirsScroll . Offset = offset ;
635+ if ( resultScroll != null && resultScroll != source )
636+ resultScroll . Offset = offset ;
637+
638+ // Update ViewModel
639+ if ( DataContext is ViewModels . MergeConflictEditor vm )
640+ vm . ScrollOffset = offset ;
641+ }
642+ finally
643+ {
644+ _isSyncingScroll = false ;
645+ }
646+ }
647+
615648 private void OnViewModelPropertyChanged ( object sender , System . ComponentModel . PropertyChangedEventArgs e )
616649 {
617650 if ( e . PropertyName == nameof ( ViewModels . MergeConflictEditor . IsLoading ) )
@@ -694,12 +727,6 @@ protected override async void OnClosing(WindowClosingEventArgs e)
694727 }
695728 }
696729
697- protected override void OnClosed ( EventArgs e )
698- {
699- base . OnClosed ( e ) ;
700- GC . Collect ( ) ;
701- }
702-
703730 protected override void OnKeyDown ( KeyEventArgs e )
704731 {
705732 base . OnKeyDown ( e ) ;
@@ -855,12 +882,52 @@ private void ScrollToCurrentConflict()
855882 var lineHeight = _oursPresenter . TextArea . TextView . DefaultLineHeight ;
856883 var vOffset = lineHeight * vm . CurrentConflictLine ;
857884 var targetOffset = new Vector ( 0 , Math . Max ( 0 , vOffset - _oursPresenter . Bounds . Height * 0.3 ) ) ;
858- vm . ScrollOffset = targetOffset ;
885+
886+ // Sync all panels to this offset
887+ _isSyncingScroll = true ;
888+ try
889+ {
890+ var oursScroll = _oursPresenter ? . GetScrollViewer ( ) ;
891+ var theirsScroll = _theirsPresenter ? . GetScrollViewer ( ) ;
892+ var resultScroll = _resultPresenter ? . GetScrollViewer ( ) ;
893+
894+ if ( oursScroll != null )
895+ oursScroll . Offset = targetOffset ;
896+ if ( theirsScroll != null )
897+ theirsScroll . Offset = targetOffset ;
898+ if ( resultScroll != null )
899+ resultScroll . Offset = targetOffset ;
900+
901+ vm . ScrollOffset = targetOffset ;
902+ }
903+ finally
904+ {
905+ _isSyncingScroll = false ;
906+ }
859907 }
860908 }
861909 }
862910
911+ protected override void OnClosed ( EventArgs e )
912+ {
913+ // Clean up scroll handlers
914+ var oursScroll = _oursPresenter ? . GetScrollViewer ( ) ;
915+ var theirsScroll = _theirsPresenter ? . GetScrollViewer ( ) ;
916+ var resultScroll = _resultPresenter ? . GetScrollViewer ( ) ;
917+
918+ if ( oursScroll != null )
919+ oursScroll . ScrollChanged -= OnPanelScrollChanged ;
920+ if ( theirsScroll != null )
921+ theirsScroll . ScrollChanged -= OnPanelScrollChanged ;
922+ if ( resultScroll != null )
923+ resultScroll . ScrollChanged -= OnPanelScrollChanged ;
924+
925+ base . OnClosed ( e ) ;
926+ GC . Collect ( ) ;
927+ }
928+
863929 private bool _forceClose = false ;
930+ private bool _isSyncingScroll = false ;
864931 private MergeDiffPresenter _oursPresenter ;
865932 private MergeDiffPresenter _theirsPresenter ;
866933 private MergeDiffPresenter _resultPresenter ;
0 commit comments