Files
ClaudeDo/src/ClaudeDo.Ui/Views/Conflicts/ConflictResolverView.axaml
Mika Kuns c4d1acc75b feat(merge): Rider-style 3-pane conflict editor view
Replace the Base|Ours|Theirs read-only columns + single-conflict result with a
whole-file 3-pane editor: Ours (read-only) | editable Result | Theirs (read-only),
reconstructed from the active file's segments so the panes line up on stable text.

- IBackgroundRenderer paints each conflict block (unresolved=blood, resolved=green)
  across all three panes.
- Result document edits are gated by an IReadOnlySectionProvider (stable text is
  read-only; only conflict regions, tracked via TextAnchors, are editable); edits
  flow back to the owning block.
- Between-pane gutters host inline accept controls (>/< ) positioned per conflict;
  click accepts ours/theirs into the result.
- Proportional synced vertical scroll across the panes; file switcher + change-nav
  arrows (F8 / Shift+F8); active-file 'M conflicts - K resolved' readout.
- Merge block tints + AmberBrush tokens; en/de keys for the new labels.

Seam unchanged. App builds; Ui.Tests 128, Localization.Tests 16.
2026-06-19 10:15:12 +02:00

167 lines
8.0 KiB
XML

<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Conflicts"
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
xmlns:ae="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
xmlns:loc="using:ClaudeDo.Ui.Localization"
x:DataType="vm:ConflictResolverViewModel"
x:Class="ClaudeDo.Ui.Views.Conflicts.ConflictResolverView"
Title="{loc:Tr conflictResolver.windowTitle}"
Width="1280" Height="820" MinWidth="960" MinHeight="560"
CanResize="True"
WindowDecorations="BorderOnly"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaTitleBarHeightHint="-1"
WindowStartupLocation="CenterOwner"
Background="{DynamicResource SurfaceBrush}">
<Window.KeyBindings>
<KeyBinding Gesture="Escape" Command="{Binding AbortCommand}"/>
<KeyBinding Gesture="F8" Command="{Binding NextCommand}"/>
<KeyBinding Gesture="Shift+F8" Command="{Binding PreviousCommand}"/>
</Window.KeyBindings>
<Window.Styles>
<Style Selector="ae|TextEditor">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
<Setter Property="Background" Value="{DynamicResource Surface2Brush}" />
<Setter Property="Padding" Value="4,2" />
<Setter Property="WordWrap" Value="False" />
</Style>
<Style Selector="Border.col-head">
<Setter Property="Padding" Value="8,4" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="BorderBrush" Value="{DynamicResource LineBrush}" />
<Setter Property="Background" Value="{DynamicResource Surface2Brush}" />
</Style>
<Style Selector="Border.pane">
<Setter Property="BorderBrush" Value="{DynamicResource LineBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="ClipToBounds" Value="True" />
</Style>
<!-- Inline accept controls in the between-pane gutters -->
<Style Selector="Button.accept-gutter">
<Setter Property="Width" Value="22" />
<Setter Property="Height" Value="20" />
<Setter Property="Padding" Value="0" />
<Setter Property="FontSize" Value="13" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background" Value="{DynamicResource Surface2Brush}" />
<Setter Property="BorderBrush" Value="{DynamicResource MergeConflictEdgeBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Foreground" Value="{DynamicResource TextBrush}" />
</Style>
<Style Selector="Button.accept-gutter:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource MergeConflictTintBrush}" />
</Style>
</Window.Styles>
<ctl:ModalShell Title="{loc:Tr conflictResolver.modalTitle}" CloseCommand="{Binding AbortCommand}">
<ctl:ModalShell.Footer>
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Classes="meta" VerticalAlignment="Center"
Foreground="{DynamicResource BloodBrush}"
Text="{Binding ContinueHint}"
IsVisible="{Binding HasBinaryFiles}"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
<Button Classes="btn accent" Content="{loc:Tr conflictResolver.continue}"
Command="{Binding ContinueCommand}" IsEnabled="{Binding CanContinue}"/>
<Button Classes="btn" Content="{loc:Tr conflictResolver.abort}" Command="{Binding AbortCommand}"/>
</StackPanel>
</Grid>
</ctl:ModalShell.Footer>
<Grid Margin="14,10" RowDefinitions="Auto,Auto,Auto,*">
<!-- Busy / error -->
<TextBlock Grid.Row="0" Classes="meta" Margin="0,0,0,6"
Text="{loc:Tr conflictResolver.loading}" IsVisible="{Binding IsBusy}"/>
<TextBlock Grid.Row="0" Classes="meta" Foreground="{DynamicResource BloodBrush}"
Text="{Binding Error}" TextWrapping="Wrap"
IsVisible="{Binding Error, Converter={x:Static ObjectConverters.IsNotNull}}"/>
<!-- Binary-conflict banner -->
<Border Grid.Row="1" Margin="0,0,0,8" Padding="10,7" CornerRadius="6"
Background="{DynamicResource ErrorTintBrush}"
BorderBrush="{DynamicResource BloodBrush}" BorderThickness="1"
IsVisible="{Binding HasBinaryFiles}">
<StackPanel Spacing="3">
<TextBlock Classes="meta" Foreground="{DynamicResource BloodBrush}"
Text="{loc:Tr conflictResolver.binaryHint}"/>
<ItemsControl ItemsSource="{Binding BinaryFilePaths}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Classes="path-mono" Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
<!-- Toolbar: change nav · file switcher · readout -->
<Grid Grid.Row="2" ColumnDefinitions="Auto,Auto,Auto,*,Auto" Margin="0,0,0,8"
IsVisible="{Binding HasCurrent}">
<Button Grid.Column="0" Classes="btn" Content="↑" Margin="0,0,4,0" Padding="10,4"
ToolTip.Tip="{loc:Tr conflictResolver.prevConflict}"
Command="{Binding PreviousCommand}"/>
<Button Grid.Column="1" Classes="btn" Content="↓" Margin="0,0,12,0" Padding="10,4"
ToolTip.Tip="{loc:Tr conflictResolver.nextConflict}"
Command="{Binding NextCommand}"/>
<ComboBox Grid.Column="2" MinWidth="240" MaxWidth="520"
ItemsSource="{Binding Files}"
SelectedItem="{Binding ActiveFile, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:MergeFile">
<TextBlock Classes="path-mono" Text="{Binding Path}" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Column="4" Classes="meta" VerticalAlignment="Center"
Foreground="{DynamicResource TextDimBrush}"
Text="{Binding PositionText}"/>
</Grid>
<!-- Three panes: Ours | (gutter) | Result | (gutter) | Theirs -->
<Grid Grid.Row="3" ColumnDefinitions="*,26,*,26,*" IsVisible="{Binding HasCurrent}">
<Border Grid.Column="0" Classes="pane">
<DockPanel>
<Border Classes="col-head" DockPanel.Dock="Top">
<TextBlock Classes="eyebrow" Text="{loc:Tr conflictResolver.ours}"
Foreground="{DynamicResource MossBrush}"/>
</Border>
<ae:TextEditor Name="OursEditor" IsReadOnly="True" ShowLineNumbers="True"/>
</DockPanel>
</Border>
<Canvas Grid.Column="1" Name="LeftGutter" Background="Transparent"/>
<Border Grid.Column="2" Classes="pane">
<DockPanel>
<Border Classes="col-head" DockPanel.Dock="Top">
<TextBlock Classes="eyebrow" Text="{loc:Tr conflictResolver.result}"/>
</Border>
<ae:TextEditor Name="ResultEditor" ShowLineNumbers="True"/>
</DockPanel>
</Border>
<Canvas Grid.Column="3" Name="RightGutter" Background="Transparent"/>
<Border Grid.Column="4" Classes="pane">
<DockPanel>
<Border Classes="col-head" DockPanel.Dock="Top">
<TextBlock Classes="eyebrow" Text="{loc:Tr conflictResolver.theirs}"
Foreground="{DynamicResource AmberBrush}"/>
</Border>
<ae:TextEditor Name="TheirsEditor" IsReadOnly="True" ShowLineNumbers="True"/>
</DockPanel>
</Border>
</Grid>
</Grid>
</ctl:ModalShell>
</Window>