using System; using System.Globalization; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; namespace SourceGit.UI { /// /// Viewer for git diff /// public partial class DiffViewer : UserControl { private double minWidth = 0; /// /// Diff options. /// public class Option { public string[] RevisionRange = new string[] { }; public string Path = ""; public string OrgPath = null; public string ExtraArgs = ""; } /// /// Constructor /// public DiffViewer() { InitializeComponent(); Reset(); } /// /// Reset data. /// public void Reset() { mask.Visibility = Visibility.Visible; sizer.Visibility = Visibility.Collapsed; } /// /// Diff with options. /// /// /// public void Diff(Git.Repository repo, Option opts) { SetTitle(opts.Path, opts.OrgPath); loading.Visibility = Visibility.Visible; sizer.Visibility = Visibility.Collapsed; Task.Run(() => { var args = $"{opts.ExtraArgs} "; if (opts.RevisionRange.Length > 0) args += $"{opts.RevisionRange[0]} "; if (opts.RevisionRange.Length > 1) args += $"{opts.RevisionRange[1]} -- "; if (!string.IsNullOrEmpty(opts.OrgPath)) args += $"\"{opts.OrgPath}\" "; args += $"\"{opts.Path}\""; var rs = Git.Diff.Run(repo, args); if (rs.IsBinary) { SetSizeChangeData(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath)); } else { SetData(rs); } }); } #region LAYOUT /// /// Show diff title /// /// /// private void SetTitle(string file, string orgFile) { fileName.Text = file; if (!string.IsNullOrEmpty(orgFile)) { orgFileNamePanel.Visibility = Visibility.Visible; orgFileName.Text = orgFile; } else { orgFileNamePanel.Visibility = Visibility.Collapsed; } } /// /// Show size changes. /// /// private void SetSizeChangeData(Git.Diff.BinaryChange bc) { Dispatcher.Invoke(() => { loading.Visibility = Visibility.Collapsed; mask.Visibility = Visibility.Collapsed; sizer.Visibility = Visibility.Visible; txtNewSize.Content = $"{bc.Size} Bytes"; txtOldSize.Content = $"{bc.PreSize} Bytes"; }); } /// /// Show diff content. /// /// private void SetData(Git.Diff.Result rs) { Dispatcher.Invoke(() => { loading.Visibility = Visibility.Collapsed; mask.Visibility = Visibility.Collapsed; minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16; leftLineNumber.Text = ""; rightLineNumber.Text = ""; leftText.Document.Blocks.Clear(); rightText.Document.Blocks.Clear(); foreach (var b in rs.Blocks) ShowBlock(b); leftText.Document.PageWidth = minWidth + 16; rightText.Document.PageWidth = minWidth + 16; leftText.ScrollToHome(); }); } /// /// Make paragraph. /// /// private void ShowBlock(Git.Diff.Block b) { var content = b.Builder.ToString(); Paragraph p = new Paragraph(new Run(content)); p.Margin = new Thickness(0); p.Padding = new Thickness(); p.LineHeight = 1; p.Background = Brushes.Transparent; p.Foreground = FindResource("Brush.FG") as SolidColorBrush; p.FontStyle = FontStyles.Normal; switch (b.Mode) { case Git.Diff.LineMode.Normal: break; case Git.Diff.LineMode.Indicator: p.Foreground = Brushes.Gray; p.FontStyle = FontStyles.Italic; break; case Git.Diff.LineMode.Empty: p.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0)); break; case Git.Diff.LineMode.Added: p.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)); break; case Git.Diff.LineMode.Deleted: p.Background = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)); break; } var formatter = new FormattedText( 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; switch (b.Side) { case Git.Diff.Side.Left: leftText.Document.Blocks.Add(p); for (int i = 0; i < b.Count; i++) { if (b.CanShowNumber) leftLineNumber.AppendText($"{i + b.LeftStart}\n"); else leftLineNumber.AppendText("\n"); } break; case Git.Diff.Side.Right: rightText.Document.Blocks.Add(p); for (int i = 0; i < b.Count; i++) { if (b.CanShowNumber) rightLineNumber.AppendText($"{i + b.RightStart}\n"); else rightLineNumber.AppendText("\n"); } 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; rightText.Document.Blocks.Add(cp); for (int i = 0; i < b.Count; i++) { if (b.Mode != Git.Diff.LineMode.Indicator) { leftLineNumber.AppendText($"{i + b.LeftStart}\n"); rightLineNumber.AppendText($"{i + b.RightStart}\n"); } else { leftLineNumber.AppendText("\n"); rightLineNumber.AppendText("\n"); } } break; } } #endregion #region EVENTS /// /// Sync scroll both sides. /// /// /// private void OnViewerScroll(object sender, ScrollChangedEventArgs e) { if (e.VerticalChange != 0) { if (leftText.VerticalOffset != e.VerticalOffset) { leftText.ScrollToVerticalOffset(e.VerticalOffset); } if (rightText.VerticalOffset != e.VerticalOffset) { rightText.ScrollToVerticalOffset(e.VerticalOffset); } leftLineNumber.Margin = new Thickness(4, -e.VerticalOffset, 4, 0); rightLineNumber.Margin = new Thickness(4, -e.VerticalOffset, 4, 0); } else { if (leftText.HorizontalOffset != e.HorizontalOffset) { leftText.ScrollToHorizontalOffset(e.HorizontalOffset); } if (rightText.HorizontalOffset != e.HorizontalOffset) { rightText.ScrollToHorizontalOffset(e.HorizontalOffset); } } } /// /// Scroll using mouse wheel. /// /// /// private void OnViewerMouseWheel(object sender, MouseWheelEventArgs e) { var text = sender as RichTextBox; if (text == null) return; if (e.Delta > 0) { text.LineUp(); } else { text.LineDown(); } e.Handled = true; } private void LeftSizeChanged(object sender, SizeChangedEventArgs e) { if (leftText.Document.PageWidth < leftText.ActualWidth) { leftText.Document.PageWidth = leftText.ActualWidth; } } private void RightSizeChanged(object sender, SizeChangedEventArgs e) { if (rightText.Document.PageWidth < rightText.ActualWidth) { rightText.Document.PageWidth = rightText.ActualWidth; } } #endregion } }