diff --git a/src/Git/Blame.cs b/src/Git/Blame.cs
index bf597274..7985aaa2 100644
--- a/src/Git/Blame.cs
+++ b/src/Git/Blame.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace SourceGit.Git {
@@ -8,9 +8,9 @@ namespace SourceGit.Git {
public class Blame {
///
- /// Block content.
+ /// Line content.
///
- public class Block {
+ public class Line {
public string CommitSHA { get; set; }
public string Author { get; set; }
public string Time { get; set; }
@@ -18,18 +18,13 @@ namespace SourceGit.Git {
}
///
- /// Blocks
+ /// Lines
///
- public List Blocks { get; set; } = new List();
+ public List Lines { get; set; } = new List();
///
/// Is binary file?
///
public bool IsBinary { get; set; } = false;
-
- ///
- /// Line count.
- ///
- public int LineCount { get; set; } = 0;
}
}
diff --git a/src/Git/Diff.cs b/src/Git/Diff.cs
index ba754fe6..273e58f8 100644
--- a/src/Git/Diff.cs
+++ b/src/Git/Diff.cs
@@ -15,100 +15,36 @@ namespace SourceGit.Git {
/// Line mode.
///
public enum LineMode {
+ None,
Normal,
Indicator,
- Empty,
Added,
Deleted,
}
///
- /// Side
+ /// Line change.
///
- public enum Side {
- Left,
- Right,
- Both,
- }
-
- ///
- /// Block
- ///
- public class Block {
- public Side Side = Side.Both;
+ public class LineChange {
public LineMode Mode = LineMode.Normal;
- public int LeftStart = 0;
- public int RightStart = 0;
- public int Count = 0;
- public StringBuilder Builder = new StringBuilder();
+ public string Content = "";
+ public string OldLine = "";
+ public string NewLine = "";
- 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++;
+ public LineChange(LineMode mode, string content, string oldLine = "", string newLine = "") {
+ Mode = mode;
+ Content = content;
+ OldLine = oldLine;
+ NewLine = newLine;
}
}
///
- /// Text file change.
+ /// Text change.
///
public class TextChange {
- public bool IsValid = false;
+ public List Lines = new List();
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);
- }
- }
}
///
@@ -136,103 +72,49 @@ namespace SourceGit.Git {
///
public static TextChange GetTextChange(Repository repo, string args) {
var rs = new TextChange();
- var current = new Block();
- var left = 0;
- var right = 0;
+ var started = false;
+ var oldLine = 0;
+ var newLine = 0;
repo.RunCommand($"diff --ignore-cr-at-eol {args}", line => {
if (rs.IsBinary) return;
- if (!rs.IsValid) {
+ if (!started) {
var match = REG_INDICATOR.Match(line);
if (!match.Success) {
- if (line.StartsWith("Binary ")) rs.SetBinary();
+ if (line.StartsWith("Binary ")) rs.IsBinary = true;
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);
+ started = true;
+ oldLine = int.Parse(match.Groups[1].Value);
+ newLine = int.Parse(match.Groups[2].Value);
+ rs.Lines.Add(new LineChange(LineMode.Indicator, 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++;
+ rs.Lines.Add(new LineChange(LineMode.Deleted, line.Substring(1), $"{oldLine}", ""));
+ oldLine++;
} 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++;
+ rs.Lines.Add(new LineChange(LineMode.Added, line.Substring(1), "", $"{newLine}"));
+ newLine++;
} 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;
+ rs.Lines.Add(new LineChange(LineMode.Indicator, line.Substring(1)));
} 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);
+ oldLine = int.Parse(match.Groups[1].Value);
+ newLine = int.Parse(match.Groups[2].Value);
+ rs.Lines.Add(new LineChange(LineMode.Indicator, 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.Lines.Add(new LineChange(LineMode.Normal, line.Substring(1), $"{oldLine}", $"{newLine}"));
+ oldLine++;
+ newLine++;
}
}
}
});
- rs.Add(current);
- rs.Fit();
-
- if (rs.IsBinary) rs.Blocks.Clear();
+ if (rs.IsBinary) rs.Lines.Clear();
return rs;
}
diff --git a/src/Git/Repository.cs b/src/Git/Repository.cs
index f0bbfe03..62f52291 100644
--- a/src/Git/Repository.cs
+++ b/src/Git/Repository.cs
@@ -945,7 +945,6 @@ namespace SourceGit.Git {
public Blame BlameFile(string file, string revision) {
var regex = new Regex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)");
var blame = new Blame();
- var current = null as Blame.Block;
var errs = RunCommand($"blame -t {revision} -- \"{file}\"", line => {
if (blame.IsBinary) return;
@@ -953,7 +952,7 @@ namespace SourceGit.Git {
if (line.IndexOf('\0') >= 0) {
blame.IsBinary = true;
- blame.Blocks.Clear();
+ blame.Lines.Clear();
return;
}
@@ -961,25 +960,19 @@ namespace SourceGit.Git {
if (!match.Success) return;
var commit = match.Groups[1].Value;
+ var author = match.Groups[2].Value;
+ var timestamp = int.Parse(match.Groups[3].Value);
var data = match.Groups[4].Value;
- if (current != null && current.CommitSHA == commit) {
- current.Content = current.Content + "\n" + data;
- } else {
- var timestamp = int.Parse(match.Groups[3].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() {
- CommitSHA = commit,
- Author = match.Groups[2].Value,
- Time = when,
- Content = data,
- };
+ var blameLine = new Blame.Line() {
+ CommitSHA = commit,
+ Author = author,
+ Time = when,
+ Content = data,
+ };
- if (current.Author == null) current.Author = "";
- blame.Blocks.Add(current);
- }
-
- blame.LineCount++;
+ blame.Lines.Add(blameLine);
});
if (errs != null) App.RaiseError(errs);
diff --git a/src/UI/Blame.xaml b/src/UI/Blame.xaml
index f356c551..443b314c 100644
--- a/src/UI/Blame.xaml
+++ b/src/UI/Blame.xaml
@@ -107,80 +107,60 @@
-
+
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -197,7 +177,7 @@
-
+
diff --git a/src/UI/Blame.xaml.cs b/src/UI/Blame.xaml.cs
index 624d67e3..77736953 100644
--- a/src/UI/Blame.xaml.cs
+++ b/src/UI/Blame.xaml.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@@ -15,6 +14,9 @@ namespace SourceGit.UI {
/// Viewer to show git-blame
///
public partial class Blame : Window {
+ private Git.Repository repo = null;
+ private string lastSHA = null;
+ private int lastBG = 1;
///
/// Background color for blocks.
@@ -24,17 +26,24 @@ namespace SourceGit.UI {
new SolidColorBrush(Color.FromArgb(128, 0, 0, 0))
};
+ ///
+ /// Record
+ ///
+ public class Record {
+ public Git.Blame.Line Line { get; set; }
+ public Brush BG { get; set; }
+ public int LineNumber { get; set; }
+ }
+
///
/// Constructor
///
- ///
+ ///
///
///
- public Blame(Git.Repository repo, string file, string revision) {
+ public Blame(Git.Repository open, string file, string revision) {
InitializeComponent();
- double minWidth = content.ActualWidth;
-
// Move to center.
var parent = App.Current.MainWindow;
Left = parent.Left + (parent.Width - Width) * 0.5;
@@ -48,97 +57,57 @@ namespace SourceGit.UI {
// Layout content
blameFile.Content = $"{file}@{revision.Substring(0, 8)}";
+ repo = open;
+
Task.Run(() => {
- var blame = repo.BlameFile(file, revision);
+ var result = repo.BlameFile(file, revision);
+ var records = new List();
+
+ 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(() => {
- 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 numbers = new List();
- 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.Visibility = Visibility.Collapsed;
+
+ blame.ItemsSource = records;
+ blame.UpdateLayout();
+
+ ContentSizeChanged(null, null);
});
});
}
+ ///
+ /// Get background brush.
+ ///
+ ///
+ ///
+ private Brush GetBG(Git.Blame.Line line) {
+ if (lastSHA != line.CommitSHA) {
+ lastSHA = line.CommitSHA;
+ lastBG = 1 - lastBG;
+ }
+
+ return BG[lastBG];
+ }
+
///
/// Click logo
///
@@ -177,68 +146,83 @@ namespace SourceGit.UI {
Close();
}
- ///
- /// Sync scroll
- ///
- ///
- ///
- private void SyncScrollChanged(object sender, ScrollChangedEventArgs e) {
- if (e.VerticalChange != 0) {
- var margin = new Thickness(4, -e.VerticalOffset, 4, 0);
- lineNumber.Margin = margin;
- }
- }
-
- ///
- /// Mouse wheel
- ///
- ///
- ///
- private void MouseWheelOnContent(object sender, MouseWheelEventArgs e) {
- if (e.Delta > 0) {
- content.LineUp();
- } else {
- content.LineDown();
- }
-
- e.Handled = true;
- }
-
///
/// Content size changed.
///
///
///
private void ContentSizeChanged(object sender, SizeChangedEventArgs e) {
- if (content.Document.PageWidth < content.ActualWidth) {
- content.Document.PageWidth = content.ActualWidth;
- }
+ var total = area.ActualWidth;
+ var offset = blame.NonFrozenColumnsViewportHorizontalOffset;
+ var minWidth = total - offset - 2;
+
+ var scroller = GetVisualChild(blame);
+ if (scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8;
+
+ blame.Columns[1].MinWidth = minWidth;
+ blame.Columns[1].Width = DataGridLength.SizeToCells;
+ blame.UpdateLayout();
}
///
- /// Auto scroll when selection changed.
+ /// Context menu opening.
///
///
///
- private void ContentSelectionChanged(object sender, RoutedEventArgs e) {
- var doc = sender as RichTextBox;
- if (doc == null || doc.IsFocused == false) return;
+ private void OnBlameContextMenuOpening(object sender, ContextMenuEventArgs ev) {
+ var item = sender as DataGridRow;
+ if (item == null) return;
- if (Mouse.LeftButton == MouseButtonState.Pressed && !doc.Selection.IsEmpty) {
- var p = Mouse.GetPosition(doc);
+ var record = item.DataContext as Record;
+ if (record == null || record.Line.CommitSHA == null) return;
- if (p.X <= 8) {
- doc.LineLeft();
- } else if (p.X >= doc.ActualWidth - 8) {
- doc.LineRight();
+ Hyperlink link = new Hyperlink(new Run(record.Line.CommitSHA));
+ link.ToolTip = "CLICK TO GO";
+ link.Click += (o, e) => {
+ 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;
+ }
+
+ ///
+ /// Prevent auto scroll.
+ ///
+ ///
+ ///
+ private void OnBlameRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
+ e.Handled = true;
+ }
+
+ ///
+ /// Find child element of type.
+ ///
+ ///
+ ///
+ ///
+ private T GetVisualChild(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(v);
}
- if (p.Y <= 8) {
- doc.LineUp();
- } else if (p.Y >= doc.ActualHeight - 8) {
- doc.LineDown();
+ if (child != null) {
+ break;
}
}
+
+ return child;
}
}
}
diff --git a/src/UI/CommitViewer.xaml b/src/UI/CommitViewer.xaml
index 02f683b2..45d5aeac 100644
--- a/src/UI/CommitViewer.xaml
+++ b/src/UI/CommitViewer.xaml
@@ -299,6 +299,7 @@
@@ -388,6 +389,7 @@
diff --git a/src/UI/CommitViewer.xaml.cs b/src/UI/CommitViewer.xaml.cs
index 1810a5c3..a2199b00 100644
--- a/src/UI/CommitViewer.xaml.cs
+++ b/src/UI/CommitViewer.xaml.cs
@@ -551,7 +551,10 @@ namespace SourceGit.UI {
menu.IsOpen = true;
e.Handled = true;
}
- #endregion
+ private void TreeRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
+ e.Handled = true;
+ }
+ #endregion
}
}
diff --git a/src/UI/DiffViewer.xaml b/src/UI/DiffViewer.xaml
index 7ad6b7c0..41dbe78d 100644
--- a/src/UI/DiffViewer.xaml
+++ b/src/UI/DiffViewer.xaml
@@ -7,12 +7,29 @@
mc:Ignorable="d"
FontFamily="Consolas">
-
+
+
+
+
+
+
+
@@ -52,140 +69,106 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+ x:Name="textChangeNewSide"
+ GridLinesVisibility="Vertical"
+ VerticalGridLinesBrush="{StaticResource Brush.Border2}"
+ FrozenColumnCount="1"
+ ScrollViewer.ScrollChanged="OnTwoSidesScroll"
+ ContextMenuOpening="OnTextChangeContextMenuOpening">
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/UI/DiffViewer.xaml.cs b/src/UI/DiffViewer.xaml.cs
index 672001df..57d38f83 100644
--- a/src/UI/DiffViewer.xaml.cs
+++ b/src/UI/DiffViewer.xaml.cs
@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
+using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
-using System.Windows.Documents;
-using System.Windows.Input;
using System.Windows.Media;
+using System.Windows.Threading;
namespace SourceGit.UI {
@@ -14,8 +13,11 @@ namespace SourceGit.UI {
/// Viewer for git diff
///
public partial class DiffViewer : UserControl {
- private double minWidth = 0;
- private Git.Diff.TextChange textChangeData = null;
+ private List lineChanges = 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;
///
/// Diff options.
@@ -27,6 +29,19 @@ namespace SourceGit.UI {
public string ExtraArgs = "";
}
+ ///
+ /// Change block.
+ ///
+ 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; }
+ }
+
///
/// Constructor
///
@@ -50,11 +65,12 @@ namespace SourceGit.UI {
public void Diff(Git.Repository repo, Option opts) {
SetTitle(opts.Path, opts.OrgPath);
- textChangeData = null;
+ lineChanges = null;
loading.Visibility = Visibility.Visible;
mask.Visibility = Visibility.Collapsed;
- textChange.Visibility = Visibility.Collapsed;
+ textChangeOneSide.Visibility = Visibility.Collapsed;
+ textChangeTwoSides.Visibility = Visibility.Collapsed;
sizeChange.Visibility = Visibility.Collapsed;
noChange.Visibility = Visibility.Collapsed;
@@ -82,8 +98,8 @@ namespace SourceGit.UI {
var rs = Git.Diff.GetTextChange(repo, args);
if (rs.IsBinary) {
SetBinaryChange(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath));
- } else if (rs.Blocks.Count > 0) {
- textChangeData = rs;
+ } else if (rs.Lines.Count > 0) {
+ lineChanges = rs.Lines;
SetTextChange();
} else {
SetSame();
@@ -112,42 +128,100 @@ namespace SourceGit.UI {
///
///
private void SetTextChange() {
- if (textChangeData == null) return;
+ if (lineChanges == null) return;
- Dispatcher.Invoke(() => {
- loading.Visibility = Visibility.Collapsed;
- textChange.Visibility = Visibility.Visible;
- textChangeOptions.Visibility = Visibility.Visible;
+ var fgCommon = FindResource("Brush.FG") as Brush;
+ var fgIndicator = FindResource("Brush.FG2") as Brush;
- 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);
+ if (App.Preference.UIUseOneSideDiff) {
+ var blocks = new List();
+
+ 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);
}
- minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16;
+ Dispatcher.Invoke(() => {
+ loading.Visibility = Visibility.Collapsed;
+ textChangeOptions.Visibility = Visibility.Visible;
+ textChangeOneSide.Visibility = Visibility.Visible;
+ textChangeTwoSides.Visibility = Visibility.Collapsed;
- leftLineNumber.ItemsSource = null;
- rightLineNumber.ItemsSource = null;
+ ResetDataGrid(textChangeOneSide);
+ textChangeOneSide.ItemsSource = blocks;
+ OnSizeChanged(null, null);
+ });
+ } else {
+ var oldSideBlocks = new List();
+ var newSideBlocks = new List();
- leftText.Document.Blocks.Clear();
- rightText.Document.Blocks.Clear();
+ 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;
- var lLineNumbers = new List();
- var rLineNumbers = new List();
+ switch (line.Mode) {
+ case Git.Diff.LineMode.Added:
+ newSideBlocks.Add(block);
- foreach (var b in textChangeData.Blocks) ShowBlock(b, lLineNumbers, rLineNumbers);
+ 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);
- if (!App.Preference.UIUseOneSideDiff) leftText.Document.PageWidth = minWidth + 16;
- rightText.Document.PageWidth = minWidth + 16;
- leftLineNumber.ItemsSource = lLineNumbers;
- rightLineNumber.ItemsSource = rLineNumbers;
- leftText.ScrollToHome();
- });
+ 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;
+ }
+ }
+
+ Dispatcher.Invoke(() => {
+ loading.Visibility = Visibility.Collapsed;
+ textChangeOptions.Visibility = Visibility.Visible;
+ textChangeOneSide.Visibility = Visibility.Collapsed;
+ textChangeTwoSides.Visibility = Visibility.Visible;
+
+ ResetDataGrid(textChangeOldSide);
+ ResetDataGrid(textChangeNewSide);
+
+ textChangeOldSide.ItemsSource = oldSideBlocks;
+ textChangeNewSide.ItemsSource = newSideBlocks;
+
+ OnSizeChanged(null, null);
+ });
+ }
}
///
@@ -195,277 +269,145 @@ namespace SourceGit.UI {
}
///
- /// Make paragraph for two-sides diff
+ /// Get background color of line.
///
///
- ///
- ///
- private void ShowBlock(Git.Diff.Block b, List leftNumber, List rightNumber) {
- bool useOneSide = App.Preference.UIUseOneSideDiff;
- if (useOneSide && b.Mode == Git.Diff.LineMode.Empty) return;
-
- var content = b.Builder.ToString();
-
- // 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;
+ ///
+ private Brush GetLineBackground(Git.Diff.LineChange line) {
+ switch (line.Mode) {
+ case Git.Diff.LineMode.Added:
+ return bgAdded;
+ case Git.Diff.LineMode.Deleted:
+ return bgDeleted;
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:
- 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;
- }
+ return bgNormal;
}
}
///
- /// Get background color of block.
+ /// Find child element of type.
///
- ///
+ ///
+ ///
///
- 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;
+ private T GetVisualChild(DependencyObject parent) where T : Visual {
+ T child = null;
- 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;
+ 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(v);
+ }
+
+ if (child != null) {
+ break;
+ }
}
- VisualBrush highlight = new VisualBrush();
- highlight.TileMode = TileMode.None;
- 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
#region EVENTS
- ///
- /// Context menu for text-change paragraph
- ///
- ///
- ///
- 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;
- }
///
- /// Fix document size.
+ /// Auto fit text change diff size.
///
///
///
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
- var text = sender as RichTextBox;
- if (text.Document.PageWidth < text.ActualWidth) {
- text.Document.PageWidth = text.ActualWidth;
+ var total = area.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(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(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();
}
}
///
- /// Scroll using mouse wheel.
+ /// Prevent default auto-scrolling when click row in DataGrid.
///
///
///
- 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();
- }
-
+ private void OnLineRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
e.Handled = true;
}
///
- /// Sync scroll both sides.
+ /// Sync scroll on two sides diff.
///
///
///
- private void OnViewerScroll(object sender, ScrollChangedEventArgs e) {
+ private void OnTwoSidesScroll(object sender, ScrollChangedEventArgs e) {
+ var oldSideScroller = GetVisualChild(textChangeOldSide);
+ var newSideScroller = GetVisualChild(textChangeNewSide);
+
if (e.VerticalChange != 0) {
- if (leftText.VerticalOffset != e.VerticalOffset) {
- leftText.ScrollToVerticalOffset(e.VerticalOffset);
+ if (oldSideScroller.VerticalOffset != e.VerticalOffset) {
+ oldSideScroller.ScrollToVerticalOffset(e.VerticalOffset);
}
- if (rightText.VerticalOffset != e.VerticalOffset) {
- rightText.ScrollToVerticalOffset(e.VerticalOffset);
+ if (newSideScroller.VerticalOffset != 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 {
- if (leftText.HorizontalOffset != e.HorizontalOffset) {
- leftText.ScrollToHorizontalOffset(e.HorizontalOffset);
+ if (oldSideScroller.HorizontalOffset != e.HorizontalOffset) {
+ oldSideScroller.ScrollToHorizontalOffset(e.HorizontalOffset);
}
- if (rightText.HorizontalOffset != e.HorizontalOffset) {
- rightText.ScrollToHorizontalOffset(e.HorizontalOffset);
- }
- }
- }
-
- ///
- /// Auto scroll when selection changed.
- ///
- ///
- ///
- 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();
+ if (newSideScroller.HorizontalOffset != e.HorizontalOffset) {
+ newSideScroller.ScrollToHorizontalOffset(e.HorizontalOffset);
}
}
}
@@ -476,46 +418,23 @@ namespace SourceGit.UI {
///
///
private void Go2Next(object sender, RoutedEventArgs e) {
- double minTop = 0;
+ var grid = textChangeOneSide;
+ if (!App.Preference.UIUseOneSideDiff) grid = textChangeNewSide;
- 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;
+ var scroller = GetVisualChild(grid);
+ var firstVisible = (int)scroller.VerticalOffset;
+ var firstModeEnded = false;
+ var first = grid.Items[firstVisible] as ChangeBlock;
+ for (int i = firstVisible + 1; i < grid.Items.Count; i++) {
+ 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;
}
+ } else {
+ firstModeEnded = true;
}
- } else {
- Paragraph next = null;
-
- 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);
}
}
@@ -525,51 +444,23 @@ namespace SourceGit.UI {
///
///
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 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;
+ var scroller = GetVisualChild(grid);
+ var firstVisible = (int)scroller.VerticalOffset;
+ var firstModeEnded = false;
+ var first = grid.Items[firstVisible] as ChangeBlock;
+ for (int i = firstVisible - 1; i >= 0; i--) {
+ 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;
}
-
- p = p.PreviousBlock as Paragraph;
- } while (p != null);
- } else {
- Paragraph next = null;
-
- 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);
+ } else {
+ firstModeEnded = true;
+ }
}
}
@@ -581,6 +472,39 @@ namespace SourceGit.UI {
private void ChangeDiffMode(object sender, RoutedEventArgs e) {
SetTextChange();
}
+
+ ///
+ /// Text change context menu opening.
+ ///
+ ///
+ ///
+ 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
}
}