diff --git a/SourceGit/Git/Diff.cs b/SourceGit/Git/Diff.cs
new file mode 100644
index 00000000..b53b2a83
--- /dev/null
+++ b/SourceGit/Git/Diff.cs
@@ -0,0 +1,233 @@
+using System.Collections.Generic;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Git {
+
+ ///
+ /// Diff helper.
+ ///
+ public class Diff {
+ private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@", RegexOptions.None);
+
+ ///
+ /// Line mode.
+ ///
+ public enum LineMode {
+ Normal,
+ Indicator,
+ Empty,
+ Added,
+ Deleted,
+ }
+
+ ///
+ /// Side
+ ///
+ public enum Side {
+ Left,
+ Right,
+ Both,
+ }
+
+ ///
+ /// Block
+ ///
+ public class Block {
+ public Side Side = Side.Both;
+ public LineMode Mode = LineMode.Normal;
+ public int LeftStart = 0;
+ public int RightStart = 0;
+ public int Count = 0;
+ public StringBuilder Builder = new StringBuilder();
+
+ public bool IsLeftDelete => Side == Side.Left && Mode == LineMode.Deleted;
+ public bool IsRightAdded => Side == Side.Right && Mode == LineMode.Added;
+ public bool IsBothSideNormal => Side == Side.Both && Mode == LineMode.Normal;
+ public bool CanShowNumber => Mode != LineMode.Indicator && Mode != LineMode.Empty;
+
+ public void Append(string data) {
+ if (Count > 0) Builder.AppendLine();
+ Builder.Append(data);
+ Count++;
+ }
+ }
+
+ ///
+ /// Diff result.
+ ///
+ public class Result {
+ public bool IsValid = false;
+ public bool IsBinary = false;
+ public List Blocks = new List();
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// Run diff process.
+ ///
+ ///
+ ///
+ ///
+ public static Result Run(Repository repo, string args) {
+ var rs = new Result();
+ var current = new Block();
+ var left = 0;
+ var right = 0;
+
+ repo.RunCommand($"diff --ignore-cr-at-eol {args}", line => {
+ if (rs.IsBinary) return;
+
+ if (!rs.IsValid) {
+ var match = REG_INDICATOR.Match(line);
+ if (!match.Success) {
+ if (line.StartsWith("Binary ")) rs.SetBinary();
+ return;
+ }
+
+ rs.IsValid = true;
+ left = int.Parse(match.Groups[1].Value);
+ right = int.Parse(match.Groups[2].Value);
+ current.Mode = LineMode.Indicator;
+ current.Append(line);
+ } else {
+ if (line[0] == '-') {
+ if (current.IsLeftDelete) {
+ current.Append(line.Substring(1));
+ } 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] == '+') {
+ if (current.IsRightAdded) {
+ current.Append(line.Substring(1));
+ } 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] == '\\') {
+ var tmp = new Block();
+ 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 {
+ var match = REG_INDICATOR.Match(line);
+ if (match.Success) {
+ rs.Add(current);
+ rs.Fit();
+
+ 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 {
+ if (current.IsBothSideNormal) {
+ current.Append(line.Substring(1));
+ } else {
+ rs.Add(current);
+ rs.Fit();
+
+ current = new Block();
+ current.LeftStart = left;
+ current.RightStart = right;
+ current.Append(line.Substring(1));
+ }
+
+ left++;
+ right++;
+ }
+ }
+ }
+ });
+
+ rs.Add(current);
+ rs.Fit();
+
+ if (rs.IsBinary) {
+ var b = new Block();
+ b.Mode = LineMode.Indicator;
+ b.Append("BINARY FILES NOT SUPPORTED!!!");
+ rs.Blocks.Clear();
+ rs.Blocks.Add(b);
+ } else if (rs.Blocks.Count == 0) {
+ var b = new Block();
+ b.Mode = LineMode.Indicator;
+ b.Append("NO CHANGES OR ONLY WHITESPACE CHANGES!!!");
+ rs.Blocks.Add(b);
+ }
+
+ return rs;
+ }
+ }
+}
diff --git a/SourceGit/Git/Repository.cs b/SourceGit/Git/Repository.cs
index 77d594dc..9a0b0383 100644
--- a/SourceGit/Git/Repository.cs
+++ b/SourceGit/Git/Repository.cs
@@ -827,26 +827,6 @@ namespace SourceGit.Git {
return stashes;
}
- ///
- /// Diff
- ///
- ///
- ///
- ///
- ///
- ///
- public List Diff(string startRevision, string endRevision, string file, string orgFile = null) {
- var args = $"diff --ignore-cr-at-eol {startRevision} {endRevision} -- ";
- if (!string.IsNullOrEmpty(orgFile)) args += $"\"{orgFile}\" ";
- args += $"\"{file}\"";
-
- var data = new List();
- var errs = RunCommand(args, line => data.Add(line));
-
- if (errs != null) App.RaiseError(errs);
- return data;
- }
-
///
/// Blame file.
///
diff --git a/SourceGit/UI/CommitViewer.xaml.cs b/SourceGit/UI/CommitViewer.xaml.cs
index ab350a12..916bcfad 100644
--- a/SourceGit/UI/CommitViewer.xaml.cs
+++ b/SourceGit/UI/CommitViewer.xaml.cs
@@ -1,468 +1,456 @@
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Navigation;
-
-namespace SourceGit.UI {
-
- ///
- /// Commit detail viewer
- ///
- public partial class CommitViewer : UserControl {
- private Git.Repository repo = null;
- private Git.Commit commit = null;
- private List cachedChanges = new List();
- private List displayChanges = new List();
- private string changeFilter = null;
-
- ///
- /// Node for file tree.
- ///
- public class Node {
- public string FilePath { get; set; } = "";
- public string OriginalPath { get; set; } = "";
- public string Name { get; set; } = "";
- public bool IsFile { get; set; } = false;
- public bool IsNodeExpanded { get; set; } = true;
- public Git.Change Change { get; set; } = null;
- public List Children { get; set; } = new List();
- }
-
- ///
- /// Constructor.
- ///
- public CommitViewer() {
- InitializeComponent();
- }
-
- #region DATA
- public void SetData(Git.Repository opened, Git.Commit selected) {
- repo = opened;
- commit = selected;
-
- SetBaseInfo(commit);
-
- Task.Run(() => {
- cachedChanges.Clear();
- cachedChanges = commit.GetChanges(repo);
-
- Dispatcher.Invoke(() => {
- changeList1.ItemsSource = null;
- changeList1.ItemsSource = cachedChanges;
- });
-
- LayoutChanges();
- SetRevisionFiles(commit.GetFiles(repo));
- });
- }
-
- private void Cleanup(object sender, RoutedEventArgs e) {
- fileTree.ItemsSource = null;
- changeList1.ItemsSource = null;
- changeList2.ItemsSource = null;
- displayChanges.Clear();
- cachedChanges.Clear();
- diffViewer.Reset();
- }
- #endregion
-
- #region BASE_INFO
- private void SetBaseInfo(Git.Commit commit) {
- var parentIds = new List();
- foreach (var p in commit.Parents) parentIds.Add(p.Substring(0, 8));
-
- SHA.Text = commit.SHA;
- refs.ItemsSource = commit.Decorators;
- parents.ItemsSource = parentIds;
- author.Text = $"{commit.Author.Name} <{commit.Author.Email}>";
- authorTime.Text = commit.Author.Time;
- committer.Text = $"{commit.Committer.Name} <{commit.Committer.Email}>";
- committerTime.Text = commit.Committer.Time;
- subject.Text = commit.Subject;
- message.Text = commit.Message.Trim();
-
- if (commit.Decorators.Count == 0) lblRefs.Visibility = Visibility.Collapsed;
- else lblRefs.Visibility = Visibility.Visible;
-
- if (commit.Committer.Email == commit.Author.Email && commit.Committer.Time == commit.Author.Time) {
- committerRow.Height = new GridLength(0);
- } else {
- committerRow.Height = GridLength.Auto;
- }
- }
-
- private void NavigateParent(object sender, RequestNavigateEventArgs e) {
- repo.OnNavigateCommit?.Invoke(e.Uri.OriginalString);
- e.Handled = true;
- }
-
- #endregion
-
- #region CHANGES
- private void LayoutChanges() {
- displayChanges.Clear();
-
- if (string.IsNullOrEmpty(changeFilter)) {
- displayChanges.AddRange(cachedChanges);
- } else {
- foreach (var c in cachedChanges) {
- if (c.Path.ToUpper().Contains(changeFilter)) displayChanges.Add(c);
- }
- }
-
- List changeTreeSource = new List();
- Dictionary folders = new Dictionary();
- bool isDefaultExpanded = displayChanges.Count < 50;
-
- foreach (var c in displayChanges) {
- var sepIdx = c.Path.IndexOf('/');
- if (sepIdx == -1) {
- Node node = new Node();
- node.FilePath = c.Path;
- node.IsFile = true;
- node.Name = c.Path;
- node.Change = c;
- node.IsNodeExpanded = isDefaultExpanded;
- if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath;
- changeTreeSource.Add(node);
- } else {
- Node lastFolder = null;
- var start = 0;
-
- while (sepIdx != -1) {
- var folder = c.Path.Substring(0, sepIdx);
- if (folders.ContainsKey(folder)) {
- lastFolder = folders[folder];
- } else if (lastFolder == null) {
- lastFolder = new Node();
- lastFolder.FilePath = folder;
- lastFolder.Name = folder.Substring(start);
- lastFolder.IsNodeExpanded = isDefaultExpanded;
- changeTreeSource.Add(lastFolder);
- folders.Add(folder, lastFolder);
- } else {
- var folderNode = new Node();
- folderNode.FilePath = folder;
- folderNode.Name = folder.Substring(start);
- folderNode.IsNodeExpanded = isDefaultExpanded;
- folders.Add(folder, folderNode);
- lastFolder.Children.Add(folderNode);
- lastFolder = folderNode;
- }
-
- start = sepIdx + 1;
- sepIdx = c.Path.IndexOf('/', start);
- }
-
- Node node = new Node();
- node.FilePath = c.Path;
- node.Name = c.Path.Substring(start);
- node.IsFile = true;
- node.Change = c;
- if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath;
- lastFolder.Children.Add(node);
- }
- }
-
- folders.Clear();
- SortTreeNodes(changeTreeSource);
-
- Dispatcher.Invoke(() => {
- changeList2.ItemsSource = null;
- changeList2.ItemsSource = displayChanges;
- changeTree.ItemsSource = changeTreeSource;
- diffViewer.Reset();
- });
- }
-
- private void SearchChangeFileTextChanged(object sender, TextChangedEventArgs e) {
- changeFilter = txtChangeFilter.Text.ToUpper();
- Task.Run(() => LayoutChanges());
- }
-
- private async void ChangeTreeItemSelected(object sender, RoutedPropertyChangedEventArgs