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.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
x:DataType="vm:ConflictResolverViewModel"
|
||||
x:Class="ClaudeDo.Ui.Views.Conflicts.ConflictResolverView"
|
||||
Title="{loc:Tr conflictResolver.windowTitle}"
|
||||
Width="1120" Height="760" MinWidth="840" MinHeight="540"
|
||||
Width="1280" Height="820" MinWidth="960" MinHeight="560"
|
||||
CanResize="True"
|
||||
WindowDecorations="BorderOnly"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
<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>
|
||||
@@ -24,13 +26,38 @@
|
||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
||||
<Setter Property="Background" Value="{DynamicResource Surface2Brush}" />
|
||||
<Setter Property="Padding" Value="6,4" />
|
||||
<Setter Property="WordWrap" Value="True" />
|
||||
<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>
|
||||
|
||||
@@ -49,7 +76,7 @@
|
||||
</Grid>
|
||||
</ctl:ModalShell.Footer>
|
||||
|
||||
<Grid Margin="14,10" RowDefinitions="Auto,Auto,Auto,*,Auto,Auto,*">
|
||||
<Grid Margin="14,10" RowDefinitions="Auto,Auto,Auto,*">
|
||||
|
||||
<!-- Busy / error -->
|
||||
<TextBlock Grid.Row="0" Classes="meta" Margin="0,0,0,6"
|
||||
@@ -65,7 +92,7 @@
|
||||
IsVisible="{Binding HasBinaryFiles}">
|
||||
<StackPanel Spacing="3">
|
||||
<TextBlock Classes="meta" Foreground="{DynamicResource BloodBrush}"
|
||||
Text="Binary files can't be merged here — abort and resolve them in your editor:"/>
|
||||
Text="{loc:Tr conflictResolver.binaryHint}"/>
|
||||
<ItemsControl ItemsSource="{Binding BinaryFilePaths}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="x:String">
|
||||
@@ -76,69 +103,64 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Navigation header -->
|
||||
<Grid Grid.Row="2" ColumnDefinitions="Auto,Auto,*,Auto" Margin="0,0,0,8"
|
||||
<!-- 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="◀ Prev" Margin="0,0,6,0"
|
||||
<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="Next ▶"
|
||||
<Button Grid.Column="1" Classes="btn" Content="↓" Margin="0,0,12,0" Padding="10,4"
|
||||
ToolTip.Tip="{loc:Tr conflictResolver.nextConflict}"
|
||||
Command="{Binding NextCommand}"/>
|
||||
<TextBlock Grid.Column="2" Classes="path-mono" VerticalAlignment="Center"
|
||||
Margin="14,0" TextTrimming="CharacterEllipsis"
|
||||
Text="{Binding CurrentPath}"/>
|
||||
<TextBlock Grid.Column="3" Classes="meta" VerticalAlignment="Center"
|
||||
<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-way columns: Base | Ours | Theirs -->
|
||||
<Grid Grid.Row="3" ColumnDefinitions="*,*,*" IsVisible="{Binding HasCurrent}">
|
||||
<Border Grid.Column="0" BorderBrush="{DynamicResource LineBrush}" BorderThickness="1" CornerRadius="6" Margin="0,0,4,0">
|
||||
<!-- 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="BASE"/>
|
||||
<TextBlock Classes="eyebrow" Text="{loc:Tr conflictResolver.ours}"
|
||||
Foreground="{DynamicResource MossBrush}"/>
|
||||
</Border>
|
||||
<ae:TextEditor Name="BaseEditor" IsReadOnly="True"/>
|
||||
<ae:TextEditor Name="OursEditor" IsReadOnly="True" ShowLineNumbers="True"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
<Border Grid.Column="1" BorderBrush="{DynamicResource LineBrush}" BorderThickness="1" CornerRadius="6" Margin="4,0">
|
||||
|
||||
<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="OURS · current (merge target)" Foreground="{DynamicResource MossBrush}"/>
|
||||
<TextBlock Classes="eyebrow" Text="{loc:Tr conflictResolver.result}"/>
|
||||
</Border>
|
||||
<ae:TextEditor Name="OursEditor" IsReadOnly="True"/>
|
||||
<ae:TextEditor Name="ResultEditor" ShowLineNumbers="True"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
<Border Grid.Column="2" BorderBrush="{DynamicResource LineBrush}" BorderThickness="1" CornerRadius="6" Margin="4,0,0,0">
|
||||
|
||||
<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="THEIRS · incoming (task)" Foreground="{DynamicResource AccentBrush}"/>
|
||||
<TextBlock Classes="eyebrow" Text="{loc:Tr conflictResolver.theirs}"
|
||||
Foreground="{DynamicResource AmberBrush}"/>
|
||||
</Border>
|
||||
<ae:TextEditor Name="TheirsEditor" IsReadOnly="True"/>
|
||||
<ae:TextEditor Name="TheirsEditor" IsReadOnly="True" ShowLineNumbers="True"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Accept actions -->
|
||||
<WrapPanel Grid.Row="4" Orientation="Horizontal" Margin="0,8" IsVisible="{Binding HasCurrent}">
|
||||
<Button Classes="btn" Content="Accept Ours" Margin="0,0,8,0"
|
||||
Command="{Binding Current.AcceptOursCommand}"/>
|
||||
<Button Classes="btn" Content="Accept Base" Margin="0,0,8,0"
|
||||
IsEnabled="{Binding Current.HasBase}"
|
||||
Command="{Binding Current.AcceptBaseCommand}"/>
|
||||
<Button Classes="btn" Content="Accept Theirs" Margin="0,0,8,0"
|
||||
Command="{Binding Current.AcceptTheirsCommand}"/>
|
||||
<Button Classes="btn" Content="Accept Both" Margin="0,0,8,0"
|
||||
Command="{Binding Current.AcceptBothCommand}"/>
|
||||
</WrapPanel>
|
||||
|
||||
<!-- Merged result -->
|
||||
<TextBlock Grid.Row="5" Classes="eyebrow" Text="MERGED RESULT" Margin="0,4,0,4"
|
||||
IsVisible="{Binding HasCurrent}"/>
|
||||
<Border Grid.Row="6" BorderBrush="{DynamicResource LineBrush}" BorderThickness="1" CornerRadius="6"
|
||||
IsVisible="{Binding HasCurrent}">
|
||||
<ae:TextEditor Name="ResultEditor" ShowLineNumbers="True"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ctl:ModalShell>
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user