mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-11-01 13:13:21 -07:00
feature<*>: use DataGrid instead of RichTextBox to improve performance
This commit is contained in:
parent
544d819c96
commit
cbdebee4c2
9 changed files with 594 additions and 848 deletions
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace SourceGit.Git {
|
namespace SourceGit.Git {
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@ namespace SourceGit.Git {
|
||||||
public class Blame {
|
public class Blame {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Block content.
|
/// Line content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Block {
|
public class Line {
|
||||||
public string CommitSHA { get; set; }
|
public string CommitSHA { get; set; }
|
||||||
public string Author { get; set; }
|
public string Author { get; set; }
|
||||||
public string Time { get; set; }
|
public string Time { get; set; }
|
||||||
|
@ -18,18 +18,13 @@ namespace SourceGit.Git {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Blocks
|
/// Lines
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Block> Blocks { get; set; } = new List<Block>();
|
public List<Line> Lines { get; set; } = new List<Line>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is binary file?
|
/// Is binary file?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsBinary { get; set; } = false;
|
public bool IsBinary { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Line count.
|
|
||||||
/// </summary>
|
|
||||||
public int LineCount { get; set; } = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
186
src/Git/Diff.cs
186
src/Git/Diff.cs
|
@ -15,100 +15,36 @@ namespace SourceGit.Git {
|
||||||
/// Line mode.
|
/// Line mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum LineMode {
|
public enum LineMode {
|
||||||
|
None,
|
||||||
Normal,
|
Normal,
|
||||||
Indicator,
|
Indicator,
|
||||||
Empty,
|
|
||||||
Added,
|
Added,
|
||||||
Deleted,
|
Deleted,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Side
|
/// Line change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum Side {
|
public class LineChange {
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Both,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Block
|
|
||||||
/// </summary>
|
|
||||||
public class Block {
|
|
||||||
public Side Side = Side.Both;
|
|
||||||
public LineMode Mode = LineMode.Normal;
|
public LineMode Mode = LineMode.Normal;
|
||||||
public int LeftStart = 0;
|
public string Content = "";
|
||||||
public int RightStart = 0;
|
public string OldLine = "";
|
||||||
public int Count = 0;
|
public string NewLine = "";
|
||||||
public StringBuilder Builder = new StringBuilder();
|
|
||||||
|
|
||||||
public bool IsLeftDelete => Side == Side.Left && Mode == LineMode.Deleted;
|
public LineChange(LineMode mode, string content, string oldLine = "", string newLine = "") {
|
||||||
public bool IsRightAdded => Side == Side.Right && Mode == LineMode.Added;
|
Mode = mode;
|
||||||
public bool IsBothSideNormal => Side == Side.Both && Mode == LineMode.Normal;
|
Content = content;
|
||||||
public bool CanShowNumber => Mode != LineMode.Indicator && Mode != LineMode.Empty;
|
OldLine = oldLine;
|
||||||
|
NewLine = newLine;
|
||||||
public void Append(string data) {
|
|
||||||
if (Count > 0) Builder.AppendLine();
|
|
||||||
Builder.Append(data);
|
|
||||||
Count++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text file change.
|
/// Text change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TextChange {
|
public class TextChange {
|
||||||
public bool IsValid = false;
|
public List<LineChange> Lines = new List<LineChange>();
|
||||||
public bool IsBinary = false;
|
public bool IsBinary = false;
|
||||||
public List<Block> Blocks = new List<Block>();
|
|
||||||
public int LeftLineCount = 0;
|
|
||||||
public int RightLineCount = 0;
|
|
||||||
|
|
||||||
public void SetBinary() {
|
|
||||||
IsValid = true;
|
|
||||||
IsBinary = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(Block b) {
|
|
||||||
if (b.Count == 0) return;
|
|
||||||
|
|
||||||
switch (b.Side) {
|
|
||||||
case Side.Left:
|
|
||||||
LeftLineCount += b.Count;
|
|
||||||
break;
|
|
||||||
case Side.Right:
|
|
||||||
RightLineCount += b.Count;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LeftLineCount += b.Count;
|
|
||||||
RightLineCount += b.Count;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Blocks.Add(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Fit() {
|
|
||||||
if (LeftLineCount > RightLineCount) {
|
|
||||||
var b = new Block();
|
|
||||||
b.Side = Side.Right;
|
|
||||||
b.Mode = LineMode.Empty;
|
|
||||||
|
|
||||||
var delta = LeftLineCount - RightLineCount;
|
|
||||||
for (int i = 0; i < delta; i++) b.Append("");
|
|
||||||
|
|
||||||
Add(b);
|
|
||||||
} else if (LeftLineCount < RightLineCount) {
|
|
||||||
var b = new Block();
|
|
||||||
b.Side = Side.Left;
|
|
||||||
b.Mode = LineMode.Empty;
|
|
||||||
|
|
||||||
var delta = RightLineCount - LeftLineCount;
|
|
||||||
for (int i = 0; i < delta; i++) b.Append("");
|
|
||||||
|
|
||||||
Add(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -136,103 +72,49 @@ namespace SourceGit.Git {
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static TextChange GetTextChange(Repository repo, string args) {
|
public static TextChange GetTextChange(Repository repo, string args) {
|
||||||
var rs = new TextChange();
|
var rs = new TextChange();
|
||||||
var current = new Block();
|
var started = false;
|
||||||
var left = 0;
|
var oldLine = 0;
|
||||||
var right = 0;
|
var newLine = 0;
|
||||||
|
|
||||||
repo.RunCommand($"diff --ignore-cr-at-eol {args}", line => {
|
repo.RunCommand($"diff --ignore-cr-at-eol {args}", line => {
|
||||||
if (rs.IsBinary) return;
|
if (rs.IsBinary) return;
|
||||||
|
|
||||||
if (!rs.IsValid) {
|
if (!started) {
|
||||||
var match = REG_INDICATOR.Match(line);
|
var match = REG_INDICATOR.Match(line);
|
||||||
if (!match.Success) {
|
if (!match.Success) {
|
||||||
if (line.StartsWith("Binary ")) rs.SetBinary();
|
if (line.StartsWith("Binary ")) rs.IsBinary = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
rs.IsValid = true;
|
started = true;
|
||||||
left = int.Parse(match.Groups[1].Value);
|
oldLine = int.Parse(match.Groups[1].Value);
|
||||||
right = int.Parse(match.Groups[2].Value);
|
newLine = int.Parse(match.Groups[2].Value);
|
||||||
current.Mode = LineMode.Indicator;
|
rs.Lines.Add(new LineChange(LineMode.Indicator, line));
|
||||||
current.Append(line);
|
|
||||||
} else {
|
} else {
|
||||||
if (line[0] == '-') {
|
if (line[0] == '-') {
|
||||||
if (current.IsLeftDelete) {
|
rs.Lines.Add(new LineChange(LineMode.Deleted, line.Substring(1), $"{oldLine}", ""));
|
||||||
current.Append(line.Substring(1));
|
oldLine++;
|
||||||
} else {
|
|
||||||
rs.Add(current);
|
|
||||||
|
|
||||||
current = new Block();
|
|
||||||
current.Side = Side.Left;
|
|
||||||
current.Mode = LineMode.Deleted;
|
|
||||||
current.LeftStart = left;
|
|
||||||
current.Append(line.Substring(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
left++;
|
|
||||||
} else if (line[0] == '+') {
|
} else if (line[0] == '+') {
|
||||||
if (current.IsRightAdded) {
|
rs.Lines.Add(new LineChange(LineMode.Added, line.Substring(1), "", $"{newLine}"));
|
||||||
current.Append(line.Substring(1));
|
newLine++;
|
||||||
} else {
|
|
||||||
rs.Add(current);
|
|
||||||
|
|
||||||
current = new Block();
|
|
||||||
current.Side = Side.Right;
|
|
||||||
current.Mode = LineMode.Added;
|
|
||||||
current.RightStart = right;
|
|
||||||
current.Append(line.Substring(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
right++;
|
|
||||||
} else if (line[0] == '\\') {
|
} else if (line[0] == '\\') {
|
||||||
var tmp = new Block();
|
rs.Lines.Add(new LineChange(LineMode.Indicator, line.Substring(1)));
|
||||||
tmp.Side = current.Side;
|
|
||||||
tmp.Mode = LineMode.Indicator;
|
|
||||||
tmp.Append(line.Substring(1));
|
|
||||||
|
|
||||||
rs.Add(current);
|
|
||||||
rs.Add(tmp);
|
|
||||||
rs.Fit();
|
|
||||||
|
|
||||||
current = new Block();
|
|
||||||
current.LeftStart = left;
|
|
||||||
current.RightStart = right;
|
|
||||||
} else {
|
} else {
|
||||||
var match = REG_INDICATOR.Match(line);
|
var match = REG_INDICATOR.Match(line);
|
||||||
if (match.Success) {
|
if (match.Success) {
|
||||||
rs.Add(current);
|
oldLine = int.Parse(match.Groups[1].Value);
|
||||||
rs.Fit();
|
newLine = int.Parse(match.Groups[2].Value);
|
||||||
|
rs.Lines.Add(new LineChange(LineMode.Indicator, line));
|
||||||
left = int.Parse(match.Groups[1].Value);
|
|
||||||
right = int.Parse(match.Groups[2].Value);
|
|
||||||
|
|
||||||
current = new Block();
|
|
||||||
current.Mode = LineMode.Indicator;
|
|
||||||
current.Append(line);
|
|
||||||
} else {
|
} else {
|
||||||
if (current.IsBothSideNormal) {
|
rs.Lines.Add(new LineChange(LineMode.Normal, line.Substring(1), $"{oldLine}", $"{newLine}"));
|
||||||
current.Append(line.Substring(1));
|
oldLine++;
|
||||||
} else {
|
newLine++;
|
||||||
rs.Add(current);
|
|
||||||
rs.Fit();
|
|
||||||
|
|
||||||
current = new Block();
|
|
||||||
current.LeftStart = left;
|
|
||||||
current.RightStart = right;
|
|
||||||
current.Append(line.Substring(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
left++;
|
|
||||||
right++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
rs.Add(current);
|
if (rs.IsBinary) rs.Lines.Clear();
|
||||||
rs.Fit();
|
|
||||||
|
|
||||||
if (rs.IsBinary) rs.Blocks.Clear();
|
|
||||||
return rs;
|
return rs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -945,7 +945,6 @@ namespace SourceGit.Git {
|
||||||
public Blame BlameFile(string file, string revision) {
|
public Blame BlameFile(string file, string revision) {
|
||||||
var regex = new Regex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)");
|
var regex = new Regex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)");
|
||||||
var blame = new Blame();
|
var blame = new Blame();
|
||||||
var current = null as Blame.Block;
|
|
||||||
|
|
||||||
var errs = RunCommand($"blame -t {revision} -- \"{file}\"", line => {
|
var errs = RunCommand($"blame -t {revision} -- \"{file}\"", line => {
|
||||||
if (blame.IsBinary) return;
|
if (blame.IsBinary) return;
|
||||||
|
@ -953,7 +952,7 @@ namespace SourceGit.Git {
|
||||||
|
|
||||||
if (line.IndexOf('\0') >= 0) {
|
if (line.IndexOf('\0') >= 0) {
|
||||||
blame.IsBinary = true;
|
blame.IsBinary = true;
|
||||||
blame.Blocks.Clear();
|
blame.Lines.Clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -961,25 +960,19 @@ namespace SourceGit.Git {
|
||||||
if (!match.Success) return;
|
if (!match.Success) return;
|
||||||
|
|
||||||
var commit = match.Groups[1].Value;
|
var commit = match.Groups[1].Value;
|
||||||
var data = match.Groups[4].Value;
|
var author = match.Groups[2].Value;
|
||||||
if (current != null && current.CommitSHA == commit) {
|
|
||||||
current.Content = current.Content + "\n" + data;
|
|
||||||
} else {
|
|
||||||
var timestamp = int.Parse(match.Groups[3].Value);
|
var timestamp = int.Parse(match.Groups[3].Value);
|
||||||
|
var data = match.Groups[4].Value;
|
||||||
var when = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
var when = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
current = new Blame.Block() {
|
var blameLine = new Blame.Line() {
|
||||||
CommitSHA = commit,
|
CommitSHA = commit,
|
||||||
Author = match.Groups[2].Value,
|
Author = author,
|
||||||
Time = when,
|
Time = when,
|
||||||
Content = data,
|
Content = data,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (current.Author == null) current.Author = "";
|
blame.Lines.Add(blameLine);
|
||||||
blame.Blocks.Add(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
blame.LineCount++;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (errs != null) App.RaiseError(errs);
|
if (errs != null) App.RaiseError(errs);
|
||||||
|
|
|
@ -107,80 +107,60 @@
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<Border Grid.Row="2" BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}" ClipToBounds="True">
|
<Border Grid.Row="2" x:Name="area" BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}" ClipToBounds="True" SizeChanged="ContentSizeChanged">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.Resources>
|
||||||
<ColumnDefinition Width="4"/>
|
<Style x:Key="Style.DataGridText.LineNumber" TargetType="{x:Type TextBlock}">
|
||||||
<ColumnDefinition Width="Auto"/>
|
<Setter Property="FontFamily" Value="Consolas"/>
|
||||||
<ColumnDefinition Width="4"/>
|
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||||
<ColumnDefinition Width="1"/>
|
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
</Grid.ColumnDefinitions>
|
<Setter Property="Padding" Value="8,0"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<ItemsControl
|
<Style x:Key="Style.DataGridText.Block" TargetType="{x:Type TextBlock}">
|
||||||
Grid.Column="1"
|
<Setter Property="FontFamily" Value="Consolas"/>
|
||||||
x:Name="lineNumber"
|
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||||
Padding="0"
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
Margin="0"
|
<Setter Property="Padding" Value="0"/>
|
||||||
BorderThickness="0"
|
</Style>
|
||||||
Background="Transparent"
|
</Grid.Resources>
|
||||||
VirtualizingPanel.ScrollUnit="Item"
|
|
||||||
VirtualizingPanel.IsVirtualizing="True"
|
|
||||||
VirtualizingPanel.VirtualizationMode="Recycling">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<VirtualizingStackPanel Orientation="Vertical"/>
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
|
|
||||||
<ItemsControl.Template>
|
<!-- Blame -->
|
||||||
<ControlTemplate TargetType="{x:Type ItemsControl}">
|
<DataGrid
|
||||||
<ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
|
Grid.Column="0"
|
||||||
<ItemsPresenter/>
|
x:Name="blame"
|
||||||
</ScrollViewer>
|
GridLinesVisibility="Vertical"
|
||||||
</ControlTemplate>
|
VerticalGridLinesBrush="{StaticResource Brush.Border2}"
|
||||||
</ItemsControl.Template>
|
FrozenColumnCount="1"
|
||||||
|
SelectionUnit="FullRow"
|
||||||
|
SelectionMode="Single">
|
||||||
|
|
||||||
<ItemsControl.ItemTemplate>
|
<DataGrid.RowStyle>
|
||||||
|
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||||
|
<EventSetter Event="RequestBringIntoView" Handler="OnBlameRequestBringIntoView"/>
|
||||||
|
<EventSetter Event="ContextMenuOpening" Handler="OnBlameContextMenuOpening"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.RowStyle>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Width="Auto" IsReadOnly="True" Binding="{Binding LineNumber}" ElementStyle="{StaticResource Style.DataGridText.LineNumber}"/>
|
||||||
|
|
||||||
|
<DataGridTemplateColumn Width="1" MinWidth="1" IsReadOnly="True">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding .}" Padding="0" Margin="0" FontFamily="Consolas" FontSize="13" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
|
<Grid>
|
||||||
|
<Border Background="{Binding BG}" BorderThickness="0"/>
|
||||||
|
<TextBlock Text="{Binding Line.Content}" Background="Transparent" Foreground="{StaticResource Brush.FG}" FontFamily="Consolas" FontSize="12" Margin="0" Padding="0"/>
|
||||||
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</ItemsControl>
|
</DataGridTemplateColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
<Rectangle Grid.Column="3" Width="1" Fill="{StaticResource Brush.Border2}"/>
|
</DataGrid>
|
||||||
|
|
||||||
<RichTextBox
|
|
||||||
x:Name="content"
|
|
||||||
Grid.Column="4"
|
|
||||||
AcceptsReturn="True"
|
|
||||||
AcceptsTab="True"
|
|
||||||
IsReadOnly="True"
|
|
||||||
BorderThickness="0"
|
|
||||||
Background="Transparent"
|
|
||||||
Foreground="{StaticResource Brush.FG}"
|
|
||||||
Height="Auto"
|
|
||||||
FontSize="13"
|
|
||||||
HorizontalScrollBarVisibility="Auto"
|
|
||||||
VerticalScrollBarVisibility="Auto"
|
|
||||||
RenderOptions.ClearTypeHint="Enabled"
|
|
||||||
ScrollViewer.ScrollChanged="SyncScrollChanged"
|
|
||||||
PreviewMouseWheel="MouseWheelOnContent"
|
|
||||||
SizeChanged="ContentSizeChanged"
|
|
||||||
SelectionChanged="ContentSelectionChanged"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
FontFamily="Consolas">
|
|
||||||
<RichTextBox.ContextMenu>
|
|
||||||
<ContextMenu>
|
|
||||||
<MenuItem Command="ApplicationCommands.Copy"/>
|
|
||||||
</ContextMenu>
|
|
||||||
</RichTextBox.ContextMenu>
|
|
||||||
<FlowDocument PageWidth="0"/>
|
|
||||||
</RichTextBox>
|
|
||||||
|
|
||||||
<!-- Loading tip -->
|
<!-- Loading tip -->
|
||||||
<Path x:Name="loading" Grid.ColumnSpan="5" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
<Path x:Name="loading" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||||
<Path.RenderTransform>
|
<Path.RenderTransform>
|
||||||
<RotateTransform Angle="0"/>
|
<RotateTransform Angle="0"/>
|
||||||
</Path.RenderTransform>
|
</Path.RenderTransform>
|
||||||
|
@ -197,7 +177,7 @@
|
||||||
</Path>
|
</Path>
|
||||||
|
|
||||||
<!-- Popup to show commit info -->
|
<!-- Popup to show commit info -->
|
||||||
<Popup x:Name="popup" Grid.ColumnSpan="5" Placement="MousePoint" IsOpen="False" StaysOpen="False" Focusable="True">
|
<Popup x:Name="popup" Placement="MousePoint" IsOpen="False" StaysOpen="False" Focusable="True">
|
||||||
<Border BorderBrush="{StaticResource Brush.Accent1}" BorderThickness="1" Background="{StaticResource Brush.BG1}">
|
<Border BorderBrush="{StaticResource Brush.Accent1}" BorderThickness="1" Background="{StaticResource Brush.BG1}">
|
||||||
<Grid Margin="4">
|
<Grid Margin="4">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
@ -15,6 +14,9 @@ namespace SourceGit.UI {
|
||||||
/// Viewer to show git-blame
|
/// Viewer to show git-blame
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class Blame : Window {
|
public partial class Blame : Window {
|
||||||
|
private Git.Repository repo = null;
|
||||||
|
private string lastSHA = null;
|
||||||
|
private int lastBG = 1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Background color for blocks.
|
/// Background color for blocks.
|
||||||
|
@ -24,17 +26,24 @@ namespace SourceGit.UI {
|
||||||
new SolidColorBrush(Color.FromArgb(128, 0, 0, 0))
|
new SolidColorBrush(Color.FromArgb(128, 0, 0, 0))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Record
|
||||||
|
/// </summary>
|
||||||
|
public class Record {
|
||||||
|
public Git.Blame.Line Line { get; set; }
|
||||||
|
public Brush BG { get; set; }
|
||||||
|
public int LineNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="repo"></param>
|
/// <param name="open"></param>
|
||||||
/// <param name="file"></param>
|
/// <param name="file"></param>
|
||||||
/// <param name="revision"></param>
|
/// <param name="revision"></param>
|
||||||
public Blame(Git.Repository repo, string file, string revision) {
|
public Blame(Git.Repository open, string file, string revision) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
double minWidth = content.ActualWidth;
|
|
||||||
|
|
||||||
// Move to center.
|
// Move to center.
|
||||||
var parent = App.Current.MainWindow;
|
var parent = App.Current.MainWindow;
|
||||||
Left = parent.Left + (parent.Width - Width) * 0.5;
|
Left = parent.Left + (parent.Width - Width) * 0.5;
|
||||||
|
@ -48,97 +57,57 @@ namespace SourceGit.UI {
|
||||||
|
|
||||||
// Layout content
|
// Layout content
|
||||||
blameFile.Content = $"{file}@{revision.Substring(0, 8)}";
|
blameFile.Content = $"{file}@{revision.Substring(0, 8)}";
|
||||||
|
repo = open;
|
||||||
|
|
||||||
Task.Run(() => {
|
Task.Run(() => {
|
||||||
var blame = repo.BlameFile(file, revision);
|
var result = repo.BlameFile(file, revision);
|
||||||
|
var records = new List<Record>();
|
||||||
|
|
||||||
|
if (result.IsBinary) {
|
||||||
|
var error = new Record();
|
||||||
|
error.Line = new Git.Blame.Line() { Content = "BINARY FILE BLAME NOT SUPPORTED!!!", CommitSHA = null };
|
||||||
|
error.BG = Brushes.Red;
|
||||||
|
error.LineNumber = 0;
|
||||||
|
records.Add(error);
|
||||||
|
} else {
|
||||||
|
int count = 1;
|
||||||
|
foreach (var line in result.Lines) {
|
||||||
|
var r = new Record();
|
||||||
|
r.Line = line;
|
||||||
|
r.BG = GetBG(line);
|
||||||
|
r.LineNumber = count;
|
||||||
|
|
||||||
|
records.Add(r);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Dispatcher.Invoke(() => {
|
Dispatcher.Invoke(() => {
|
||||||
content.Document.Blocks.Clear();
|
|
||||||
|
|
||||||
if (blame.IsBinary) {
|
|
||||||
lineNumber.ItemsSource = null;
|
|
||||||
|
|
||||||
Paragraph p = new Paragraph(new Run("BINARY FILE BLAME NOT SUPPORTED!!!"));
|
|
||||||
p.Margin = new Thickness(0);
|
|
||||||
p.Padding = new Thickness(0);
|
|
||||||
p.LineHeight = 1;
|
|
||||||
p.Background = Brushes.Transparent;
|
|
||||||
p.Foreground = FindResource("Brush.FG") as SolidColorBrush;
|
|
||||||
p.FontStyle = FontStyles.Normal;
|
|
||||||
|
|
||||||
content.Document.Blocks.Add(p);
|
|
||||||
} else {
|
|
||||||
List<string> numbers = new List<string>();
|
|
||||||
for (int i = 0; i < blame.LineCount; i++) numbers.Add(i.ToString());
|
|
||||||
lineNumber.ItemsSource = numbers;
|
|
||||||
|
|
||||||
var fg = FindResource("Brush.FG") as SolidColorBrush;
|
|
||||||
var tf = new Typeface(content.FontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
|
|
||||||
var ns = new NumberSubstitution();
|
|
||||||
var mp = new Thickness(0);
|
|
||||||
|
|
||||||
for (int i = 0; i < blame.Blocks.Count; i++) {
|
|
||||||
var frag = blame.Blocks[i];
|
|
||||||
var idx = i;
|
|
||||||
|
|
||||||
Paragraph p = new Paragraph(new Run(frag.Content));
|
|
||||||
p.DataContext = frag;
|
|
||||||
p.Margin = mp;
|
|
||||||
p.Padding = mp;
|
|
||||||
p.LineHeight = 1;
|
|
||||||
p.Background = BG[i % 2];
|
|
||||||
p.Foreground = fg;
|
|
||||||
p.FontStyle = FontStyles.Normal;
|
|
||||||
p.ContextMenuOpening += (sender, ev) => {
|
|
||||||
if (!content.Selection.IsEmpty) return;
|
|
||||||
|
|
||||||
Hyperlink link = new Hyperlink(new Run(frag.CommitSHA));
|
|
||||||
link.ToolTip = "CLICK TO GO";
|
|
||||||
link.Click += (o, e) => {
|
|
||||||
repo.OnNavigateCommit?.Invoke(frag.CommitSHA);
|
|
||||||
e.Handled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var block in content.Document.Blocks) {
|
|
||||||
var paragraph = block as Paragraph;
|
|
||||||
if ((paragraph.DataContext as Git.Blame.Block).CommitSHA == frag.CommitSHA) {
|
|
||||||
paragraph.Background = Brushes.Green;
|
|
||||||
} else {
|
|
||||||
paragraph.Background = BG[i % 2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commitID.Content = link;
|
|
||||||
authorName.Content = frag.Author;
|
|
||||||
authorTime.Content = frag.Time;
|
|
||||||
popup.IsOpen = true;
|
|
||||||
ev.Handled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
var formatter = new FormattedText(
|
|
||||||
frag.Content,
|
|
||||||
CultureInfo.CurrentUICulture,
|
|
||||||
FlowDirection.LeftToRight,
|
|
||||||
tf,
|
|
||||||
content.FontSize,
|
|
||||||
Brushes.Black,
|
|
||||||
ns,
|
|
||||||
TextFormattingMode.Ideal);
|
|
||||||
if (minWidth < formatter.Width) {
|
|
||||||
content.Document.PageWidth = formatter.Width + 16;
|
|
||||||
minWidth = formatter.Width;
|
|
||||||
}
|
|
||||||
|
|
||||||
content.Document.Blocks.Add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide loading.
|
|
||||||
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||||
loading.Visibility = Visibility.Collapsed;
|
loading.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
blame.ItemsSource = records;
|
||||||
|
blame.UpdateLayout();
|
||||||
|
|
||||||
|
ContentSizeChanged(null, null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get background brush.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private Brush GetBG(Git.Blame.Line line) {
|
||||||
|
if (lastSHA != line.CommitSHA) {
|
||||||
|
lastSHA = line.CommitSHA;
|
||||||
|
lastBG = 1 - lastBG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BG[lastBG];
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Click logo
|
/// Click logo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -177,68 +146,83 @@ namespace SourceGit.UI {
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sync scroll
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender"></param>
|
|
||||||
/// <param name="e"></param>
|
|
||||||
private void SyncScrollChanged(object sender, ScrollChangedEventArgs e) {
|
|
||||||
if (e.VerticalChange != 0) {
|
|
||||||
var margin = new Thickness(4, -e.VerticalOffset, 4, 0);
|
|
||||||
lineNumber.Margin = margin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mouse wheel
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender"></param>
|
|
||||||
/// <param name="e"></param>
|
|
||||||
private void MouseWheelOnContent(object sender, MouseWheelEventArgs e) {
|
|
||||||
if (e.Delta > 0) {
|
|
||||||
content.LineUp();
|
|
||||||
} else {
|
|
||||||
content.LineDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Content size changed.
|
/// Content size changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender"></param>
|
/// <param name="sender"></param>
|
||||||
/// <param name="e"></param>
|
/// <param name="e"></param>
|
||||||
private void ContentSizeChanged(object sender, SizeChangedEventArgs e) {
|
private void ContentSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||||
if (content.Document.PageWidth < content.ActualWidth) {
|
var total = area.ActualWidth;
|
||||||
content.Document.PageWidth = content.ActualWidth;
|
var offset = blame.NonFrozenColumnsViewportHorizontalOffset;
|
||||||
}
|
var minWidth = total - offset - 2;
|
||||||
|
|
||||||
|
var scroller = GetVisualChild<ScrollViewer>(blame);
|
||||||
|
if (scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8;
|
||||||
|
|
||||||
|
blame.Columns[1].MinWidth = minWidth;
|
||||||
|
blame.Columns[1].Width = DataGridLength.SizeToCells;
|
||||||
|
blame.UpdateLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Auto scroll when selection changed.
|
/// Context menu opening.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender"></param>
|
/// <param name="sender"></param>
|
||||||
/// <param name="e"></param>
|
/// <param name="e"></param>
|
||||||
private void ContentSelectionChanged(object sender, RoutedEventArgs e) {
|
private void OnBlameContextMenuOpening(object sender, ContextMenuEventArgs ev) {
|
||||||
var doc = sender as RichTextBox;
|
var item = sender as DataGridRow;
|
||||||
if (doc == null || doc.IsFocused == false) return;
|
if (item == null) return;
|
||||||
|
|
||||||
if (Mouse.LeftButton == MouseButtonState.Pressed && !doc.Selection.IsEmpty) {
|
var record = item.DataContext as Record;
|
||||||
var p = Mouse.GetPosition(doc);
|
if (record == null || record.Line.CommitSHA == null) return;
|
||||||
|
|
||||||
if (p.X <= 8) {
|
Hyperlink link = new Hyperlink(new Run(record.Line.CommitSHA));
|
||||||
doc.LineLeft();
|
link.ToolTip = "CLICK TO GO";
|
||||||
} else if (p.X >= doc.ActualWidth - 8) {
|
link.Click += (o, e) => {
|
||||||
doc.LineRight();
|
repo.OnNavigateCommit?.Invoke(record.Line.CommitSHA);
|
||||||
|
e.Handled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
commitID.Content = link;
|
||||||
|
authorName.Content = record.Line.Author;
|
||||||
|
authorTime.Content = record.Line.Time;
|
||||||
|
popup.IsOpen = true;
|
||||||
|
ev.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p.Y <= 8) {
|
/// <summary>
|
||||||
doc.LineUp();
|
/// Prevent auto scroll.
|
||||||
} else if (p.Y >= doc.ActualHeight - 8) {
|
/// </summary>
|
||||||
doc.LineDown();
|
/// <param name="sender"></param>
|
||||||
}
|
/// <param name="e"></param>
|
||||||
}
|
private void OnBlameRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find child element of type.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="parent"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private T GetVisualChild<T>(DependencyObject parent) where T : Visual {
|
||||||
|
T child = null;
|
||||||
|
|
||||||
|
int count = VisualTreeHelper.GetChildrenCount(parent);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
|
||||||
|
child = v as T;
|
||||||
|
|
||||||
|
if (child == null) {
|
||||||
|
child = GetVisualChild<T>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,6 +299,7 @@
|
||||||
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
||||||
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
|
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
|
||||||
<EventSetter Event="ContextMenuOpening" Handler="TreeContextMenuOpening"/>
|
<EventSetter Event="ContextMenuOpening" Handler="TreeContextMenuOpening"/>
|
||||||
|
<EventSetter Event="RequestBringIntoView" Handler="TreeRequestBringIntoView"/>
|
||||||
</Style>
|
</Style>
|
||||||
</TreeView.ItemContainerStyle>
|
</TreeView.ItemContainerStyle>
|
||||||
<TreeView.ItemTemplate>
|
<TreeView.ItemTemplate>
|
||||||
|
@ -388,6 +389,7 @@
|
||||||
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
||||||
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
|
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
|
||||||
<EventSetter Event="ContextMenuOpening" Handler="TreeContextMenuOpening"/>
|
<EventSetter Event="ContextMenuOpening" Handler="TreeContextMenuOpening"/>
|
||||||
|
<EventSetter Event="RequestBringIntoView" Handler="TreeRequestBringIntoView"/>
|
||||||
</Style>
|
</Style>
|
||||||
</TreeView.ItemContainerStyle>
|
</TreeView.ItemContainerStyle>
|
||||||
<TreeView.ItemTemplate>
|
<TreeView.ItemTemplate>
|
||||||
|
|
|
@ -551,7 +551,10 @@ namespace SourceGit.UI {
|
||||||
menu.IsOpen = true;
|
menu.IsOpen = true;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
private void TreeRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,29 @@
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
FontFamily="Consolas">
|
FontFamily="Consolas">
|
||||||
<Border BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}">
|
<Border BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}">
|
||||||
<Grid>
|
<Grid x:Name="area" SizeChanged="OnSizeChanged">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="26"/>
|
<RowDefinition Height="26"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.Resources>
|
||||||
|
<Style x:Key="Style.DataGridText.LineNumber" TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="FontFamily" Value="Consolas"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Padding" Value="8,0"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Style.DataGridText.Block" TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="FontFamily" Value="Consolas"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
|
</Style>
|
||||||
|
</Grid.Resources>
|
||||||
|
|
||||||
<Border Grid.Row="0" BorderBrush="{StaticResource Brush.Border2}" BorderThickness="0,0,0,1">
|
<Border Grid.Row="0" BorderBrush="{StaticResource Brush.Border2}" BorderThickness="0,0,0,1">
|
||||||
<Grid Margin="8,4">
|
<Grid Margin="8,4">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
@ -52,139 +69,105 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Grid x:Name="textChange" Grid.Row="1" ClipToBounds="True">
|
<Grid Grid.Row="1" x:Name="textChangeTwoSides">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="4"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="4"/>
|
|
||||||
<ColumnDefinition Width="1"/>
|
|
||||||
<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>
|
||||||
|
|
||||||
<ItemsControl
|
<DataGrid
|
||||||
Grid.Column="1"
|
Grid.Column="0"
|
||||||
x:Name="leftLineNumber"
|
x:Name="textChangeOldSide"
|
||||||
Padding="0"
|
GridLinesVisibility="Vertical"
|
||||||
Margin="0"
|
VerticalGridLinesBrush="{StaticResource Brush.Border2}"
|
||||||
BorderThickness="0"
|
FrozenColumnCount="1"
|
||||||
Background="Transparent"
|
ScrollViewer.ScrollChanged="OnTwoSidesScroll"
|
||||||
VirtualizingPanel.ScrollUnit="Item"
|
ContextMenuOpening="OnTextChangeContextMenuOpening">
|
||||||
VirtualizingPanel.IsVirtualizing="True"
|
|
||||||
VirtualizingPanel.VirtualizationMode="Recycling">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<VirtualizingStackPanel Orientation="Vertical"/>
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
|
|
||||||
<ItemsControl.Template>
|
<DataGrid.RowStyle>
|
||||||
<ControlTemplate TargetType="{x:Type ItemsControl}">
|
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||||
<ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
|
<EventSetter Event="RequestBringIntoView" Handler="OnLineRequestBringIntoView"/>
|
||||||
<ItemsPresenter/>
|
</Style>
|
||||||
</ScrollViewer>
|
</DataGrid.RowStyle>
|
||||||
</ControlTemplate>
|
|
||||||
</ItemsControl.Template>
|
|
||||||
|
|
||||||
<ItemsControl.ItemTemplate>
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Width="Auto" IsReadOnly="True" Binding="{Binding OldLine}" ElementStyle="{StaticResource Style.DataGridText.LineNumber}"/>
|
||||||
|
|
||||||
|
<DataGridTemplateColumn Width="Auto" IsReadOnly="True">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding .}" Padding="0" Margin="0" FontFamily="Consolas" FontSize="13" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
|
<Grid>
|
||||||
</DataTemplate>
|
<Border Background="{Binding BG}" BorderThickness="0"/>
|
||||||
</ItemsControl.ItemTemplate>
|
<TextBlock Text="{Binding Content}" Background="Transparent" Foreground="{Binding FG}" FontFamily="Consolas" FontSize="12" FontStyle="{Binding Style}" Margin="0" Padding="0"/>
|
||||||
</ItemsControl>
|
|
||||||
|
|
||||||
<Rectangle Grid.Column="3" Width="1" Fill="{StaticResource Brush.Border2}"/>
|
|
||||||
|
|
||||||
<RichTextBox
|
|
||||||
x:Name="leftText"
|
|
||||||
Grid.Column="4"
|
|
||||||
AcceptsReturn="True"
|
|
||||||
AcceptsTab="True"
|
|
||||||
IsReadOnly="True"
|
|
||||||
BorderThickness="0"
|
|
||||||
Background="Transparent"
|
|
||||||
Foreground="{StaticResource Brush.FG}"
|
|
||||||
Height="Auto"
|
|
||||||
FontSize="13"
|
|
||||||
HorizontalScrollBarVisibility="Auto"
|
|
||||||
VerticalScrollBarVisibility="Auto"
|
|
||||||
RenderOptions.ClearTypeHint="Enabled"
|
|
||||||
ScrollViewer.ScrollChanged="OnViewerScroll"
|
|
||||||
PreviewMouseWheel="OnViewerMouseWheel"
|
|
||||||
SizeChanged="OnSizeChanged"
|
|
||||||
SelectionChanged="OnViewerSelectionChanged"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch">
|
|
||||||
<RichTextBox.Document>
|
|
||||||
<FlowDocument PageWidth="0"/>
|
|
||||||
</RichTextBox.Document>
|
|
||||||
</RichTextBox>
|
|
||||||
|
|
||||||
<Rectangle Grid.Column="5" Width="1" Fill="{StaticResource Brush.Border2}"/>
|
|
||||||
|
|
||||||
<ItemsControl
|
|
||||||
Grid.Column="7"
|
|
||||||
x:Name="rightLineNumber"
|
|
||||||
Padding="0"
|
|
||||||
Margin="0"
|
|
||||||
BorderThickness="0"
|
|
||||||
Background="Transparent"
|
|
||||||
VirtualizingPanel.ScrollUnit="Item"
|
|
||||||
VirtualizingPanel.IsVirtualizing="True"
|
|
||||||
VirtualizingPanel.VirtualizationMode="Recycling">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<VirtualizingStackPanel Orientation="Vertical"/>
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
|
|
||||||
<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
|
|
||||||
x:Name="rightText"
|
|
||||||
Grid.Column="10"
|
|
||||||
AcceptsReturn="True"
|
|
||||||
AcceptsTab="True"
|
|
||||||
IsReadOnly="True"
|
|
||||||
BorderThickness="0"
|
|
||||||
Background="Transparent"
|
|
||||||
Foreground="{StaticResource Brush.FG}"
|
|
||||||
Height="Auto"
|
|
||||||
FontSize="13"
|
|
||||||
HorizontalScrollBarVisibility="Auto"
|
|
||||||
VerticalScrollBarVisibility="Auto"
|
|
||||||
RenderOptions.ClearTypeHint="Enabled"
|
|
||||||
ScrollViewer.ScrollChanged="OnViewerScroll"
|
|
||||||
PreviewMouseWheel="OnViewerMouseWheel"
|
|
||||||
SizeChanged="OnSizeChanged"
|
|
||||||
SelectionChanged="OnViewerSelectionChanged"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch">
|
|
||||||
<RichTextBox.Document>
|
|
||||||
<FlowDocument PageWidth="0"/>
|
|
||||||
</RichTextBox.Document>
|
|
||||||
</RichTextBox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
|
<DataGrid
|
||||||
|
Grid.Column="1"
|
||||||
|
x:Name="textChangeNewSide"
|
||||||
|
GridLinesVisibility="Vertical"
|
||||||
|
VerticalGridLinesBrush="{StaticResource Brush.Border2}"
|
||||||
|
FrozenColumnCount="1"
|
||||||
|
ScrollViewer.ScrollChanged="OnTwoSidesScroll"
|
||||||
|
ContextMenuOpening="OnTextChangeContextMenuOpening">
|
||||||
|
|
||||||
|
<DataGrid.RowStyle>
|
||||||
|
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||||
|
<EventSetter Event="RequestBringIntoView" Handler="OnLineRequestBringIntoView"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.RowStyle>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Width="Auto" IsReadOnly="True" Binding="{Binding NewLine}" ElementStyle="{StaticResource Style.DataGridText.LineNumber}"/>
|
||||||
|
|
||||||
|
<DataGridTemplateColumn Width="Auto" IsReadOnly="True">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid>
|
||||||
|
<Border Background="{Binding BG}" BorderThickness="0"/>
|
||||||
|
<TextBlock Text="{Binding Content}" Background="Transparent" Foreground="{Binding FG}" FontFamily="Consolas" FontSize="12" FontStyle="{Binding Style}" Margin="0" Padding="0"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<DataGrid
|
||||||
|
Grid.Row="1"
|
||||||
|
x:Name="textChangeOneSide"
|
||||||
|
GridLinesVisibility="Vertical"
|
||||||
|
VerticalGridLinesBrush="{StaticResource Brush.Border2}"
|
||||||
|
FrozenColumnCount="2"
|
||||||
|
ContextMenuOpening="OnTextChangeContextMenuOpening">
|
||||||
|
|
||||||
|
<DataGrid.RowStyle>
|
||||||
|
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||||
|
<EventSetter Event="RequestBringIntoView" Handler="OnLineRequestBringIntoView"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.RowStyle>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Width="Auto" IsReadOnly="True" Binding="{Binding OldLine}" ElementStyle="{StaticResource Style.DataGridText.LineNumber}"/>
|
||||||
|
<DataGridTextColumn Width="Auto" IsReadOnly="True" Binding="{Binding NewLine}" ElementStyle="{StaticResource Style.DataGridText.LineNumber}"/>
|
||||||
|
|
||||||
|
<DataGridTemplateColumn Width="Auto" IsReadOnly="True">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid>
|
||||||
|
<Border Background="{Binding BG}" BorderThickness="0"/>
|
||||||
|
<TextBlock Text="{Binding Content}" Background="Transparent" Foreground="{Binding FG}" FontFamily="Consolas" FontSize="12" FontStyle="{Binding Style}" Margin="0" Padding="0"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
<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">
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
namespace SourceGit.UI {
|
namespace SourceGit.UI {
|
||||||
|
|
||||||
|
@ -14,8 +13,11 @@ namespace SourceGit.UI {
|
||||||
/// Viewer for git diff
|
/// Viewer for git diff
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class DiffViewer : UserControl {
|
public partial class DiffViewer : UserControl {
|
||||||
private double minWidth = 0;
|
private List<Git.Diff.LineChange> lineChanges = null;
|
||||||
private Git.Diff.TextChange textChangeData = null;
|
private Brush bgEmpty = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
|
||||||
|
private Brush bgAdded = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
|
||||||
|
private Brush bgDeleted = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
|
||||||
|
private Brush bgNormal = Brushes.Transparent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Diff options.
|
/// Diff options.
|
||||||
|
@ -27,6 +29,19 @@ namespace SourceGit.UI {
|
||||||
public string ExtraArgs = "";
|
public string ExtraArgs = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Change block.
|
||||||
|
/// </summary>
|
||||||
|
public class ChangeBlock {
|
||||||
|
public string Content { get; set; }
|
||||||
|
public Git.Diff.LineMode Mode { get; set; }
|
||||||
|
public Brush BG { get; set; }
|
||||||
|
public Brush FG { get; set; }
|
||||||
|
public FontStyle Style { get; set; }
|
||||||
|
public string OldLine { get; set; }
|
||||||
|
public string NewLine { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -50,11 +65,12 @@ 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;
|
lineChanges = null;
|
||||||
|
|
||||||
loading.Visibility = Visibility.Visible;
|
loading.Visibility = Visibility.Visible;
|
||||||
mask.Visibility = Visibility.Collapsed;
|
mask.Visibility = Visibility.Collapsed;
|
||||||
textChange.Visibility = Visibility.Collapsed;
|
textChangeOneSide.Visibility = Visibility.Collapsed;
|
||||||
|
textChangeTwoSides.Visibility = Visibility.Collapsed;
|
||||||
sizeChange.Visibility = Visibility.Collapsed;
|
sizeChange.Visibility = Visibility.Collapsed;
|
||||||
noChange.Visibility = Visibility.Collapsed;
|
noChange.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
@ -82,8 +98,8 @@ namespace SourceGit.UI {
|
||||||
var rs = Git.Diff.GetTextChange(repo, args);
|
var rs = Git.Diff.GetTextChange(repo, args);
|
||||||
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.Lines.Count > 0) {
|
||||||
textChangeData = rs;
|
lineChanges = rs.Lines;
|
||||||
SetTextChange();
|
SetTextChange();
|
||||||
} else {
|
} else {
|
||||||
SetSame();
|
SetSame();
|
||||||
|
@ -112,43 +128,101 @@ namespace SourceGit.UI {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rs"></param>
|
/// <param name="rs"></param>
|
||||||
private void SetTextChange() {
|
private void SetTextChange() {
|
||||||
if (textChangeData == null) return;
|
if (lineChanges == null) return;
|
||||||
|
|
||||||
|
var fgCommon = FindResource("Brush.FG") as Brush;
|
||||||
|
var fgIndicator = FindResource("Brush.FG2") as Brush;
|
||||||
|
|
||||||
|
if (App.Preference.UIUseOneSideDiff) {
|
||||||
|
var blocks = new List<ChangeBlock>();
|
||||||
|
|
||||||
|
foreach (var line in lineChanges) {
|
||||||
|
var block = new ChangeBlock();
|
||||||
|
block.Content = line.Content;
|
||||||
|
block.Mode = line.Mode;
|
||||||
|
block.BG = GetLineBackground(line);
|
||||||
|
block.FG = line.Mode == Git.Diff.LineMode.Indicator ? fgIndicator : fgCommon;
|
||||||
|
block.Style = line.Mode == Git.Diff.LineMode.Indicator ? FontStyles.Italic : FontStyles.Normal;
|
||||||
|
block.OldLine = line.OldLine;
|
||||||
|
block.NewLine = line.NewLine;
|
||||||
|
|
||||||
|
blocks.Add(block);
|
||||||
|
}
|
||||||
|
|
||||||
Dispatcher.Invoke(() => {
|
Dispatcher.Invoke(() => {
|
||||||
loading.Visibility = Visibility.Collapsed;
|
loading.Visibility = Visibility.Collapsed;
|
||||||
textChange.Visibility = Visibility.Visible;
|
|
||||||
textChangeOptions.Visibility = Visibility.Visible;
|
textChangeOptions.Visibility = Visibility.Visible;
|
||||||
|
textChangeOneSide.Visibility = Visibility.Visible;
|
||||||
|
textChangeTwoSides.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
if (App.Preference.UIUseOneSideDiff) {
|
ResetDataGrid(textChangeOneSide);
|
||||||
twoSideLeft.Width = new GridLength(0);
|
textChangeOneSide.ItemsSource = blocks;
|
||||||
twoSideLeft.MinWidth = 0;
|
OnSizeChanged(null, null);
|
||||||
twoSideSplittter.Width = new GridLength(0);
|
});
|
||||||
} else {
|
} else {
|
||||||
twoSideLeft.Width = new GridLength(1, GridUnitType.Star);
|
var oldSideBlocks = new List<ChangeBlock>();
|
||||||
twoSideLeft.MinWidth = 100;
|
var newSideBlocks = new List<ChangeBlock>();
|
||||||
twoSideSplittter.Width = new GridLength(2);
|
|
||||||
|
foreach (var line in lineChanges) {
|
||||||
|
var block = new ChangeBlock();
|
||||||
|
block.Content = line.Content;
|
||||||
|
block.Mode = line.Mode;
|
||||||
|
block.BG = GetLineBackground(line);
|
||||||
|
block.FG = line.Mode == Git.Diff.LineMode.Indicator ? fgIndicator : fgCommon;
|
||||||
|
block.Style = line.Mode == Git.Diff.LineMode.Indicator ? FontStyles.Italic : FontStyles.Normal;
|
||||||
|
block.OldLine = line.OldLine;
|
||||||
|
block.NewLine = line.NewLine;
|
||||||
|
|
||||||
|
switch (line.Mode) {
|
||||||
|
case Git.Diff.LineMode.Added:
|
||||||
|
newSideBlocks.Add(block);
|
||||||
|
|
||||||
|
var oldEmpty = new ChangeBlock();
|
||||||
|
oldEmpty.Content = "";
|
||||||
|
oldEmpty.Mode = Git.Diff.LineMode.None;
|
||||||
|
oldEmpty.BG = bgEmpty;
|
||||||
|
oldEmpty.FG = fgCommon;
|
||||||
|
oldEmpty.Style = FontStyles.Normal;
|
||||||
|
oldEmpty.OldLine = block.OldLine;
|
||||||
|
oldEmpty.NewLine = block.NewLine;
|
||||||
|
oldSideBlocks.Add(oldEmpty);
|
||||||
|
break;
|
||||||
|
case Git.Diff.LineMode.Deleted:
|
||||||
|
oldSideBlocks.Add(block);
|
||||||
|
|
||||||
|
var newEmpty = new ChangeBlock();
|
||||||
|
newEmpty.Content = "";
|
||||||
|
newEmpty.Mode = Git.Diff.LineMode.None;
|
||||||
|
newEmpty.BG = bgEmpty;
|
||||||
|
newEmpty.FG = fgCommon;
|
||||||
|
newEmpty.Style = FontStyles.Normal;
|
||||||
|
newEmpty.OldLine = block.OldLine;
|
||||||
|
newEmpty.NewLine = block.NewLine;
|
||||||
|
newSideBlocks.Add(newEmpty);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
oldSideBlocks.Add(block);
|
||||||
|
newSideBlocks.Add(block);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16;
|
Dispatcher.Invoke(() => {
|
||||||
|
loading.Visibility = Visibility.Collapsed;
|
||||||
|
textChangeOptions.Visibility = Visibility.Visible;
|
||||||
|
textChangeOneSide.Visibility = Visibility.Collapsed;
|
||||||
|
textChangeTwoSides.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
leftLineNumber.ItemsSource = null;
|
ResetDataGrid(textChangeOldSide);
|
||||||
rightLineNumber.ItemsSource = null;
|
ResetDataGrid(textChangeNewSide);
|
||||||
|
|
||||||
leftText.Document.Blocks.Clear();
|
textChangeOldSide.ItemsSource = oldSideBlocks;
|
||||||
rightText.Document.Blocks.Clear();
|
textChangeNewSide.ItemsSource = newSideBlocks;
|
||||||
|
|
||||||
var lLineNumbers = new List<string>();
|
OnSizeChanged(null, null);
|
||||||
var rLineNumbers = new List<string>();
|
|
||||||
|
|
||||||
foreach (var b in textChangeData.Blocks) ShowBlock(b, lLineNumbers, rLineNumbers);
|
|
||||||
|
|
||||||
if (!App.Preference.UIUseOneSideDiff) leftText.Document.PageWidth = minWidth + 16;
|
|
||||||
rightText.Document.PageWidth = minWidth + 16;
|
|
||||||
leftLineNumber.ItemsSource = lLineNumbers;
|
|
||||||
rightLineNumber.ItemsSource = rLineNumbers;
|
|
||||||
leftText.ScrollToHome();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show size changes.
|
/// Show size changes.
|
||||||
|
@ -195,277 +269,145 @@ namespace SourceGit.UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Make paragraph for two-sides diff
|
/// Get background color of line.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="b"></param>
|
/// <param name="b"></param>
|
||||||
/// <param name="leftNumber"></param>
|
/// <returns></returns>
|
||||||
/// <param name="rightNumber"></param>
|
private Brush GetLineBackground(Git.Diff.LineChange line) {
|
||||||
private void ShowBlock(Git.Diff.Block b, List<string> leftNumber, List<string> rightNumber) {
|
switch (line.Mode) {
|
||||||
bool useOneSide = App.Preference.UIUseOneSideDiff;
|
case Git.Diff.LineMode.Added:
|
||||||
if (useOneSide && b.Mode == Git.Diff.LineMode.Empty) return;
|
return bgAdded;
|
||||||
|
case Git.Diff.LineMode.Deleted:
|
||||||
var content = b.Builder.ToString();
|
return bgDeleted;
|
||||||
|
|
||||||
// Make paragraph element
|
|
||||||
Paragraph p = new Paragraph(new Run(content));
|
|
||||||
p.Margin = new Thickness(0);
|
|
||||||
p.Padding = new Thickness(0);
|
|
||||||
p.LineHeight = 1;
|
|
||||||
p.Background = GetBlockBackground(b);
|
|
||||||
p.Foreground = b.Mode == Git.Diff.LineMode.Indicator ? Brushes.Gray : FindResource("Brush.FG") as SolidColorBrush;
|
|
||||||
p.FontStyle = b.Mode == Git.Diff.LineMode.Indicator ? FontStyles.Italic : FontStyles.Normal;
|
|
||||||
p.DataContext = b;
|
|
||||||
p.ContextMenuOpening += OnParagraphContextMenuOpening;
|
|
||||||
|
|
||||||
// Calculate with
|
|
||||||
var formatter = new FormattedText(
|
|
||||||
content,
|
|
||||||
CultureInfo.CurrentUICulture,
|
|
||||||
FlowDirection.LeftToRight,
|
|
||||||
new Typeface(leftText.FontFamily, p.FontStyle, p.FontWeight, p.FontStretch),
|
|
||||||
leftText.FontSize,
|
|
||||||
Brushes.Black,
|
|
||||||
new NumberSubstitution(),
|
|
||||||
TextFormattingMode.Ideal);
|
|
||||||
if (minWidth < formatter.Width) minWidth = formatter.Width;
|
|
||||||
|
|
||||||
// Line numbers
|
|
||||||
switch (b.Side) {
|
|
||||||
case Git.Diff.Side.Left:
|
|
||||||
for (int i = 0; i < b.Count; i++) {
|
|
||||||
if (b.CanShowNumber) leftNumber.Add($"{i + b.LeftStart}");
|
|
||||||
else leftNumber.Add("");
|
|
||||||
|
|
||||||
if (useOneSide) rightNumber.Add("");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Git.Diff.Side.Right:
|
|
||||||
for (int i = 0; i < b.Count; i++) {
|
|
||||||
if (b.CanShowNumber) rightNumber.Add($"{i + b.RightStart}");
|
|
||||||
else rightNumber.Add("");
|
|
||||||
|
|
||||||
if (useOneSide) leftNumber.Add("");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
for (int i = 0; i < b.Count; i++) {
|
return bgNormal;
|
||||||
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:
|
|
||||||
leftText.Document.Blocks.Add(p);
|
|
||||||
|
|
||||||
var cp = new Paragraph(new Run(content));
|
|
||||||
cp.Margin = new Thickness(0);
|
|
||||||
cp.Padding = new Thickness();
|
|
||||||
cp.LineHeight = 1;
|
|
||||||
cp.Background = p.Background;
|
|
||||||
cp.Foreground = p.Foreground;
|
|
||||||
cp.FontStyle = p.FontStyle;
|
|
||||||
cp.DataContext = b;
|
|
||||||
cp.ContextMenuOpening += OnParagraphContextMenuOpening;
|
|
||||||
|
|
||||||
rightText.Document.Blocks.Add(cp);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get background color of block.
|
/// Find child element of type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="b"></param>
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="parent"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private Brush GetBlockBackground(Git.Diff.Block b) {
|
private T GetVisualChild<T>(DependencyObject parent) where T : Visual {
|
||||||
Border border = new Border();
|
T child = null;
|
||||||
border.BorderThickness = new Thickness(0);
|
|
||||||
border.BorderBrush = Brushes.LightBlue;
|
|
||||||
border.Height = b.Count * 16 - 1;
|
|
||||||
border.Width = minWidth - 1;
|
|
||||||
|
|
||||||
switch (b.Mode) {
|
int count = VisualTreeHelper.GetChildrenCount(parent);
|
||||||
case Git.Diff.LineMode.Empty:
|
for (int i = 0; i < count; i++) {
|
||||||
border.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
|
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
|
||||||
break;
|
child = v as T;
|
||||||
case Git.Diff.LineMode.Added:
|
|
||||||
border.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
|
if (child == null) {
|
||||||
break;
|
child = GetVisualChild<T>(v);
|
||||||
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();
|
if (child != null) {
|
||||||
highlight.TileMode = TileMode.None;
|
break;
|
||||||
highlight.Stretch = Stretch.Fill;
|
}
|
||||||
highlight.Visual = border;
|
}
|
||||||
return highlight;
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetDataGrid(DataGrid dg) {
|
||||||
|
dg.ItemsSource = null;
|
||||||
|
dg.Items.Clear();
|
||||||
|
|
||||||
|
foreach (var col in dg.Columns) {
|
||||||
|
col.MinWidth = 0;
|
||||||
|
col.Width = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#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>
|
/// <summary>
|
||||||
/// Fix document size.
|
/// Auto fit text change diff size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender"></param>
|
/// <param name="sender"></param>
|
||||||
/// <param name="e"></param>
|
/// <param name="e"></param>
|
||||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
|
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||||
var text = sender as RichTextBox;
|
var total = area.ActualWidth;
|
||||||
if (text.Document.PageWidth < text.ActualWidth) {
|
|
||||||
text.Document.PageWidth = text.ActualWidth;
|
if (App.Preference.UIUseOneSideDiff) {
|
||||||
|
textChangeOneSide.Columns[0].Width = DataGridLength.Auto;
|
||||||
|
textChangeOneSide.Columns[1].Width = DataGridLength.Auto;
|
||||||
|
textChangeOneSide.Columns[2].MinWidth = 1;
|
||||||
|
textChangeOneSide.Columns[2].Width = 1;
|
||||||
|
textChangeOneSide.UpdateLayout();
|
||||||
|
|
||||||
|
var offset = textChangeOneSide.NonFrozenColumnsViewportHorizontalOffset;
|
||||||
|
var minWidth = total - offset;
|
||||||
|
|
||||||
|
var scroller = GetVisualChild<ScrollViewer>(textChangeOneSide);
|
||||||
|
if (scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8;
|
||||||
|
|
||||||
|
textChangeOneSide.Columns[2].MinWidth = minWidth;
|
||||||
|
textChangeOneSide.Columns[2].Width = DataGridLength.Auto;
|
||||||
|
textChangeOneSide.UpdateLayout();
|
||||||
|
} else {
|
||||||
|
textChangeOldSide.Columns[0].Width = DataGridLength.Auto;
|
||||||
|
textChangeOldSide.Columns[1].MinWidth = 1;
|
||||||
|
textChangeOldSide.Columns[1].Width = 1;
|
||||||
|
textChangeOldSide.UpdateLayout();
|
||||||
|
|
||||||
|
textChangeNewSide.Columns[0].Width = DataGridLength.Auto;
|
||||||
|
textChangeNewSide.Columns[1].MinWidth = 1;
|
||||||
|
textChangeNewSide.Columns[1].Width = 1;
|
||||||
|
textChangeNewSide.UpdateLayout();
|
||||||
|
|
||||||
|
var oldOffset = textChangeOldSide.NonFrozenColumnsViewportHorizontalOffset;
|
||||||
|
var newOffset = textChangeNewSide.NonFrozenColumnsViewportHorizontalOffset;
|
||||||
|
var minWidth = total - Math.Min(oldOffset, newOffset);
|
||||||
|
|
||||||
|
var scroller = GetVisualChild<ScrollViewer>(textChangeNewSide);
|
||||||
|
if (scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8;
|
||||||
|
|
||||||
|
textChangeOldSide.Columns[1].MinWidth = minWidth;
|
||||||
|
textChangeOldSide.Columns[1].Width = DataGridLength.Auto;
|
||||||
|
textChangeOldSide.UpdateLayout();
|
||||||
|
|
||||||
|
textChangeNewSide.Columns[1].MinWidth = minWidth;
|
||||||
|
textChangeNewSide.Columns[1].Width = DataGridLength.Auto;
|
||||||
|
textChangeNewSide.UpdateLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scroll using mouse wheel.
|
/// Prevent default auto-scrolling when click row in DataGrid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender"></param>
|
/// <param name="sender"></param>
|
||||||
/// <param name="e"></param>
|
/// <param name="e"></param>
|
||||||
private void OnViewerMouseWheel(object sender, MouseWheelEventArgs e) {
|
private void OnLineRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
|
||||||
var text = sender as RichTextBox;
|
|
||||||
if (text == null) return;
|
|
||||||
|
|
||||||
if (e.Delta > 0) {
|
|
||||||
text.LineUp();
|
|
||||||
} else {
|
|
||||||
text.LineDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sync scroll both sides.
|
/// Sync scroll on two sides diff.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender"></param>
|
/// <param name="sender"></param>
|
||||||
/// <param name="e"></param>
|
/// <param name="e"></param>
|
||||||
private void OnViewerScroll(object sender, ScrollChangedEventArgs e) {
|
private void OnTwoSidesScroll(object sender, ScrollChangedEventArgs e) {
|
||||||
|
var oldSideScroller = GetVisualChild<ScrollViewer>(textChangeOldSide);
|
||||||
|
var newSideScroller = GetVisualChild<ScrollViewer>(textChangeNewSide);
|
||||||
|
|
||||||
if (e.VerticalChange != 0) {
|
if (e.VerticalChange != 0) {
|
||||||
if (leftText.VerticalOffset != e.VerticalOffset) {
|
if (oldSideScroller.VerticalOffset != e.VerticalOffset) {
|
||||||
leftText.ScrollToVerticalOffset(e.VerticalOffset);
|
oldSideScroller.ScrollToVerticalOffset(e.VerticalOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rightText.VerticalOffset != e.VerticalOffset) {
|
if (newSideScroller.VerticalOffset != e.VerticalOffset) {
|
||||||
rightText.ScrollToVerticalOffset(e.VerticalOffset);
|
newSideScroller.ScrollToVerticalOffset(e.VerticalOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
leftLineNumber.Margin = new Thickness(0, -e.VerticalOffset, 0, 0);
|
|
||||||
rightLineNumber.Margin = new Thickness(0, -e.VerticalOffset, 0, 0);
|
|
||||||
} else {
|
} else {
|
||||||
if (leftText.HorizontalOffset != e.HorizontalOffset) {
|
if (oldSideScroller.HorizontalOffset != e.HorizontalOffset) {
|
||||||
leftText.ScrollToHorizontalOffset(e.HorizontalOffset);
|
oldSideScroller.ScrollToHorizontalOffset(e.HorizontalOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rightText.HorizontalOffset != e.HorizontalOffset) {
|
if (newSideScroller.HorizontalOffset != e.HorizontalOffset) {
|
||||||
rightText.ScrollToHorizontalOffset(e.HorizontalOffset);
|
newSideScroller.ScrollToHorizontalOffset(e.HorizontalOffset);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Auto scroll when selection changed.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender"></param>
|
|
||||||
/// <param name="e"></param>
|
|
||||||
private void OnViewerSelectionChanged(object sender, RoutedEventArgs e) {
|
|
||||||
var doc = sender as RichTextBox;
|
|
||||||
if (doc == null || doc.IsFocused == false) return;
|
|
||||||
|
|
||||||
if (Mouse.LeftButton == MouseButtonState.Pressed && !doc.Selection.IsEmpty) {
|
|
||||||
var p = Mouse.GetPosition(doc);
|
|
||||||
|
|
||||||
if (p.X <= 8) {
|
|
||||||
doc.LineLeft();
|
|
||||||
} else if (p.X >= doc.ActualWidth - 8) {
|
|
||||||
doc.LineRight();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p.Y <= 8) {
|
|
||||||
doc.LineUp();
|
|
||||||
} else if (p.Y >= doc.ActualHeight - 8) {
|
|
||||||
doc.LineDown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,47 +418,24 @@ 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) {
|
||||||
double minTop = 0;
|
var grid = textChangeOneSide;
|
||||||
|
if (!App.Preference.UIUseOneSideDiff) grid = textChangeNewSide;
|
||||||
|
|
||||||
if (App.Preference.UIUseOneSideDiff) {
|
var scroller = GetVisualChild<ScrollViewer>(grid);
|
||||||
foreach (var p in rightText.Document.Blocks) {
|
var firstVisible = (int)scroller.VerticalOffset;
|
||||||
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
|
var firstModeEnded = false;
|
||||||
var block = p.DataContext as Git.Diff.Block;
|
var first = grid.Items[firstVisible] as ChangeBlock;
|
||||||
if (rect.Top > 17 && (block.IsLeftDelete || block.IsRightAdded)) {
|
for (int i = firstVisible + 1; i < grid.Items.Count; i++) {
|
||||||
minTop = rect.Top;
|
var next = grid.Items[i] as ChangeBlock;
|
||||||
|
if (next.Mode != Git.Diff.LineMode.Normal && next.Mode != Git.Diff.LineMode.Indicator) {
|
||||||
|
if (firstModeEnded || next.Mode != first.Mode) {
|
||||||
|
scroller.ScrollToVerticalOffset(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Paragraph next = null;
|
firstModeEnded = true;
|
||||||
|
|
||||||
foreach (var p in leftText.Document.Blocks) {
|
|
||||||
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
|
|
||||||
var block = p.DataContext as Git.Diff.Block;
|
|
||||||
if (rect.Top > 17 && block.IsLeftDelete) {
|
|
||||||
next = p as Paragraph;
|
|
||||||
minTop = rect.Top;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.IsRightAdded) {
|
|
||||||
if (next == null || minTop > rect.Top) {
|
|
||||||
next = p as Paragraph;
|
|
||||||
minTop = rect.Top;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minTop > 0) {
|
|
||||||
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + minTop - 16);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -525,51 +444,23 @@ 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;
|
var grid = textChangeOneSide;
|
||||||
|
if (!App.Preference.UIUseOneSideDiff) grid = textChangeNewSide;
|
||||||
|
|
||||||
if (App.Preference.UIUseOneSideDiff) {
|
var scroller = GetVisualChild<ScrollViewer>(grid);
|
||||||
var p = rightText.Document.Blocks.LastBlock as Paragraph;
|
var firstVisible = (int)scroller.VerticalOffset;
|
||||||
do {
|
var firstModeEnded = false;
|
||||||
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
|
var first = grid.Items[firstVisible] as ChangeBlock;
|
||||||
var block = p.DataContext as Git.Diff.Block;
|
for (int i = firstVisible - 1; i >= 0; i--) {
|
||||||
if (rect.Top < 15 && (block.IsLeftDelete || block.IsRightAdded)) {
|
var next = grid.Items[i] as ChangeBlock;
|
||||||
maxTop = rect.Top;
|
if (next.Mode != Git.Diff.LineMode.Normal && next.Mode != Git.Diff.LineMode.Indicator) {
|
||||||
|
if (firstModeEnded || next.Mode != first.Mode) {
|
||||||
|
scroller.ScrollToVerticalOffset(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
p = p.PreviousBlock as Paragraph;
|
|
||||||
} while (p != null);
|
|
||||||
} else {
|
} else {
|
||||||
Paragraph next = null;
|
firstModeEnded = true;
|
||||||
|
|
||||||
var p = leftText.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) {
|
|
||||||
next = p;
|
|
||||||
maxTop = rect.Top;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p = p.PreviousBlock as Paragraph;
|
|
||||||
} while (p != null);
|
|
||||||
|
|
||||||
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.IsRightAdded) {
|
|
||||||
if (next == null || maxTop < rect.Top) maxTop = rect.Top;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = p.PreviousBlock as Paragraph;
|
|
||||||
} while (p != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxTop != double.MaxValue) {
|
|
||||||
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + maxTop - 16);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,6 +472,39 @@ namespace SourceGit.UI {
|
||||||
private void ChangeDiffMode(object sender, RoutedEventArgs e) {
|
private void ChangeDiffMode(object sender, RoutedEventArgs e) {
|
||||||
SetTextChange();
|
SetTextChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text change context menu opening.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender"></param>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
private void OnTextChangeContextMenuOpening(object sender, ContextMenuEventArgs e) {
|
||||||
|
var grid = sender as DataGrid;
|
||||||
|
if (grid == null) return;
|
||||||
|
|
||||||
|
var menu = new ContextMenu();
|
||||||
|
var copy = new MenuItem();
|
||||||
|
copy.Header = "Copy Selected Lines";
|
||||||
|
copy.Click += (o, ev) => {
|
||||||
|
var items = grid.SelectedItems;
|
||||||
|
if (items.Count == 0) return;
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var item in items) {
|
||||||
|
var block = item as ChangeBlock;
|
||||||
|
if (block == null) continue;
|
||||||
|
if (block.Mode == Git.Diff.LineMode.None || block.Mode == Git.Diff.LineMode.Indicator) continue;
|
||||||
|
|
||||||
|
builder.Append(block.Content);
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
Clipboard.SetText(builder.ToString());
|
||||||
|
};
|
||||||
|
menu.Items.Add(copy);
|
||||||
|
menu.IsOpen = true;
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue