mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-24 20:57:19 -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 bin
|
||||||
rmdir /s /q obj
|
rmdir /s /q obj
|
||||||
dotnet publish SourceGit_48.csproj --nologo -c Release -r win-x86 -o ..\publish\net48
|
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
|
cd ..\publish
|
||||||
ren SourceGit.exe SourceGit_48.exe
|
ren SourceGit.exe SourceGit_48.exe
|
||||||
rmdir /s /q net48
|
rmdir /s /q net48
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace SourceGit.Commands {
|
namespace SourceGit.Commands {
|
||||||
|
@ -8,6 +9,8 @@ namespace SourceGit.Commands {
|
||||||
public class Diff : Command {
|
public class Diff : Command {
|
||||||
private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@");
|
private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@");
|
||||||
private Models.TextChanges changes = new Models.TextChanges();
|
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 oldLine = 0;
|
||||||
private int newLine = 0;
|
private int newLine = 0;
|
||||||
|
|
||||||
|
@ -18,6 +21,7 @@ namespace SourceGit.Commands {
|
||||||
|
|
||||||
public Models.TextChanges Result() {
|
public Models.TextChanges Result() {
|
||||||
Exec();
|
Exec();
|
||||||
|
ProcessChanges();
|
||||||
if (changes.IsBinary) changes.Lines.Clear();
|
if (changes.IsBinary) changes.Lines.Clear();
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
@ -37,6 +41,7 @@ namespace SourceGit.Commands {
|
||||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Indicator, line, "", ""));
|
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Indicator, line, "", ""));
|
||||||
} else {
|
} else {
|
||||||
if (line.Length == 0) {
|
if (line.Length == 0) {
|
||||||
|
ProcessChanges();
|
||||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Normal, "", $"{oldLine}", $"{newLine}"));
|
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Normal, "", $"{oldLine}", $"{newLine}"));
|
||||||
oldLine++;
|
oldLine++;
|
||||||
newLine++;
|
newLine++;
|
||||||
|
@ -45,12 +50,13 @@ namespace SourceGit.Commands {
|
||||||
|
|
||||||
var ch = line[0];
|
var ch = line[0];
|
||||||
if (ch == '-') {
|
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++;
|
oldLine++;
|
||||||
} else if (ch == '+') {
|
} 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++;
|
newLine++;
|
||||||
} else if (ch != '\\') {
|
} else if (ch != '\\') {
|
||||||
|
ProcessChanges();
|
||||||
var match = REG_INDICATOR.Match(line);
|
var match = REG_INDICATOR.Match(line);
|
||||||
if (match.Success) {
|
if (match.Success) {
|
||||||
oldLine = int.Parse(match.Groups[1].Value);
|
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,
|
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 class Line {
|
||||||
public LineMode Mode = LineMode.Normal;
|
public LineMode Mode = LineMode.Normal;
|
||||||
public string Content = "";
|
public string Content = "";
|
||||||
public string OldLine = "";
|
public string OldLine = "";
|
||||||
public string NewLine = "";
|
public string NewLine = "";
|
||||||
|
public List<HighlightRange> Highlights = new List<HighlightRange>();
|
||||||
|
|
||||||
public Line(LineMode mode, string content, string oldLine, string newLine) {
|
public Line(LineMode mode, string content, string oldLine, string newLine) {
|
||||||
Mode = mode;
|
Mode = mode;
|
||||||
|
|
|
@ -17,4 +17,7 @@
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DiffPlex" Version="1.7.0" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -21,5 +21,6 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
<PackageReference Include="DiffPlex" Version="1.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</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_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_DELETED = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
|
||||||
private static readonly Brush BG_NORMAL = Brushes.Transparent;
|
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 class Option {
|
||||||
public string[] RevisionRange = new string[] { };
|
public string[] RevisionRange = new string[] { };
|
||||||
|
@ -29,13 +32,13 @@ namespace SourceGit.Views.Widgets {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Block {
|
public class Block {
|
||||||
public string Content { get; set; }
|
|
||||||
public Models.TextChanges.LineMode Mode { get; set; }
|
public Models.TextChanges.LineMode Mode { get; set; }
|
||||||
public Brush BG { get; set; }
|
public Brush BG { get; set; }
|
||||||
public Brush FG { get; set; }
|
public Brush FG { get; set; }
|
||||||
public FontStyle Style { get; set; }
|
public FontStyle Style { get; set; }
|
||||||
public string OldLine { get; set; }
|
public string OldLine { get; set; }
|
||||||
public string NewLine { get; set; }
|
public string NewLine { get; set; }
|
||||||
|
public Controls.HighlightableTextBlock.Data Data { get; set; } = new Controls.HighlightableTextBlock.Data();
|
||||||
|
|
||||||
public bool IsContent {
|
public bool IsContent {
|
||||||
get {
|
get {
|
||||||
|
@ -216,14 +219,15 @@ namespace SourceGit.Views.Widgets {
|
||||||
|
|
||||||
foreach (var line in cachedTextChanges) {
|
foreach (var line in cachedTextChanges) {
|
||||||
var block = new Block();
|
var block = new Block();
|
||||||
block.Content = line.Content;
|
|
||||||
block.Mode = line.Mode;
|
block.Mode = line.Mode;
|
||||||
block.BG = GetLineBackground(line);
|
block.BG = GetLineBackground(line);
|
||||||
block.FG = block.IsContent ? fgCommon : fgIndicator;
|
block.FG = block.IsContent ? fgCommon : fgIndicator;
|
||||||
block.Style = block.IsContent ? FontStyles.Normal : FontStyles.Italic;
|
block.Style = block.IsContent ? FontStyles.Normal : FontStyles.Italic;
|
||||||
block.OldLine = line.OldLine;
|
block.OldLine = line.OldLine;
|
||||||
block.NewLine = line.NewLine;
|
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.OldLine.Length > 0) lastOldLine = line.OldLine;
|
||||||
if (line.NewLine.Length > 0) lastNewLine = line.NewLine;
|
if (line.NewLine.Length > 0) lastNewLine = line.NewLine;
|
||||||
|
@ -278,13 +282,15 @@ namespace SourceGit.Views.Widgets {
|
||||||
|
|
||||||
foreach (var line in cachedTextChanges) {
|
foreach (var line in cachedTextChanges) {
|
||||||
var block = new Block();
|
var block = new Block();
|
||||||
block.Content = line.Content;
|
|
||||||
block.Mode = line.Mode;
|
block.Mode = line.Mode;
|
||||||
block.BG = GetLineBackground(line);
|
block.BG = GetLineBackground(line);
|
||||||
block.FG = block.IsContent ? fgCommon : fgIndicator;
|
block.FG = block.IsContent ? fgCommon : fgIndicator;
|
||||||
block.Style = block.IsContent ? FontStyles.Normal : FontStyles.Italic;
|
block.Style = block.IsContent ? FontStyles.Normal : FontStyles.Italic;
|
||||||
block.OldLine = line.OldLine;
|
block.OldLine = line.OldLine;
|
||||||
block.NewLine = line.NewLine;
|
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.OldLine.Length > 0) lastOldLine = line.OldLine;
|
||||||
if (line.NewLine.Length > 0) lastNewLine = line.NewLine;
|
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) {
|
private void FillEmptyLines(List<Block> old, List<Block> cur) {
|
||||||
if (old.Count < cur.Count) {
|
if (old.Count < cur.Count) {
|
||||||
int diff = cur.Count - old.Count;
|
int diff = cur.Count - old.Count;
|
||||||
|
|
||||||
for (int i = 0; i < diff; i++) {
|
for (int i = 0; i < diff; i++) {
|
||||||
var empty = new Block();
|
var empty = new Block();
|
||||||
empty.Content = "";
|
|
||||||
empty.Mode = Models.TextChanges.LineMode.None;
|
empty.Mode = Models.TextChanges.LineMode.None;
|
||||||
empty.BG = BG_EMPTY;
|
empty.BG = BG_EMPTY;
|
||||||
empty.FG = Brushes.Transparent;
|
empty.FG = Brushes.Transparent;
|
||||||
|
@ -389,7 +405,6 @@ namespace SourceGit.Views.Widgets {
|
||||||
|
|
||||||
for (int i = 0; i < diff; i++) {
|
for (int i = 0; i < diff; i++) {
|
||||||
var empty = new Block();
|
var empty = new Block();
|
||||||
empty.Content = "";
|
|
||||||
empty.Mode = Models.TextChanges.LineMode.None;
|
empty.Mode = Models.TextChanges.LineMode.None;
|
||||||
empty.BG = BG_EMPTY;
|
empty.BG = BG_EMPTY;
|
||||||
empty.FG = Brushes.Transparent;
|
empty.FG = Brushes.Transparent;
|
||||||
|
@ -432,7 +447,7 @@ namespace SourceGit.Views.Widgets {
|
||||||
if (block == null) continue;
|
if (block == null) continue;
|
||||||
if (!block.IsContent) continue;
|
if (!block.IsContent) continue;
|
||||||
|
|
||||||
builder.Append(block.Content);
|
builder.Append(block.Data.Text);
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,27 +462,21 @@ namespace SourceGit.Views.Widgets {
|
||||||
grid.Columns.Add(colLineNumber);
|
grid.Columns.Add(colLineNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
var borderContent = new FrameworkElementFactory(typeof(Border));
|
var line = new FrameworkElementFactory(typeof(Controls.HighlightableTextBlock));
|
||||||
borderContent.SetBinding(Border.BackgroundProperty, new Binding("BG"));
|
line.SetBinding(Controls.HighlightableTextBlock.ContentProperty, new Binding("Data"));
|
||||||
|
line.SetBinding(TextBlock.BackgroundProperty, new Binding("BG"));
|
||||||
var textContent = new FrameworkElementFactory(typeof(TextBlock));
|
line.SetBinding(TextBlock.ForegroundProperty, new Binding("FG"));
|
||||||
textContent.SetBinding(TextBlock.TextProperty, new Binding("Content"));
|
line.SetBinding(TextBlock.FontStyleProperty, new Binding("Style"));
|
||||||
textContent.SetBinding(TextBlock.ForegroundProperty, new Binding("FG"));
|
line.SetValue(TextBlock.FontSizeProperty, new FontSizeConverter().ConvertFrom("9pt"));
|
||||||
textContent.SetBinding(TextBlock.FontStyleProperty, new Binding("Style"));
|
line.SetValue(TextBlock.MarginProperty, new Thickness(0));
|
||||||
textContent.SetValue(TextBlock.BackgroundProperty, Brushes.Transparent);
|
line.SetValue(TextBlock.PaddingProperty, new Thickness(4, 0, 0, 0));
|
||||||
textContent.SetValue(TextBlock.FontSizeProperty, new FontSizeConverter().ConvertFrom("9pt"));
|
line.SetValue(TextBlock.LineHeightProperty, 16.0);
|
||||||
textContent.SetValue(TextBlock.MarginProperty, new Thickness(0));
|
line.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display);
|
||||||
textContent.SetValue(TextBlock.PaddingProperty, new Thickness(4, 0, 0, 0));
|
line.SetValue(TextOptions.TextRenderingModeProperty, TextRenderingMode.ClearType);
|
||||||
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 colContent = new DataGridTemplateColumn();
|
var colContent = new DataGridTemplateColumn();
|
||||||
colContent.CellTemplate = new DataTemplate();
|
colContent.CellTemplate = new DataTemplate();
|
||||||
colContent.CellTemplate.VisualTree = visualTree;
|
colContent.CellTemplate.VisualTree = line;
|
||||||
colContent.Width = DataGridLength.SizeToCells;
|
colContent.Width = DataGridLength.SizeToCells;
|
||||||
grid.Columns.Add(colContent);
|
grid.Columns.Add(colContent);
|
||||||
|
|
||||||
|
@ -567,7 +576,7 @@ namespace SourceGit.Views.Widgets {
|
||||||
if (block == null) continue;
|
if (block == null) continue;
|
||||||
if (!block.IsContent) continue;
|
if (!block.IsContent) continue;
|
||||||
|
|
||||||
builder.Append(block.Content);
|
builder.Append(block.Data.Text);
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue