diff --git a/src/ViewModels/Conflict.cs b/src/ViewModels/Conflict.cs index 1ccf4a33a..6e01d0f68 100644 --- a/src/ViewModels/Conflict.cs +++ b/src/ViewModels/Conflict.cs @@ -41,10 +41,21 @@ public bool IsResolved private set; } + public Models.Change Change + { + get => _change; + } + + public Repository Repository + { + get => _repo; + } + public Conflict(Repository repo, WorkingCopy wc, Models.Change change) { _wc = wc; _change = change; + _repo = repo; IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).ReadToEnd().IsSuccess; @@ -98,5 +109,6 @@ public void OpenExternalMergeTool() private WorkingCopy _wc = null; private Models.Change _change = null; + private Repository _repo = null; } } diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 925c7622c..57b2c5104 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Globalization; using System.IO; +using System.Linq; using System.Text; using Avalonia; @@ -2016,4 +2017,111 @@ private void OnDiscardChunk(object _1, RoutedEventArgs _2) repo.SetWatcherEnabled(true); } } + + public class ConflictTextDiffPresenter: SingleSideTextDiffPresenter + { + public enum Side : int + { + Ours = 0, WorkingCopy = 1, Theirs = 2 + } + + public Side DisplaySide { + get; set; + } + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + } + protected override void OnUnloaded(RoutedEventArgs e) + { + base.OnUnloaded(e); + } + + protected override void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + + if (DataContext is ViewModels.Conflict diff) + { + string conflict_start_marker = "<<<<<<< HEAD"; + string conflict_separator_marker = "======="; + string conflict_end_marker = ">>>>>>> "; + + var cmd = new Commands.QueryRefsContainsCommit(diff.Repository.FullPath, (diff.Theirs as Models.Commit)?.SHA); + var their_branches = cmd.Result().ToArray(); + + string filePath = Path.Combine(diff.Repository.FullPath, diff.Change.Path); + var lines =File.ReadAllLines(filePath).ToList<string>(); + + bool shouldFilter = DisplaySide != Side.WorkingCopy; + while(shouldFilter) { + int idx_start = lines.IndexOf(conflict_start_marker); + if (idx_start == -1) { + shouldFilter = false; + break; + } + + int idx_sep = lines.IndexOf(conflict_separator_marker, idx_start); + if (idx_sep == -1) { + shouldFilter = false; + break; + } + + int idx_end = idx_sep + 1; + while(idx_end < lines.Count - 1) { + bool found = false; + for (int i = 0; i < their_branches.Length; i++) { + string ce_marker = conflict_end_marker + their_branches[i].Name; + if(lines[idx_end].StartsWith(ce_marker)) { + found = true; + break; + } + } + if (found) { + break; + } + idx_end++; + } + if (idx_end == lines.Count - 1) { + shouldFilter = false; + break; + } + + switch (DisplaySide) { + case Side.Ours: + lines.RemoveRange(idx_sep, idx_end - idx_sep + 1); + lines.RemoveAt(idx_start); + break; + case Side.WorkingCopy: + break; + case Side.Theirs: + lines.RemoveAt(idx_end); + lines.RemoveRange(idx_start, idx_sep - idx_start + 1); + break; + } + } + + var builder = new StringBuilder(); + foreach (var line in lines) + { + if (line.Length > 10000) + { + builder.Append(line.Substring(0, 1000)); + builder.Append($"...({line.Length - 1000} characters trimmed)"); + builder.AppendLine(); + } + else + { + builder.AppendLine(line); + } + } + + Text = builder.ToString(); + } + else + { + Text = string.Empty; + } + } + } } diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index c9b94d592..272490423 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -203,7 +203,146 @@ <ContentControl Content="{Binding DetailContext}"> <ContentControl.DataTemplates> <DataTemplate DataType="vm:Conflict"> - <v:Conflict/> + <Border Background="{DynamicResource Brush.Window}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"> + <Grid VerticalAlignment="Center"> + <StackPanel Orientation="Vertical" IsVisible="{Binding !IsResolved}"> + <StackPanel.DataTemplates> + <DataTemplate DataType="m:Branch"> + <StackPanel Orientation="Horizontal"> + <Path Width="12" Height="12" Data="{StaticResource Icons.Branch}"/> + <TextBlock Margin="4,0,0,0" Text="{Binding FriendlyName}"/> + <TextBlock Margin="4,0,0,0" Text="{Binding Head, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/> + </StackPanel> + </DataTemplate> + + <DataTemplate DataType="m:Commit"> + <StackPanel Orientation="Horizontal"> + <Path Width="12" Height="12" Data="{StaticResource Icons.Commit}"/> + <v:CommitRefsPresenter Margin="8,0,0,0" + TagBackground="{DynamicResource Brush.DecoratorTag}" + Foreground="{DynamicResource Brush.FG1}" + FontFamily="{DynamicResource Fonts.Primary}" + FontSize="11" + VerticalAlignment="Center" + UseGraphColor="False"/> + <TextBlock Margin="4,0,0,0" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/> + <TextBlock Margin="4,0,0,0" Text="{Binding Subject}"/> + </StackPanel> + </DataTemplate> + + <DataTemplate DataType="m:Tag"> + <StackPanel Orientation="Horizontal"> + <Path Width="12" Height="12" Data="{StaticResource Icons.Tag}"/> + <TextBlock Margin="4,0,0,0" Text="{Binding Name}"/> + <TextBlock Margin="4,0,0,0" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/> + </StackPanel> + </DataTemplate> + </StackPanel.DataTemplates> + + <StackPanel Orientation="Horizontal"> + <Path Width="32" Height="32" Margin="10,10,10,10" + Data="{StaticResource Icons.Conflict}" Fill="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/> + <TextBlock Margin="10,10,10,10" FontSize="20" FontWeight="Bold" + Text="{DynamicResource Text.WorkingCopy.Conflicts}" + Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/> + </StackPanel> + + <Border Margin="16,0" Padding="8" CornerRadius="4" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"> + <Border.IsVisible> + <MultiBinding Converter="{x:Static BoolConverters.And}"> + <Binding Path="Theirs" Converter="{x:Static ObjectConverters.IsNotNull}"/> + <Binding Path="Mine" Converter="{x:Static ObjectConverters.IsNotNull}"/> + </MultiBinding> + </Border.IsVisible> + + <Grid Margin="8,0,0,0" RowDefinitions="32,32,*" ColumnDefinitions="*,1,*,1,*,1,12"> + <TextBlock Grid.Row="0" Grid.Column="0" Classes="info_label" HorizontalAlignment="Left" Text="MINE"/> + <ContentControl Grid.Row="1" Grid.Column="0" Margin="16,0,0,0" HorizontalAlignment="Left" Content="{Binding Mine}"/> + + <TextBlock Grid.Row="0" Grid.RowSpan="2" Grid.Column="2" Classes="info_label" HorizontalAlignment="Center" Text="{DynamicResource Text.WorkingCopy}"/> + + <TextBlock Grid.Row="0" Grid.Column="4" Classes="info_label" HorizontalAlignment="Right" Text="THEIRS"/> + <ContentControl Grid.Row="1" Grid.Column="4" Margin="16,0,0,0" HorizontalAlignment="Right" Content="{Binding Theirs}"/> + + <v:ConflictTextDiffPresenter Grid.Row="2" Grid.Column="0" + x:Name="OurSidePresenter" + DisplaySide="0" + Foreground="{DynamicResource Brush.FG1}" + LineBrush="{DynamicResource Brush.Border2}" + EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}" + AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}" + DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}" + AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}" + DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}" + IndicatorForeground="{DynamicResource Brush.FG2}" + FontFamily="{DynamicResource Fonts.Monospace}" + FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}" + UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}" + WordWrap="False" + ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}" + EnableChunkSelection="True"/> + + <Rectangle Grid.Row="2" Grid.Column="1" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/> + + <v:ConflictTextDiffPresenter Grid.Row="2" Grid.Column="2" + x:Name="WorkingCopyPresenter" + DisplaySide="1" + Foreground="{DynamicResource Brush.FG1}" + LineBrush="{DynamicResource Brush.Border2}" + EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}" + AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}" + DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}" + AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}" + DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}" + IndicatorForeground="{DynamicResource Brush.FG2}" + FontFamily="{DynamicResource Fonts.Monospace}" + FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}" + UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}" + WordWrap="False" + ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}"/> + + <Rectangle Grid.Row="2" Grid.Column="3" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/> + + <v:ConflictTextDiffPresenter Grid.Row="2" Grid.Column="4" + x:Name="TherrSidePresenter" + DisplaySide="2" + Foreground="{DynamicResource Brush.FG1}" + LineBrush="{DynamicResource Brush.Border2}" + EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}" + AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}" + DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}" + AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}" + DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}" + IndicatorForeground="{DynamicResource Brush.FG2}" + FontFamily="{DynamicResource Fonts.Monospace}" + FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}" + UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}" + WordWrap="False" + ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}"/> + + <Rectangle Grid.Row="2" Grid.Column="5" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/> + + <v:TextDiffViewMinimap Grid.Column="6" + DisplayRange="{Binding #OurSidePresenter.DisplayRange}" + AddedLineBrush="{DynamicResource Brush.Diff.AddedBG}" + DeletedLineBrush="{DynamicResource Brush.Diff.DeletedBG}"/> + </Grid> + </Border> + + <StackPanel Margin="0,8,0,0" Orientation="Horizontal" HorizontalAlignment="Center"> + <Button Classes="flat" Content="USE THEIRS" Command="{Binding UseTheirs}"/> + <Button Classes="flat" Margin="8,0,0,0" Content="USE MINE" Command="{Binding UseMine}"/> + <Button Classes="flat" Margin="8,0,0,0" Content="OPEN EXTERNAL MERGETOOL" Command="{Binding OpenExternalMergeTool}"/> + </StackPanel> + </StackPanel> + + <StackPanel Orientation="Vertical" IsVisible="{Binding IsResolved}"> + <Path Width="64" Height="64" Data="{StaticResource Icons.Check}" Fill="Green"/> + <TextBlock Margin="0,16,0,8" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts.Resolved}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/> + <TextBlock Text="{DynamicResource Text.WorkingCopy.CanStageTip}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/> + </StackPanel> + </Grid> + </Border> </DataTemplate> <DataTemplate DataType="vm:DiffContext">