feature<DiffViewer>: add combined diff view mode; optimize line number using virtualizing ItemsControl instead of TextBox

This commit is contained in:
leo 2020-11-20 15:45:04 +08:00
parent 788013817d
commit b97d5e608d
5 changed files with 457 additions and 254 deletions

View file

@ -110,6 +110,10 @@ namespace SourceGit.Git {
/// Use list instead of tree in change view. /// Use list instead of tree in change view.
/// </summary> /// </summary>
public bool UIUseListInChanges { get; set; } public bool UIUseListInChanges { get; set; }
/// <summary>
/// Use one side diff instead of two sides.
/// </summary>
public bool UIUseOneSideDiff { get; set; }
#endregion #endregion
#region SETTING_REPOS #region SETTING_REPOS

View file

@ -22,12 +22,14 @@
<Geometry x:Key="Icon.MoveUp">M868 545.5L536.1 163c-12.7-14.7-35.5-14.7-48.3 0L156 545.5c-4.5 5.2-0.8 13.2 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z</Geometry> <Geometry x:Key="Icon.MoveUp">M868 545.5L536.1 163c-12.7-14.7-35.5-14.7-48.3 0L156 545.5c-4.5 5.2-0.8 13.2 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z</Geometry>
<Geometry x:Key="Icon.MoveDown">M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861c12.7 14.7 35.5 14.7 48.3 0L868 478.5c4.5-5.2 0.8-13.2-6-13.2z</Geometry> <Geometry x:Key="Icon.MoveDown">M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861c12.7 14.7 35.5 14.7 48.3 0L868 478.5c4.5-5.2 0.8-13.2-6-13.2z</Geometry>
<Geometry x:Key="Icon.Down">M509.44 546.304l270.848-270.912 90.56 90.56-347.52 349.056-0.832-0.768-13.056 13.056-362.624-361.28 91.136-91.264z</Geometry> <Geometry x:Key="Icon.Down">M509.44 546.304l270.848-270.912 90.56 90.56-347.52 349.056-0.832-0.768-13.056 13.056-362.624-361.28 91.136-91.264z</Geometry>
<Geometry x:Key="Icon.DoubleDown">M256 224l1e-8 115.2L512 544l255.99999999-204.8 1e-8-115.2-256 204.80000001L256 224zM512 684.8l-256-204.8L256 595.2 512 800 768 595.2l0-115.2L512 684.8z</Geometry> <Geometry x:Key="Icon.DoubleDown">M256 224l1e-8 115.2L512 544l255.99999999-204.8 1e-8-115.2-256 204.80000001L256 224zM512 684.8l-256-204.8L256 595.2 512 800 768 595.2l0-115.2L512 684.8z</Geometry>
<Geometry x:Key="Icon.Up">M169.5 831l342.8-341.9L855.1 831l105.3-105.3-448.1-448.1L64.2 725.7 169.5 831z</Geometry> <Geometry x:Key="Icon.Up">M169.5 831l342.8-341.9L855.1 831l105.3-105.3-448.1-448.1L64.2 725.7 169.5 831z</Geometry>
<Geometry x:Key="Icon.DoubleUp">M768 800V684.8L512 480 256 684.8V800l256-204.8L768 800zM512 339.2L768 544V428.8L512 224 256 428.8V544l256-204.8z</Geometry> <Geometry x:Key="Icon.DoubleUp">M768 800V684.8L512 480 256 684.8V800l256-204.8L768 800zM512 339.2L768 544V428.8L512 224 256 428.8V544l256-204.8z</Geometry>
<Geometry x:Key="Icon.SplitHorizontal">M73.152 1024H1024V658.282667h-73.152v292.565333H73.173333V658.282667H0V1024h73.152zM0 0v365.717333h73.152V73.152H950.826667V0H0z m950.848 365.717333V0H1024v365.717333h-73.152zM0 548.565333v-73.130666h1024v73.130666H0z</Geometry>
<Geometry x:Key="Icon.SplitVertical">M0 73.152V1024h365.717333v-73.152H73.152V73.173333h292.565333V0H0v73.152zM1024 0H658.282667v73.152h292.565333V950.826667H1024V0zM658.282667 950.848H1024V1024H658.282667v-73.152zM475.434667 0h73.130666v1024h-73.130666V0z</Geometry>
<Geometry x:Key="Icon.Preference">M64.2 180.3h418.2v120.6H64.2zM64.2 461.7h358.5v120.6H64.2zM64.2 723.1h418.2v120.6H64.2zM601.9 180.3h358.5v120.6H601.9zM482.4 119.9h179.2v241.3H482.4zM303.2 401.4h179.2v241.3H303.2zM482.4 662.8h179.2v241.3H482.4zM540.3 461.7h420.1v120.6H540.3zM601.9 723.1h358.5v120.6H601.9z</Geometry> <Geometry x:Key="Icon.Preference">M64.2 180.3h418.2v120.6H64.2zM64.2 461.7h358.5v120.6H64.2zM64.2 723.1h418.2v120.6H64.2zM601.9 180.3h358.5v120.6H601.9zM482.4 119.9h179.2v241.3H482.4zM303.2 401.4h179.2v241.3H303.2zM482.4 662.8h179.2v241.3H482.4zM540.3 461.7h420.1v120.6H540.3zM601.9 723.1h358.5v120.6H601.9z</Geometry>
<Geometry x:Key="Icon.Setting">M887 576.8v-129.4L796.6 418c-4.6-14-10.2-27.4-16.8-40.4l43.2-84.8-91.6-91.6-84.8 43.2c-13-6.6-26.6-12.2-40.4-16.8l-29.4-90.4h-129.4L418 227.6c-13.8 4.6-27.4 10.2-40.4 16.8l-84.8-43.2-91.6 91.6 43.2 84.8c-6.6 13-12.2 26.6-16.8 40.4l-90.4 29.4v129.4l90.4 29.4c4.6 13.8 10.2 27.4 16.8 40.4l-43.2 84.8 91.6 91.6 84.8-43.2c13 6.6 26.6 12.2 40.4 16.8l29.4 90.4h129.4l29.4-90.4c14-4.6 27.4-10.2 40.4-16.8l84.8 43.2 91.6-91.6-43.2-84.8c6.6-13 12.2-26.6 16.8-40.4l90.4-29.4zM512 662c-82.8 0-150-67.2-150-150s67.2-150 150-150 150 67.2 150 150-67.2 150-150 150z</Geometry> <Geometry x:Key="Icon.Setting">M887 576.8v-129.4L796.6 418c-4.6-14-10.2-27.4-16.8-40.4l43.2-84.8-91.6-91.6-84.8 43.2c-13-6.6-26.6-12.2-40.4-16.8l-29.4-90.4h-129.4L418 227.6c-13.8 4.6-27.4 10.2-40.4 16.8l-84.8-43.2-91.6 91.6 43.2 84.8c-6.6 13-12.2 26.6-16.8 40.4l-90.4 29.4v129.4l90.4 29.4c4.6 13.8 10.2 27.4 16.8 40.4l-43.2 84.8 91.6 91.6 84.8-43.2c13 6.6 26.6 12.2 40.4 16.8l29.4 90.4h129.4l29.4-90.4c14-4.6 27.4-10.2 40.4-16.8l84.8 43.2 91.6-91.6-43.2-84.8c6.6-13 12.2-26.6 16.8-40.4l90.4-29.4zM512 662c-82.8 0-150-67.2-150-150s67.2-150 150-150 150 67.2 150 150-67.2 150-150 150z</Geometry>
<Geometry x:Key="Icon.Info">M 38,19C 48.4934,19 57,27.5066 57,38C 57,48.4934 48.4934,57 38,57C 27.5066,57 19,48.4934 19,38C 19,27.5066 27.5066,19 38,19 Z M 33.25,33.25L 33.25,36.4167L 36.4166,36.4167L 36.4166,47.5L 33.25,47.5L 33.25,50.6667L 44.3333,50.6667L 44.3333,47.5L 41.1666,47.5L 41.1666,36.4167L 41.1666,33.25L 33.25,33.25 Z M 38.7917,25.3333C 37.48,25.3333 36.4167,26.3967 36.4167,27.7083C 36.4167,29.02 37.48,30.0833 38.7917,30.0833C 40.1033,30.0833 41.1667,29.02 41.1667,27.7083C 41.1667,26.3967 40.1033,25.3333 38.7917,25.3333 Z</Geometry> <Geometry x:Key="Icon.Info">M 38,19C 48.4934,19 57,27.5066 57,38C 57,48.4934 48.4934,57 38,57C 27.5066,57 19,48.4934 19,38C 19,27.5066 27.5066,19 38,19 Z M 33.25,33.25L 33.25,36.4167L 36.4166,36.4167L 36.4166,47.5L 33.25,47.5L 33.25,50.6667L 44.3333,50.6667L 44.3333,47.5L 41.1666,47.5L 41.1666,36.4167L 41.1666,33.25L 33.25,33.25 Z M 38.7917,25.3333C 37.48,25.3333 36.4167,26.3967 36.4167,27.7083C 36.4167,29.02 37.48,30.0833 38.7917,30.0833C 40.1033,30.0833 41.1667,29.02 41.1667,27.7083C 41.1667,26.3967 40.1033,25.3333 38.7917,25.3333 Z</Geometry>

View file

@ -108,4 +108,34 @@
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<Style x:Key="Style.ToggleButton.SplitDirection" TargetType="{x:Type ToggleButton}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid Background="Transparent">
<Path
x:Name="Icon"
Height="12"
Width="12"
Style="{DynamicResource Style.Icon}"
Fill="Transparent"
Stroke="{DynamicResource Brush.FG}"
StrokeThickness=".4"
Data="{DynamicResource Icon.SplitHorizontal}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Icon" Property="Data" Value="{DynamicResource Icon.SplitVertical}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary> </ResourceDictionary>

View file

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sourcegit="clr-namespace:SourceGit"
mc:Ignorable="d" mc:Ignorable="d"
FontFamily="Consolas"> FontFamily="Consolas">
<Border BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}"> <Border BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}">
@ -32,7 +33,7 @@
<Path x:Name="loading" Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}"/> <Path x:Name="loading" Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}"/>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="2" x:Name="diffNavigation" Orientation="Horizontal" HorizontalAlignment="Right"> <StackPanel Grid.Column="2" x:Name="textChangeOptions" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="26" Click="Go2Next" ToolTip="Next Difference" Background="Transparent"> <Button Width="26" Click="Go2Next" ToolTip="Next Difference" Background="Transparent">
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveDown}"/> <Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveDown}"/>
</Button> </Button>
@ -40,43 +41,68 @@
<Button Click="Go2Prev" ToolTip="Previous Difference" Background="Transparent"> <Button Click="Go2Prev" ToolTip="Previous Difference" Background="Transparent">
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveUp}"/> <Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveUp}"/>
</Button> </Button>
<ToggleButton
Margin="8,0,0,0"
Style="{StaticResource Style.ToggleButton.SplitDirection}"
ToolTip="Toggle One-Side/Two-Sides"
IsChecked="{Binding Source={x:Static sourcegit:App.Preference}, Path=UIUseOneSideDiff, Mode=TwoWay}"
Checked="ChangeDiffMode" Unchecked="ChangeDiffMode"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
<Grid x:Name="textChange" Grid.Row="1" ClipToBounds="True"> <Grid x:Name="textChange" Grid.Row="1" ClipToBounds="True">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="100"/> <ColumnDefinition Width="4"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="*" MinWidth="100"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="1"/> <ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/> <ColumnDefinition x:Name="twoSideLeft"/>
<ColumnDefinition x:Name="twoSideSplittter" Width="1"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*" MinWidth="100"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBox <ItemsControl
Grid.Column="1"
x:Name="leftLineNumber" x:Name="leftLineNumber"
Grid.Column="0" Padding="0"
AcceptsReturn="True" Margin="0"
AcceptsTab="True"
BorderThickness="0" BorderThickness="0"
Background="Transparent" Background="Transparent"
IsReadOnly="True" VirtualizingPanel.ScrollUnit="Item"
Padding="2,0" VirtualizingPanel.IsVirtualizing="True"
Margin="0" VirtualizingPanel.VirtualizationMode="Recycling">
FontSize="13" <ItemsControl.ItemsPanel>
HorizontalContentAlignment="Right" <ItemsPanelTemplate>
VerticalAlignment="Stretch"/> <VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/> <ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}" Padding="0" Margin="0" FontFamily="Consolas" FontSize="13" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Rectangle Grid.Column="3" Width="1" Fill="{StaticResource Brush.Border2}"/>
<RichTextBox <RichTextBox
x:Name="leftText" x:Name="leftText"
Grid.Column="2" Grid.Column="4"
AcceptsReturn="True" AcceptsReturn="True"
AcceptsTab="True" AcceptsTab="True"
IsReadOnly="True" IsReadOnly="True"
@ -90,49 +116,53 @@
RenderOptions.ClearTypeHint="Enabled" RenderOptions.ClearTypeHint="Enabled"
ScrollViewer.ScrollChanged="OnViewerScroll" ScrollViewer.ScrollChanged="OnViewerScroll"
PreviewMouseWheel="OnViewerMouseWheel" PreviewMouseWheel="OnViewerMouseWheel"
SizeChanged="LeftSizeChanged" SizeChanged="OnSizeChanged"
SelectionChanged="OnViewerSelectionChanged" SelectionChanged="OnViewerSelectionChanged"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<RichTextBox.Document> <RichTextBox.Document>
<FlowDocument PageWidth="0"/> <FlowDocument PageWidth="0"/>
</RichTextBox.Document> </RichTextBox.Document>
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy"/>
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox> </RichTextBox>
</Grid>
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.Border2}"/> <Rectangle Grid.Column="5" Width="1" Fill="{StaticResource Brush.Border2}"/>
<Grid Grid.Column="2"> <ItemsControl
<Grid.ColumnDefinitions> Grid.Column="7"
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox
x:Name="rightLineNumber" x:Name="rightLineNumber"
Grid.Column="0" Padding="0"
AcceptsReturn="True" Margin="0"
AcceptsTab="True"
IsReadOnly="True"
BorderThickness="0" BorderThickness="0"
Background="Transparent" Background="Transparent"
Padding="2,0" VirtualizingPanel.ScrollUnit="Item"
Margin="0" VirtualizingPanel.IsVirtualizing="True"
FontSize="13" VirtualizingPanel.VirtualizationMode="Recycling">
HorizontalContentAlignment="Right" <ItemsControl.ItemsPanel>
VerticalAlignment="Stretch"/> <ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/> <ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}" Padding="0" Margin="0" FontFamily="Consolas" FontSize="13" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Rectangle Grid.Column="9" Width="1" Fill="{StaticResource Brush.Border2}"/>
<RichTextBox <RichTextBox
x:Name="rightText" x:Name="rightText"
Grid.Column="2" Grid.Column="10"
AcceptsReturn="True" AcceptsReturn="True"
AcceptsTab="True" AcceptsTab="True"
IsReadOnly="True" IsReadOnly="True"
@ -146,21 +176,15 @@
RenderOptions.ClearTypeHint="Enabled" RenderOptions.ClearTypeHint="Enabled"
ScrollViewer.ScrollChanged="OnViewerScroll" ScrollViewer.ScrollChanged="OnViewerScroll"
PreviewMouseWheel="OnViewerMouseWheel" PreviewMouseWheel="OnViewerMouseWheel"
SizeChanged="RightSizeChanged" SizeChanged="OnSizeChanged"
SelectionChanged="OnViewerSelectionChanged" SelectionChanged="OnViewerSelectionChanged"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"> VerticalAlignment="Stretch">
<RichTextBox.Document> <RichTextBox.Document>
<FlowDocument PageWidth="0"/> <FlowDocument PageWidth="0"/>
</RichTextBox.Document> </RichTextBox.Document>
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy"/>
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox> </RichTextBox>
</Grid> </Grid>
</Grid>
<Border x:Name="sizeChange" Grid.Row="1" ClipToBounds="True" Background="{StaticResource Brush.BG3}" Visibility="Collapsed"> <Border x:Name="sizeChange" Grid.Row="1" ClipToBounds="True" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
<StackPanel Orientation="Vertical" VerticalAlignment="Center"> <StackPanel Orientation="Vertical" VerticalAlignment="Center">

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -15,6 +16,7 @@ namespace SourceGit.UI {
/// </summary> /// </summary>
public partial class DiffViewer : UserControl { public partial class DiffViewer : UserControl {
private double minWidth = 0; private double minWidth = 0;
private Git.Diff.TextChange textChangeData = null;
/// <summary> /// <summary>
/// Diff options. /// Diff options.
@ -49,6 +51,8 @@ namespace SourceGit.UI {
public void Diff(Git.Repository repo, Option opts) { public void Diff(Git.Repository repo, Option opts) {
SetTitle(opts.Path, opts.OrgPath); SetTitle(opts.Path, opts.OrgPath);
textChangeData = null;
loading.Visibility = Visibility.Visible; loading.Visibility = Visibility.Visible;
mask.Visibility = Visibility.Collapsed; mask.Visibility = Visibility.Collapsed;
textChange.Visibility = Visibility.Collapsed; textChange.Visibility = Visibility.Collapsed;
@ -80,7 +84,8 @@ namespace SourceGit.UI {
if (rs.IsBinary) { if (rs.IsBinary) {
SetBinaryChange(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath)); SetBinaryChange(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath));
} else if (rs.Blocks.Count > 0) { } else if (rs.Blocks.Count > 0) {
SetTextChange(rs); textChangeData = rs;
SetTextChange();
} else { } else {
SetSame(); SetSame();
} }
@ -107,28 +112,41 @@ namespace SourceGit.UI {
/// Show diff content. /// Show diff content.
/// </summary> /// </summary>
/// <param name="rs"></param> /// <param name="rs"></param>
private void SetTextChange(Git.Diff.TextChange rs) { private void SetTextChange() {
if (textChangeData == null) return;
Dispatcher.Invoke(() => { Dispatcher.Invoke(() => {
loading.Visibility = Visibility.Collapsed; loading.Visibility = Visibility.Collapsed;
textChange.Visibility = Visibility.Visible; textChange.Visibility = Visibility.Visible;
diffNavigation.Visibility = Visibility.Visible; textChangeOptions.Visibility = Visibility.Visible;
if (App.Preference.UIUseOneSideDiff) {
twoSideLeft.Width = new GridLength(0);
twoSideLeft.MinWidth = 0;
twoSideSplittter.Width = new GridLength(0);
} else {
twoSideLeft.Width = new GridLength(1, GridUnitType.Star);
twoSideLeft.MinWidth = 100;
twoSideSplittter.Width = new GridLength(2);
}
minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16; minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16;
leftLineNumber.Text = ""; leftLineNumber.ItemsSource = null;
rightLineNumber.Text = ""; rightLineNumber.ItemsSource = null;
leftText.Document.Blocks.Clear(); leftText.Document.Blocks.Clear();
rightText.Document.Blocks.Clear(); rightText.Document.Blocks.Clear();
var leftLineNumberBuilder = new StringBuilder(); var lLineNumbers = new List<string>();
var rightLineNumberBuilder = new StringBuilder(); var rLineNumbers = new List<string>();
foreach (var b in rs.Blocks) ShowBlock(b, leftLineNumberBuilder, rightLineNumberBuilder); foreach (var b in textChangeData.Blocks) ShowBlock(b, lLineNumbers, rLineNumbers);
leftText.Document.PageWidth = minWidth + 16; if (!App.Preference.UIUseOneSideDiff) leftText.Document.PageWidth = minWidth + 16;
rightText.Document.PageWidth = minWidth + 16; rightText.Document.PageWidth = minWidth + 16;
leftLineNumber.Text = leftLineNumberBuilder.ToString(); leftLineNumber.ItemsSource = lLineNumbers;
rightLineNumber.Text = rightLineNumberBuilder.ToString(); rightLineNumber.ItemsSource = rLineNumbers;
leftText.ScrollToHome(); leftText.ScrollToHome();
}); });
} }
@ -141,7 +159,7 @@ namespace SourceGit.UI {
Dispatcher.Invoke(() => { Dispatcher.Invoke(() => {
loading.Visibility = Visibility.Collapsed; loading.Visibility = Visibility.Collapsed;
sizeChange.Visibility = Visibility.Visible; sizeChange.Visibility = Visibility.Visible;
diffNavigation.Visibility = Visibility.Collapsed; textChangeOptions.Visibility = Visibility.Collapsed;
txtSizeChangeTitle.Content = "BINARY DIFF"; txtSizeChangeTitle.Content = "BINARY DIFF";
txtNewSize.Content = $"{bc.Size} Bytes"; txtNewSize.Content = $"{bc.Size} Bytes";
txtOldSize.Content = $"{bc.PreSize} Bytes"; txtOldSize.Content = $"{bc.PreSize} Bytes";
@ -159,7 +177,7 @@ namespace SourceGit.UI {
loading.Visibility = Visibility.Collapsed; loading.Visibility = Visibility.Collapsed;
sizeChange.Visibility = Visibility.Visible; sizeChange.Visibility = Visibility.Visible;
diffNavigation.Visibility = Visibility.Collapsed; textChangeOptions.Visibility = Visibility.Collapsed;
txtSizeChangeTitle.Content = "LFS OBJECT CHANGE"; txtSizeChangeTitle.Content = "LFS OBJECT CHANGE";
txtNewSize.Content = $"{newSize} Bytes"; txtNewSize.Content = $"{newSize} Bytes";
txtOldSize.Content = $"{oldSize} Bytes"; txtOldSize.Content = $"{oldSize} Bytes";
@ -173,44 +191,34 @@ namespace SourceGit.UI {
Dispatcher.Invoke(() => { Dispatcher.Invoke(() => {
loading.Visibility = Visibility.Collapsed; loading.Visibility = Visibility.Collapsed;
noChange.Visibility = Visibility.Visible; noChange.Visibility = Visibility.Visible;
diffNavigation.Visibility = Visibility.Collapsed; textChangeOptions.Visibility = Visibility.Collapsed;
}); });
} }
/// <summary> /// <summary>
/// Make paragraph. /// Make paragraph for two-sides diff
/// </summary> /// </summary>
/// <param name="b"></param> /// <param name="b"></param>
private void ShowBlock(Git.Diff.Block b, StringBuilder leftNumber, StringBuilder rightNumber) { /// <param name="leftNumber"></param>
/// <param name="rightNumber"></param>
private void ShowBlock(Git.Diff.Block b, List<string> leftNumber, List<string> rightNumber) {
bool useOneSide = App.Preference.UIUseOneSideDiff;
if (useOneSide && b.Mode == Git.Diff.LineMode.Empty) return;
var content = b.Builder.ToString(); var content = b.Builder.ToString();
// Make paragraph element
Paragraph p = new Paragraph(new Run(content)); Paragraph p = new Paragraph(new Run(content));
p.Margin = new Thickness(0); p.Margin = new Thickness(0);
p.Padding = new Thickness(); p.Padding = new Thickness(0);
p.LineHeight = 1; p.LineHeight = 1;
p.Background = Brushes.Transparent; p.Background = GetBlockBackground(b);
p.Foreground = FindResource("Brush.FG") as SolidColorBrush; p.Foreground = b.Mode == Git.Diff.LineMode.Indicator ? Brushes.Gray : FindResource("Brush.FG") as SolidColorBrush;
p.FontStyle = FontStyles.Normal; p.FontStyle = b.Mode == Git.Diff.LineMode.Indicator ? FontStyles.Italic : FontStyles.Normal;
p.DataContext = b; p.DataContext = b;
p.ContextMenuOpening += OnParagraphContextMenuOpening;
switch (b.Mode) { // Calculate with
case Git.Diff.LineMode.Normal:
break;
case Git.Diff.LineMode.Indicator:
p.Foreground = Brushes.Gray;
p.FontStyle = FontStyles.Italic;
break;
case Git.Diff.LineMode.Empty:
p.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
break;
case Git.Diff.LineMode.Added:
p.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
break;
case Git.Diff.LineMode.Deleted:
p.Background = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
break;
}
var formatter = new FormattedText( var formatter = new FormattedText(
content, content,
CultureInfo.CurrentUICulture, CultureInfo.CurrentUICulture,
@ -220,24 +228,50 @@ namespace SourceGit.UI {
Brushes.Black, Brushes.Black,
new NumberSubstitution(), new NumberSubstitution(),
TextFormattingMode.Ideal); TextFormattingMode.Ideal);
if (minWidth < formatter.Width) minWidth = formatter.Width; if (minWidth < formatter.Width) minWidth = formatter.Width;
// Line numbers
switch (b.Side) { switch (b.Side) {
case Git.Diff.Side.Left: case Git.Diff.Side.Left:
leftText.Document.Blocks.Add(p);
for (int i = 0; i < b.Count; i++) { for (int i = 0; i < b.Count; i++) {
if (b.CanShowNumber) leftNumber.AppendLine($"{i + b.LeftStart}"); if (b.CanShowNumber) leftNumber.Add($"{i + b.LeftStart}");
else leftNumber.AppendLine(); else leftNumber.Add("");
if (useOneSide) rightNumber.Add("");
} }
break; break;
case Git.Diff.Side.Right: case Git.Diff.Side.Right:
rightText.Document.Blocks.Add(p);
for (int i = 0; i < b.Count; i++) { for (int i = 0; i < b.Count; i++) {
if (b.CanShowNumber) rightNumber.AppendLine($"{i + b.RightStart}"); if (b.CanShowNumber) rightNumber.Add($"{i + b.RightStart}");
else rightNumber.AppendLine(); else rightNumber.Add("");
if (useOneSide) leftNumber.Add("");
} }
break; break;
default:
for (int i = 0; i < b.Count; i++) {
if (b.CanShowNumber) {
leftNumber.Add($"{i + b.LeftStart}");
rightNumber.Add($"{i + b.RightStart}");
} else {
leftNumber.Add("");
rightNumber.Add("");
}
}
break;
}
// Add this paragraph to document.
if (App.Preference.UIUseOneSideDiff) {
rightText.Document.Blocks.Add(p);
} else {
switch (b.Side) {
case Git.Diff.Side.Left:
leftText.Document.Blocks.Add(p);
break;
case Git.Diff.Side.Right:
rightText.Document.Blocks.Add(p);
break;
default: default:
leftText.Document.Blocks.Add(p); leftText.Document.Blocks.Add(p);
@ -249,23 +283,140 @@ namespace SourceGit.UI {
cp.Foreground = p.Foreground; cp.Foreground = p.Foreground;
cp.FontStyle = p.FontStyle; cp.FontStyle = p.FontStyle;
cp.DataContext = b; cp.DataContext = b;
rightText.Document.Blocks.Add(cp); cp.ContextMenuOpening += OnParagraphContextMenuOpening;
for (int i = 0; i < b.Count; i++) { rightText.Document.Blocks.Add(cp);
if (b.Mode != Git.Diff.LineMode.Indicator) {
leftNumber.AppendLine($"{i + b.LeftStart}");
rightNumber.AppendLine($"{i + b.RightStart}");
} else {
leftNumber.AppendLine();
rightNumber.AppendLine();
}
}
break; break;
} }
} }
}
/// <summary>
/// Get background color of block.
/// </summary>
/// <param name="b"></param>
/// <returns></returns>
private Brush GetBlockBackground(Git.Diff.Block b) {
Border border = new Border();
border.BorderThickness = new Thickness(0);
border.BorderBrush = Brushes.LightBlue;
border.Height = b.Count * 16 - 1;
border.Width = minWidth - 1;
switch (b.Mode) {
case Git.Diff.LineMode.Empty:
border.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
break;
case Git.Diff.LineMode.Added:
border.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
break;
case Git.Diff.LineMode.Deleted:
border.Background = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
break;
default:
border.Background = Brushes.Transparent;
break;
}
VisualBrush highlight = new VisualBrush();
highlight.TileMode = TileMode.None;
highlight.Stretch = Stretch.Fill;
highlight.Visual = border;
return highlight;
}
#endregion #endregion
#region EVENTS #region EVENTS
/// <summary>
/// Context menu for text-change paragraph
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnParagraphContextMenuOpening(object sender, ContextMenuEventArgs ev) {
var paragraph = sender as Paragraph;
var doc = (paragraph.Parent as FlowDocument);
if (doc != null) {
var textBox = doc.Parent as RichTextBox;
if (textBox != null && !textBox.Selection.IsEmpty) {
var copyItem = new MenuItem();
copyItem.Header = "Copy";
copyItem.Click += (o, e) => {
Clipboard.SetText(textBox.Selection.Text);
e.Handled = true;
};
var copyMenu = new ContextMenu();
copyMenu.Items.Add(copyItem);
copyMenu.IsOpen = true;
ev.Handled = true;
return;
}
}
var block = paragraph.DataContext as Git.Diff.Block;
if (block.Mode == Git.Diff.LineMode.Empty || block.Mode == Git.Diff.LineMode.Indicator) {
ev.Handled = true;
return;
}
var highlight = paragraph.Background as VisualBrush;
if (highlight != null) {
(highlight.Visual as Border).BorderThickness = new Thickness(.5);
}
paragraph.ContextMenu = new ContextMenu();
paragraph.ContextMenu.Closed += (o, e) => {
if (paragraph.ContextMenu == (o as ContextMenu)) {
if (highlight != null) {
(highlight.Visual as Border).BorderThickness = new Thickness(0);
}
paragraph.ContextMenu = null;
}
};
var copy = new MenuItem();
copy.Header = "Copy";
copy.Click += (o, e) => {
Clipboard.SetText(block.Builder.ToString());
e.Handled = true;
};
paragraph.ContextMenu.Items.Add(copy);
paragraph.ContextMenu.IsOpen = true;
ev.Handled = true;
}
/// <summary>
/// Fix document size.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
var text = sender as RichTextBox;
if (text.Document.PageWidth < text.ActualWidth) {
text.Document.PageWidth = text.ActualWidth;
}
}
/// <summary>
/// Scroll using mouse wheel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnViewerMouseWheel(object sender, MouseWheelEventArgs e) {
var text = sender as RichTextBox;
if (text == null) return;
if (e.Delta > 0) {
text.LineUp();
} else {
text.LineDown();
}
e.Handled = true;
}
/// <summary> /// <summary>
/// Sync scroll both sides. /// Sync scroll both sides.
/// </summary> /// </summary>
@ -294,46 +445,6 @@ namespace SourceGit.UI {
} }
} }
/// <summary>
/// Scroll using mouse wheel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnViewerMouseWheel(object sender, MouseWheelEventArgs e) {
var text = sender as RichTextBox;
if (text == null) return;
if (e.Delta > 0) {
text.LineUp();
} else {
text.LineDown();
}
e.Handled = true;
}
/// <summary>
/// Fix document size for left side.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LeftSizeChanged(object sender, SizeChangedEventArgs e) {
if (leftText.Document.PageWidth < leftText.ActualWidth) {
leftText.Document.PageWidth = leftText.ActualWidth;
}
}
/// <summary>
/// Fix document size for right side.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RightSizeChanged(object sender, SizeChangedEventArgs e) {
if (rightText.Document.PageWidth < rightText.ActualWidth) {
rightText.Document.PageWidth = rightText.ActualWidth;
}
}
/// <summary> /// <summary>
/// Auto scroll when selection changed. /// Auto scroll when selection changed.
/// </summary> /// </summary>
@ -366,9 +477,20 @@ namespace SourceGit.UI {
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="e"></param> /// <param name="e"></param>
private void Go2Next(object sender, RoutedEventArgs e) { private void Go2Next(object sender, RoutedEventArgs e) {
Paragraph next = null;
double minTop = 0; double minTop = 0;
if (App.Preference.UIUseOneSideDiff) {
foreach (var p in rightText.Document.Blocks) {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top > 17 && (block.IsLeftDelete || block.IsRightAdded)) {
minTop = rect.Top;
break;
}
}
} else {
Paragraph next = null;
foreach (var p in leftText.Document.Blocks) { foreach (var p in leftText.Document.Blocks) {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward); var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block; var block = p.DataContext as Git.Diff.Block;
@ -391,8 +513,9 @@ namespace SourceGit.UI {
break; break;
} }
} }
}
if (next != null) { if (minTop > 0) {
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + minTop - 16); rightText.ScrollToVerticalOffset(rightText.VerticalOffset + minTop - 16);
} }
} }
@ -403,8 +526,22 @@ namespace SourceGit.UI {
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="e"></param> /// <param name="e"></param>
private void Go2Prev(object sender, RoutedEventArgs e) { private void Go2Prev(object sender, RoutedEventArgs e) {
double maxTop = double.MaxValue;
if (App.Preference.UIUseOneSideDiff) {
var p = rightText.Document.Blocks.LastBlock as Paragraph;
do {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top < 15 && (block.IsLeftDelete || block.IsRightAdded)) {
maxTop = rect.Top;
break;
}
p = p.PreviousBlock as Paragraph;
} while (p != null);
} else {
Paragraph next = null; Paragraph next = null;
double maxTop = 0;
var p = leftText.Document.Blocks.LastBlock as Paragraph; var p = leftText.Document.Blocks.LastBlock as Paragraph;
do { do {
@ -424,21 +561,27 @@ namespace SourceGit.UI {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward); var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block; var block = p.DataContext as Git.Diff.Block;
if (rect.Top < 15 && block.IsRightAdded) { if (rect.Top < 15 && block.IsRightAdded) {
if (next == null || maxTop < rect.Top) { if (next == null || maxTop < rect.Top) maxTop = rect.Top;
next = p;
maxTop = rect.Top;
}
break; break;
} }
p = p.PreviousBlock as Paragraph; p = p.PreviousBlock as Paragraph;
} while (p != null); } while (p != null);
}
if (next != null) { if (maxTop != double.MaxValue) {
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + maxTop - 16); rightText.ScrollToVerticalOffset(rightText.VerticalOffset + maxTop - 16);
} }
} }
/// <summary>
/// Chang diff mode.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ChangeDiffMode(object sender, RoutedEventArgs e) {
SetTextChange();
}
#endregion #endregion
} }
} }