mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-23 20:47:25 -08:00
feature<Diff>: highlights differences for modified lines (both added and removed)
This commit is contained in:
parent
776defe6c3
commit
92de6f2b79
7 changed files with 142 additions and 29 deletions
|
@ -6,7 +6,7 @@ cd src
|
|||
rmdir /s /q bin
|
||||
rmdir /s /q obj
|
||||
dotnet publish SourceGit_48.csproj --nologo -c Release -r win-x86 -o ..\publish\net48
|
||||
ilrepack /ndebug /out:..\publish\SourceGit.exe ..\publish\net48\SourceGit.exe ..\publish\net48\Newtonsoft.Json.dll
|
||||
ilrepack /ndebug /out:..\publish\SourceGit.exe ..\publish\net48\SourceGit.exe ..\publish\net48\Newtonsoft.Json.dll ..\publish\net48\DiffPlex.dll
|
||||
cd ..\publish
|
||||
ren SourceGit.exe SourceGit_48.exe
|
||||
rmdir /s /q net48
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
@ -8,6 +9,8 @@ namespace SourceGit.Commands {
|
|||
public class Diff : Command {
|
||||
private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@");
|
||||
private Models.TextChanges changes = new Models.TextChanges();
|
||||
private List<Models.TextChanges.Line> deleted = new List<Models.TextChanges.Line>();
|
||||
private List<Models.TextChanges.Line> added = new List<Models.TextChanges.Line>();
|
||||
private int oldLine = 0;
|
||||
private int newLine = 0;
|
||||
|
||||
|
@ -18,6 +21,7 @@ namespace SourceGit.Commands {
|
|||
|
||||
public Models.TextChanges Result() {
|
||||
Exec();
|
||||
ProcessChanges();
|
||||
if (changes.IsBinary) changes.Lines.Clear();
|
||||
return changes;
|
||||
}
|
||||
|
@ -37,6 +41,7 @@ namespace SourceGit.Commands {
|
|||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Indicator, line, "", ""));
|
||||
} else {
|
||||
if (line.Length == 0) {
|
||||
ProcessChanges();
|
||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Normal, "", $"{oldLine}", $"{newLine}"));
|
||||
oldLine++;
|
||||
newLine++;
|
||||
|
@ -45,12 +50,13 @@ namespace SourceGit.Commands {
|
|||
|
||||
var ch = line[0];
|
||||
if (ch == '-') {
|
||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Deleted, line.Substring(1), $"{oldLine}", ""));
|
||||
deleted.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Deleted, line.Substring(1), $"{oldLine}", ""));
|
||||
oldLine++;
|
||||
} else if (ch == '+') {
|
||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Added, line.Substring(1), "", $"{newLine}"));
|
||||
added.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Added, line.Substring(1), "", $"{newLine}"));
|
||||
newLine++;
|
||||
} else if (ch != '\\') {
|
||||
ProcessChanges();
|
||||
var match = REG_INDICATOR.Match(line);
|
||||
if (match.Success) {
|
||||
oldLine = int.Parse(match.Groups[1].Value);
|
||||
|
@ -64,5 +70,29 @@ namespace SourceGit.Commands {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessChanges() {
|
||||
if (deleted.Count > 0) {
|
||||
if (added.Count == deleted.Count) {
|
||||
for (int i = added.Count - 1; i >= 0; i--) {
|
||||
var left = deleted[i];
|
||||
var right = added[i];
|
||||
var result = DiffPlex.Differ.Instance.CreateCharacterDiffs(left.Content, right.Content, false, false);
|
||||
foreach (var block in result.DiffBlocks) {
|
||||
left.Highlights.Add(new Models.TextChanges.HighlightRange(block.DeleteStartA, block.DeleteCountA));
|
||||
right.Highlights.Add(new Models.TextChanges.HighlightRange(block.InsertStartB, block.InsertCountB));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changes.Lines.AddRange(deleted);
|
||||
deleted.Clear();
|
||||
}
|
||||
|
||||
if (added.Count > 0) {
|
||||
changes.Lines.AddRange(added);
|
||||
added.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,18 @@ namespace SourceGit.Models {
|
|||
Deleted,
|
||||
}
|
||||
|
||||
public class HighlightRange {
|
||||
public int Start { get; set; }
|
||||
public int Count { get; set; }
|
||||
public HighlightRange(int p, int n) { Start = p; Count = n; }
|
||||
}
|
||||
|
||||
public class Line {
|
||||
public LineMode Mode = LineMode.Normal;
|
||||
public string Content = "";
|
||||
public string OldLine = "";
|
||||
public string NewLine = "";
|
||||
public List<HighlightRange> Highlights = new List<HighlightRange>();
|
||||
|
||||
public Line(LineMode mode, string content, string oldLine, string newLine) {
|
||||
Mode = mode;
|
||||
|
|
|
@ -17,4 +17,7 @@
|
|||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiffPlex" Version="1.7.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -21,5 +21,6 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="DiffPlex" Version="1.7.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
63
src/Views/Controls/HighlightableTextBlock.cs
Normal file
63
src/Views/Controls/HighlightableTextBlock.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
/// <summary>
|
||||
/// 支持部分高亮的文本组件
|
||||
/// </summary>
|
||||
public class HighlightableTextBlock : TextBlock {
|
||||
|
||||
public class Data {
|
||||
public string Text { get; set; } = "";
|
||||
public List<Models.TextChanges.HighlightRange> Highlights { get; set; } = new List<Models.TextChanges.HighlightRange>();
|
||||
public Brush HighlightBrush { get; set; } = Brushes.Transparent;
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
|
||||
"Content",
|
||||
typeof(Data),
|
||||
typeof(HighlightableTextBlock),
|
||||
new PropertyMetadata(null, OnContentChanged));
|
||||
|
||||
public Data Content {
|
||||
get { return (Data)GetValue(ContentProperty); }
|
||||
set { SetValue(ContentProperty, value); }
|
||||
}
|
||||
|
||||
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var txt = d as HighlightableTextBlock;
|
||||
if (txt == null) return;
|
||||
|
||||
txt.Inlines.Clear();
|
||||
txt.Text = null;
|
||||
if (txt.Content == null) return;
|
||||
|
||||
if (txt.Content.Highlights == null || txt.Content.Highlights.Count == 0) {
|
||||
txt.Text = txt.Content.Text;
|
||||
return;
|
||||
}
|
||||
|
||||
var started = 0;
|
||||
foreach (var highlight in txt.Content.Highlights) {
|
||||
if (started < highlight.Start) {
|
||||
txt.Inlines.Add(new Run(txt.Content.Text.Substring(started, highlight.Start - started)));
|
||||
}
|
||||
|
||||
txt.Inlines.Add(new TextBlock() {
|
||||
Background = txt.Content.HighlightBrush,
|
||||
LineHeight = txt.LineHeight,
|
||||
Text = txt.Content.Text.Substring(highlight.Start, highlight.Count),
|
||||
});
|
||||
|
||||
started = highlight.Start + highlight.Count;
|
||||
}
|
||||
|
||||
if (started < txt.Content.Text.Length) {
|
||||
txt.Inlines.Add(new Run(txt.Content.Text.Substring(started)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,9 @@ namespace SourceGit.Views.Widgets {
|
|||
private static readonly Brush BG_ADDED = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
|
||||
private static readonly Brush BG_DELETED = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
|
||||
private static readonly Brush BG_NORMAL = Brushes.Transparent;
|
||||
private static readonly Brush HL_ADDED = new SolidColorBrush(Color.FromArgb(100, 0, 255, 0));
|
||||
private static readonly Brush HL_DELETED = new SolidColorBrush(Color.FromArgb(100, 255, 0, 0));
|
||||
private static readonly Brush HL_NORMAL = Brushes.Transparent;
|
||||
|
||||
public class Option {
|
||||
public string[] RevisionRange = new string[] { };
|
||||
|
@ -29,13 +32,13 @@ namespace SourceGit.Views.Widgets {
|
|||
}
|
||||
|
||||
public class Block {
|
||||
public string Content { get; set; }
|
||||
public Models.TextChanges.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; }
|
||||
public Controls.HighlightableTextBlock.Data Data { get; set; } = new Controls.HighlightableTextBlock.Data();
|
||||
|
||||
public bool IsContent {
|
||||
get {
|
||||
|
@ -216,14 +219,15 @@ namespace SourceGit.Views.Widgets {
|
|||
|
||||
foreach (var line in cachedTextChanges) {
|
||||
var block = new Block();
|
||||
block.Content = line.Content;
|
||||
block.Mode = line.Mode;
|
||||
block.BG = GetLineBackground(line);
|
||||
block.FG = block.IsContent ? fgCommon : fgIndicator;
|
||||
block.Style = block.IsContent ? FontStyles.Normal : FontStyles.Italic;
|
||||
block.OldLine = line.OldLine;
|
||||
block.NewLine = line.NewLine;
|
||||
|
||||
block.Data.Text = line.Content;
|
||||
block.Data.HighlightBrush = GetLineHighlight(line);
|
||||
block.Data.Highlights.AddRange(line.Highlights);
|
||||
|
||||
if (line.OldLine.Length > 0) lastOldLine = line.OldLine;
|
||||
if (line.NewLine.Length > 0) lastNewLine = line.NewLine;
|
||||
|
@ -278,13 +282,15 @@ namespace SourceGit.Views.Widgets {
|
|||
|
||||
foreach (var line in cachedTextChanges) {
|
||||
var block = new Block();
|
||||
block.Content = line.Content;
|
||||
block.Mode = line.Mode;
|
||||
block.BG = GetLineBackground(line);
|
||||
block.FG = block.IsContent ? fgCommon : fgIndicator;
|
||||
block.Style = block.IsContent ? FontStyles.Normal : FontStyles.Italic;
|
||||
block.OldLine = line.OldLine;
|
||||
block.NewLine = line.NewLine;
|
||||
block.Data.Text = line.Content;
|
||||
block.Data.HighlightBrush = GetLineHighlight(line);
|
||||
block.Data.Highlights.AddRange(line.Highlights);
|
||||
|
||||
if (line.OldLine.Length > 0) lastOldLine = line.OldLine;
|
||||
if (line.NewLine.Length > 0) lastNewLine = line.NewLine;
|
||||
|
@ -369,13 +375,23 @@ namespace SourceGit.Views.Widgets {
|
|||
}
|
||||
}
|
||||
|
||||
private Brush GetLineHighlight(Models.TextChanges.Line line) {
|
||||
switch (line.Mode) {
|
||||
case Models.TextChanges.LineMode.Added:
|
||||
return HL_ADDED;
|
||||
case Models.TextChanges.LineMode.Deleted:
|
||||
return HL_DELETED;
|
||||
default:
|
||||
return HL_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
private void FillEmptyLines(List<Block> old, List<Block> cur) {
|
||||
if (old.Count < cur.Count) {
|
||||
int diff = cur.Count - old.Count;
|
||||
|
||||
for (int i = 0; i < diff; i++) {
|
||||
var empty = new Block();
|
||||
empty.Content = "";
|
||||
empty.Mode = Models.TextChanges.LineMode.None;
|
||||
empty.BG = BG_EMPTY;
|
||||
empty.FG = Brushes.Transparent;
|
||||
|
@ -389,7 +405,6 @@ namespace SourceGit.Views.Widgets {
|
|||
|
||||
for (int i = 0; i < diff; i++) {
|
||||
var empty = new Block();
|
||||
empty.Content = "";
|
||||
empty.Mode = Models.TextChanges.LineMode.None;
|
||||
empty.BG = BG_EMPTY;
|
||||
empty.FG = Brushes.Transparent;
|
||||
|
@ -432,7 +447,7 @@ namespace SourceGit.Views.Widgets {
|
|||
if (block == null) continue;
|
||||
if (!block.IsContent) continue;
|
||||
|
||||
builder.Append(block.Content);
|
||||
builder.Append(block.Data.Text);
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
|
@ -447,27 +462,21 @@ namespace SourceGit.Views.Widgets {
|
|||
grid.Columns.Add(colLineNumber);
|
||||
}
|
||||
|
||||
var borderContent = new FrameworkElementFactory(typeof(Border));
|
||||
borderContent.SetBinding(Border.BackgroundProperty, new Binding("BG"));
|
||||
|
||||
var textContent = new FrameworkElementFactory(typeof(TextBlock));
|
||||
textContent.SetBinding(TextBlock.TextProperty, new Binding("Content"));
|
||||
textContent.SetBinding(TextBlock.ForegroundProperty, new Binding("FG"));
|
||||
textContent.SetBinding(TextBlock.FontStyleProperty, new Binding("Style"));
|
||||
textContent.SetValue(TextBlock.BackgroundProperty, Brushes.Transparent);
|
||||
textContent.SetValue(TextBlock.FontSizeProperty, new FontSizeConverter().ConvertFrom("9pt"));
|
||||
textContent.SetValue(TextBlock.MarginProperty, new Thickness(0));
|
||||
textContent.SetValue(TextBlock.PaddingProperty, new Thickness(4, 0, 0, 0));
|
||||
textContent.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display);
|
||||
textContent.SetValue(TextOptions.TextRenderingModeProperty, TextRenderingMode.ClearType);
|
||||
|
||||
var visualTree = new FrameworkElementFactory(typeof(Grid));
|
||||
visualTree.AppendChild(borderContent);
|
||||
visualTree.AppendChild(textContent);
|
||||
var line = new FrameworkElementFactory(typeof(Controls.HighlightableTextBlock));
|
||||
line.SetBinding(Controls.HighlightableTextBlock.ContentProperty, new Binding("Data"));
|
||||
line.SetBinding(TextBlock.BackgroundProperty, new Binding("BG"));
|
||||
line.SetBinding(TextBlock.ForegroundProperty, new Binding("FG"));
|
||||
line.SetBinding(TextBlock.FontStyleProperty, new Binding("Style"));
|
||||
line.SetValue(TextBlock.FontSizeProperty, new FontSizeConverter().ConvertFrom("9pt"));
|
||||
line.SetValue(TextBlock.MarginProperty, new Thickness(0));
|
||||
line.SetValue(TextBlock.PaddingProperty, new Thickness(4, 0, 0, 0));
|
||||
line.SetValue(TextBlock.LineHeightProperty, 16.0);
|
||||
line.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display);
|
||||
line.SetValue(TextOptions.TextRenderingModeProperty, TextRenderingMode.ClearType);
|
||||
|
||||
var colContent = new DataGridTemplateColumn();
|
||||
colContent.CellTemplate = new DataTemplate();
|
||||
colContent.CellTemplate.VisualTree = visualTree;
|
||||
colContent.CellTemplate.VisualTree = line;
|
||||
colContent.Width = DataGridLength.SizeToCells;
|
||||
grid.Columns.Add(colContent);
|
||||
|
||||
|
@ -567,7 +576,7 @@ namespace SourceGit.Views.Widgets {
|
|||
if (block == null) continue;
|
||||
if (!block.IsContent) continue;
|
||||
|
||||
builder.Append(block.Content);
|
||||
builder.Append(block.Data.Text);
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue