mirror of
https://fastgit.cc/github.com/sourcegit-scm/sourcegit
synced 2026-04-25 11:24:02 +08:00
refactor: rewrite text diff view
- Move some data-only code from `Views` to `ViewModels` - Remove unnecessary attributes - This commit also contains a feature request #1722 Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
@@ -20,11 +20,7 @@ namespace SourceGit.Commands
|
||||
|
||||
public Diff(string repo, Models.DiffOption opt, int unified, bool ignoreWhitespace)
|
||||
{
|
||||
_result.TextDiff = new Models.TextDiff()
|
||||
{
|
||||
Repo = repo,
|
||||
Option = opt,
|
||||
};
|
||||
_result.TextDiff = new Models.TextDiff() { Option = opt };
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Media.Imaging;
|
||||
|
||||
namespace SourceGit.Models
|
||||
@@ -62,12 +60,9 @@ namespace SourceGit.Models
|
||||
public partial class TextDiff
|
||||
{
|
||||
public string File { get; set; } = string.Empty;
|
||||
public List<TextDiffLine> Lines { get; set; } = new List<TextDiffLine>();
|
||||
public Vector ScrollOffset { get; set; } = Vector.Zero;
|
||||
public int MaxLineNumber = 0;
|
||||
|
||||
public string Repo { get; set; } = null;
|
||||
public DiffOption Option { get; set; } = null;
|
||||
public List<TextDiffLine> Lines { get; set; } = new List<TextDiffLine>();
|
||||
public int MaxLineNumber = 0;
|
||||
|
||||
public TextDiffSelection MakeSelection(int startLine, int endLine, bool isCombined, bool isOldSide)
|
||||
{
|
||||
|
||||
@@ -48,17 +48,11 @@ namespace SourceGit.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public BlockNavigation(object context)
|
||||
public BlockNavigation(List<Models.TextDiffLine> lines)
|
||||
{
|
||||
Blocks.Clear();
|
||||
Current = -1;
|
||||
|
||||
var lines = new List<Models.TextDiffLine>();
|
||||
if (context is Models.TextDiff combined)
|
||||
lines = combined.Lines;
|
||||
else if (context is TwoSideTextDiff twoSide)
|
||||
lines = twoSide.Old;
|
||||
|
||||
if (lines.Count == 0)
|
||||
return;
|
||||
|
||||
@@ -96,7 +90,10 @@ namespace SourceGit.ViewModels
|
||||
|
||||
public Block GetCurrentBlock()
|
||||
{
|
||||
return (_current >= 0 && _current < Blocks.Count) ? Blocks[_current] : null;
|
||||
if (_current >= 0 && _current < Blocks.Count)
|
||||
return Blocks[_current];
|
||||
|
||||
return Blocks.Count > 0 ? Blocks[0] : null;
|
||||
}
|
||||
|
||||
public Block GotoFirst()
|
||||
@@ -105,6 +102,7 @@ namespace SourceGit.ViewModels
|
||||
return null;
|
||||
|
||||
Current = 0;
|
||||
OnPropertyChanged(nameof(Indicator));
|
||||
return Blocks[_current];
|
||||
}
|
||||
|
||||
@@ -117,6 +115,8 @@ namespace SourceGit.ViewModels
|
||||
Current = 0;
|
||||
else if (_current > 0)
|
||||
Current = _current - 1;
|
||||
|
||||
OnPropertyChanged(nameof(Indicator));
|
||||
return Blocks[_current];
|
||||
}
|
||||
|
||||
@@ -127,6 +127,8 @@ namespace SourceGit.ViewModels
|
||||
|
||||
if (_current < Blocks.Count - 1)
|
||||
Current = _current + 1;
|
||||
|
||||
OnPropertyChanged(nameof(Indicator));
|
||||
return Blocks[_current];
|
||||
}
|
||||
|
||||
@@ -136,9 +138,35 @@ namespace SourceGit.ViewModels
|
||||
return null;
|
||||
|
||||
Current = Blocks.Count - 1;
|
||||
OnPropertyChanged(nameof(Indicator));
|
||||
return Blocks[_current];
|
||||
}
|
||||
|
||||
public void AutoUpdate(int start, int end)
|
||||
{
|
||||
if (_current >= 0 && _current < Blocks.Count)
|
||||
{
|
||||
var block = Blocks[_current];
|
||||
if ((block.Start >= start && block.Start <= end) ||
|
||||
(block.End >= start && block.End <= end) ||
|
||||
(block.Start <= start && block.End >= end))
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < Blocks.Count; i++)
|
||||
{
|
||||
var block = Blocks[i];
|
||||
if ((block.Start >= start && block.Start <= end) ||
|
||||
(block.End >= start && block.End <= end) ||
|
||||
(block.Start <= start && block.End >= end))
|
||||
{
|
||||
Current = i;
|
||||
OnPropertyChanged(nameof(Indicator));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _current = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
@@ -24,7 +22,58 @@ namespace SourceGit.ViewModels
|
||||
{
|
||||
Preferences.Instance.IgnoreWhitespaceChangesInDiff = value;
|
||||
OnPropertyChanged();
|
||||
LoadDiffContent();
|
||||
|
||||
if (_isTextDiff)
|
||||
LoadContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowEntireFile
|
||||
{
|
||||
get => Preferences.Instance.UseFullTextDiff;
|
||||
set
|
||||
{
|
||||
if (value != Preferences.Instance.UseFullTextDiff)
|
||||
{
|
||||
Preferences.Instance.UseFullTextDiff = value;
|
||||
OnPropertyChanged();
|
||||
|
||||
if (Content is TextDiffContext ctx)
|
||||
{
|
||||
ctx.Data.File = string.Empty; // Just to ignore both previous `ScrollOffset` and `BlockNavigation`
|
||||
LoadContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseBlockNavigation
|
||||
{
|
||||
get => Preferences.Instance.UseBlockNavigationInDiffView;
|
||||
set
|
||||
{
|
||||
if (value != Preferences.Instance.UseBlockNavigationInDiffView)
|
||||
{
|
||||
Preferences.Instance.UseBlockNavigationInDiffView = value;
|
||||
OnPropertyChanged();
|
||||
(Content as TextDiffContext)?.ResetBlockNavigation(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseSideBySide
|
||||
{
|
||||
get => Preferences.Instance.UseSideBySideDiff;
|
||||
set
|
||||
{
|
||||
if (value != Preferences.Instance.UseSideBySideDiff)
|
||||
{
|
||||
Preferences.Instance.UseSideBySideDiff = value;
|
||||
OnPropertyChanged();
|
||||
|
||||
if (Content is TextDiffContext ctx && ctx.IsSideBySide() != value)
|
||||
Content = ctx.SwitchMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,25 +121,19 @@ namespace SourceGit.ViewModels
|
||||
else
|
||||
Title = $"{_option.OrgPath} → {_option.Path}";
|
||||
|
||||
LoadDiffContent();
|
||||
}
|
||||
|
||||
public void ToggleFullTextDiff()
|
||||
{
|
||||
Preferences.Instance.UseFullTextDiff = !Preferences.Instance.UseFullTextDiff;
|
||||
LoadDiffContent();
|
||||
LoadContent();
|
||||
}
|
||||
|
||||
public void IncrUnified()
|
||||
{
|
||||
UnifiedLines = _unifiedLines + 1;
|
||||
LoadDiffContent();
|
||||
LoadContent();
|
||||
}
|
||||
|
||||
public void DecrUnified()
|
||||
{
|
||||
UnifiedLines = Math.Max(4, _unifiedLines - 1);
|
||||
LoadDiffContent();
|
||||
LoadContent();
|
||||
}
|
||||
|
||||
public void OpenExternalMergeTool()
|
||||
@@ -98,7 +141,29 @@ namespace SourceGit.ViewModels
|
||||
new Commands.DiffTool(_repo, _option).Open();
|
||||
}
|
||||
|
||||
private void LoadDiffContent()
|
||||
public void CheckSettings()
|
||||
{
|
||||
if (Content is TextDiffContext ctx)
|
||||
{
|
||||
if ((ShowEntireFile && _info.UnifiedLines != _entireFileLine) ||
|
||||
(!ShowEntireFile && _info.UnifiedLines == _entireFileLine) ||
|
||||
(IgnoreWhitespace != _info.IgnoreWhitespace))
|
||||
{
|
||||
LoadContent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.IsSideBySide() != UseSideBySide)
|
||||
{
|
||||
ctx = ctx.SwitchMode();
|
||||
Content = ctx;
|
||||
}
|
||||
|
||||
ctx.ResetBlockNavigation(UseBlockNavigation);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadContent()
|
||||
{
|
||||
if (_option.Path.EndsWith('/'))
|
||||
{
|
||||
@@ -109,7 +174,7 @@ namespace SourceGit.ViewModels
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var numLines = Preferences.Instance.UseFullTextDiff ? 999999999 : _unifiedLines;
|
||||
var numLines = Preferences.Instance.UseFullTextDiff ? _entireFileLine : _unifiedLines;
|
||||
var ignoreWhitespace = Preferences.Instance.IgnoreWhitespaceChangesInDiff;
|
||||
|
||||
var latest = await new Commands.Diff(_repo, _option, numLines, ignoreWhitespace)
|
||||
@@ -228,12 +293,23 @@ namespace SourceGit.ViewModels
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (_content is Models.TextDiff old && rs is Models.TextDiff cur && old.File == cur.File)
|
||||
cur.ScrollOffset = old.ScrollOffset;
|
||||
|
||||
FileModeChange = latest.FileModeChange;
|
||||
Content = rs;
|
||||
IsTextDiff = rs is Models.TextDiff;
|
||||
|
||||
if (rs is Models.TextDiff cur)
|
||||
{
|
||||
IsTextDiff = true;
|
||||
|
||||
var hasBlockNavigation = Preferences.Instance.UseBlockNavigationInDiffView;
|
||||
if (Preferences.Instance.UseSideBySideDiff)
|
||||
Content = new TwoSideTextDiff(cur, hasBlockNavigation, _content as TwoSideTextDiff);
|
||||
else
|
||||
Content = new CombinedTextDiff(cur, hasBlockNavigation, _content as CombinedTextDiff);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTextDiff = false;
|
||||
Content = rs;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -279,6 +355,7 @@ namespace SourceGit.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private readonly int _entireFileLine = 999999999;
|
||||
private readonly string _repo;
|
||||
private readonly Models.DiffOption _option = null;
|
||||
private string _fileModeChange = string.Empty;
|
||||
|
||||
291
src/ViewModels/TextDiffContext.cs
Normal file
291
src/ViewModels/TextDiffContext.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public record TextDiffSelectedChunk(double y, double h, int start, int end, bool combined, bool isOldSide)
|
||||
{
|
||||
public double Y { get; set; } = y;
|
||||
public double Height { get; set; } = h;
|
||||
public int StartIdx { get; set; } = start;
|
||||
public int EndIdx { get; set; } = end;
|
||||
public bool Combined { get; set; } = combined;
|
||||
public bool IsOldSide { get; set; } = isOldSide;
|
||||
|
||||
public static bool IsChanged(TextDiffSelectedChunk oldValue, TextDiffSelectedChunk newValue)
|
||||
{
|
||||
if (newValue == null)
|
||||
return oldValue != null;
|
||||
|
||||
if (oldValue == null)
|
||||
return true;
|
||||
|
||||
return Math.Abs(newValue.Y - oldValue.Y) > 0.001 ||
|
||||
Math.Abs(newValue.Height - oldValue.Height) > 0.001 ||
|
||||
newValue.StartIdx != oldValue.StartIdx ||
|
||||
newValue.EndIdx != oldValue.EndIdx ||
|
||||
newValue.Combined != oldValue.Combined ||
|
||||
newValue.IsOldSide != oldValue.IsOldSide;
|
||||
}
|
||||
}
|
||||
|
||||
public record TextDiffDisplayRange(int start, int end)
|
||||
{
|
||||
public int Start { get; set; } = start;
|
||||
public int End { get; set; } = end;
|
||||
}
|
||||
|
||||
public class TextDiffContext : ObservableObject
|
||||
{
|
||||
public Models.TextDiff Data => _data;
|
||||
public string File => _data.File;
|
||||
public bool IsUnstaged => _data.Option.IsUnstaged;
|
||||
public bool EnableChunkOption => _data.Option.WorkingCopyChange != null;
|
||||
|
||||
public Vector ScrollOffset
|
||||
{
|
||||
get => _scrollOffset;
|
||||
set => SetProperty(ref _scrollOffset, value);
|
||||
}
|
||||
|
||||
public BlockNavigation BlockNavigation
|
||||
{
|
||||
get => _blockNavigation;
|
||||
set => SetProperty(ref _blockNavigation, value);
|
||||
}
|
||||
|
||||
public TextDiffDisplayRange DisplayRange
|
||||
{
|
||||
get => _displayRange;
|
||||
set => SetProperty(ref _displayRange, value);
|
||||
}
|
||||
|
||||
public TextDiffSelectedChunk SelectedChunk
|
||||
{
|
||||
get => _selectedChunk;
|
||||
set => SetProperty(ref _selectedChunk, value);
|
||||
}
|
||||
|
||||
public void ResetBlockNavigation(bool enabled)
|
||||
{
|
||||
if (!enabled)
|
||||
BlockNavigation = null;
|
||||
else if (_blockNavigation == null)
|
||||
BlockNavigation = CreateBlockNavigation();
|
||||
}
|
||||
|
||||
public (int, int) FindRangeByIndex(List<Models.TextDiffLine> lines, int lineIdx)
|
||||
{
|
||||
var startIdx = -1;
|
||||
var endIdx = -1;
|
||||
|
||||
var normalLineCount = 0;
|
||||
var modifiedLineCount = 0;
|
||||
|
||||
for (int i = lineIdx; i >= 0; i--)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (line.Type == Models.TextDiffLineType.Indicator)
|
||||
{
|
||||
startIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.Type == Models.TextDiffLineType.Normal)
|
||||
{
|
||||
normalLineCount++;
|
||||
if (normalLineCount >= 2)
|
||||
{
|
||||
startIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
normalLineCount = 0;
|
||||
modifiedLineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
normalLineCount = lines[lineIdx].Type == Models.TextDiffLineType.Normal ? 1 : 0;
|
||||
for (int i = lineIdx + 1; i < lines.Count; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (line.Type == Models.TextDiffLineType.Indicator)
|
||||
{
|
||||
endIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.Type == Models.TextDiffLineType.Normal)
|
||||
{
|
||||
normalLineCount++;
|
||||
if (normalLineCount >= 2)
|
||||
{
|
||||
endIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
normalLineCount = 0;
|
||||
modifiedLineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIdx == -1)
|
||||
endIdx = lines.Count - 1;
|
||||
|
||||
return modifiedLineCount > 0 ? (startIdx, endIdx) : (-1, -1);
|
||||
}
|
||||
|
||||
public virtual bool IsSideBySide()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual TextDiffContext SwitchMode()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual BlockNavigation CreateBlockNavigation()
|
||||
{
|
||||
return new BlockNavigation(_data.Lines);
|
||||
}
|
||||
|
||||
protected Models.TextDiff _data = null;
|
||||
protected Vector _scrollOffset = Vector.Zero;
|
||||
protected BlockNavigation _blockNavigation = null;
|
||||
protected TextDiffDisplayRange _displayRange = null;
|
||||
protected TextDiffSelectedChunk _selectedChunk = null;
|
||||
}
|
||||
|
||||
public class CombinedTextDiff : TextDiffContext
|
||||
{
|
||||
public CombinedTextDiff(Models.TextDiff diff, bool hasBlockNavigation, CombinedTextDiff previous = null)
|
||||
{
|
||||
_data = diff;
|
||||
|
||||
if (previous != null && previous.File.Equals(File, StringComparison.Ordinal))
|
||||
_scrollOffset = previous.ScrollOffset;
|
||||
|
||||
if (hasBlockNavigation)
|
||||
_blockNavigation = CreateBlockNavigation();
|
||||
}
|
||||
|
||||
public override TextDiffContext SwitchMode()
|
||||
{
|
||||
return new TwoSideTextDiff(_data, _blockNavigation != null);
|
||||
}
|
||||
}
|
||||
|
||||
public class TwoSideTextDiff : TextDiffContext
|
||||
{
|
||||
public List<Models.TextDiffLine> Old { get; } = new List<Models.TextDiffLine>();
|
||||
public List<Models.TextDiffLine> New { get; } = new List<Models.TextDiffLine>();
|
||||
|
||||
public TwoSideTextDiff(Models.TextDiff diff, bool hasBlockNavigation, TwoSideTextDiff previous = null)
|
||||
{
|
||||
_data = diff;
|
||||
|
||||
foreach (var line in diff.Lines)
|
||||
{
|
||||
switch (line.Type)
|
||||
{
|
||||
case Models.TextDiffLineType.Added:
|
||||
New.Add(line);
|
||||
break;
|
||||
case Models.TextDiffLineType.Deleted:
|
||||
Old.Add(line);
|
||||
break;
|
||||
default:
|
||||
FillEmptyLines();
|
||||
Old.Add(line);
|
||||
New.Add(line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FillEmptyLines();
|
||||
|
||||
if (previous != null && previous.File.Equals(File, StringComparison.Ordinal))
|
||||
_scrollOffset = previous._scrollOffset;
|
||||
|
||||
if (hasBlockNavigation)
|
||||
_blockNavigation = CreateBlockNavigation();
|
||||
}
|
||||
|
||||
public override bool IsSideBySide()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override TextDiffContext SwitchMode()
|
||||
{
|
||||
return new CombinedTextDiff(_data, _blockNavigation != null);
|
||||
}
|
||||
|
||||
public override BlockNavigation CreateBlockNavigation()
|
||||
{
|
||||
return new BlockNavigation(Old);
|
||||
}
|
||||
|
||||
public void ConvertsToCombinedRange(ref int startLine, ref int endLine, bool isOldSide)
|
||||
{
|
||||
endLine = Math.Min(endLine, _data.Lines.Count - 1);
|
||||
|
||||
var oneSide = isOldSide ? Old : New;
|
||||
var firstContentLine = -1;
|
||||
for (int i = startLine; i <= endLine; i++)
|
||||
{
|
||||
var line = oneSide[i];
|
||||
if (line.Type != Models.TextDiffLineType.None)
|
||||
{
|
||||
firstContentLine = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstContentLine < 0)
|
||||
return;
|
||||
|
||||
var endContentLine = -1;
|
||||
for (int i = Math.Min(endLine, oneSide.Count - 1); i >= startLine; i--)
|
||||
{
|
||||
var line = oneSide[i];
|
||||
if (line.Type != Models.TextDiffLineType.None)
|
||||
{
|
||||
endContentLine = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endContentLine < 0)
|
||||
return;
|
||||
|
||||
var firstContent = oneSide[firstContentLine];
|
||||
var endContent = oneSide[endContentLine];
|
||||
startLine = _data.Lines.IndexOf(firstContent);
|
||||
endLine = _data.Lines.IndexOf(endContent);
|
||||
}
|
||||
|
||||
private void FillEmptyLines()
|
||||
{
|
||||
if (Old.Count < New.Count)
|
||||
{
|
||||
int diff = New.Count - Old.Count;
|
||||
for (int i = 0; i < diff; i++)
|
||||
Old.Add(new Models.TextDiffLine());
|
||||
}
|
||||
else if (Old.Count > New.Count)
|
||||
{
|
||||
int diff = Old.Count - New.Count;
|
||||
for (int i = 0; i < diff; i++)
|
||||
New.Add(new Models.TextDiffLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class TwoSideTextDiff : ObservableObject
|
||||
{
|
||||
public string File { get; set; }
|
||||
public List<Models.TextDiffLine> Old { get; set; } = new List<Models.TextDiffLine>();
|
||||
public List<Models.TextDiffLine> New { get; set; } = new List<Models.TextDiffLine>();
|
||||
public int MaxLineNumber = 0;
|
||||
|
||||
public Vector SyncScrollOffset
|
||||
{
|
||||
get => _syncScrollOffset;
|
||||
set => SetProperty(ref _syncScrollOffset, value);
|
||||
}
|
||||
|
||||
public TwoSideTextDiff(Models.TextDiff diff, TwoSideTextDiff previous = null)
|
||||
{
|
||||
File = diff.File;
|
||||
MaxLineNumber = diff.MaxLineNumber;
|
||||
|
||||
foreach (var line in diff.Lines)
|
||||
{
|
||||
switch (line.Type)
|
||||
{
|
||||
case Models.TextDiffLineType.Added:
|
||||
New.Add(line);
|
||||
break;
|
||||
case Models.TextDiffLineType.Deleted:
|
||||
Old.Add(line);
|
||||
break;
|
||||
default:
|
||||
FillEmptyLines();
|
||||
Old.Add(line);
|
||||
New.Add(line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FillEmptyLines();
|
||||
|
||||
if (previous != null && previous.File == File)
|
||||
_syncScrollOffset = previous._syncScrollOffset;
|
||||
}
|
||||
|
||||
public void ConvertsToCombinedRange(Models.TextDiff combined, ref int startLine, ref int endLine, bool isOldSide)
|
||||
{
|
||||
endLine = Math.Min(endLine, combined.Lines.Count - 1);
|
||||
|
||||
var oneSide = isOldSide ? Old : New;
|
||||
var firstContentLine = -1;
|
||||
for (int i = startLine; i <= endLine; i++)
|
||||
{
|
||||
var line = oneSide[i];
|
||||
if (line.Type != Models.TextDiffLineType.None)
|
||||
{
|
||||
firstContentLine = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstContentLine < 0)
|
||||
return;
|
||||
|
||||
var endContentLine = -1;
|
||||
for (int i = Math.Min(endLine, oneSide.Count - 1); i >= startLine; i--)
|
||||
{
|
||||
var line = oneSide[i];
|
||||
if (line.Type != Models.TextDiffLineType.None)
|
||||
{
|
||||
endContentLine = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endContentLine < 0)
|
||||
return;
|
||||
|
||||
var firstContent = oneSide[firstContentLine];
|
||||
var endContent = oneSide[endContentLine];
|
||||
startLine = combined.Lines.IndexOf(firstContent);
|
||||
endLine = combined.Lines.IndexOf(endContent);
|
||||
}
|
||||
|
||||
private void FillEmptyLines()
|
||||
{
|
||||
if (Old.Count < New.Count)
|
||||
{
|
||||
int diff = New.Count - Old.Count;
|
||||
for (int i = 0; i < diff; i++)
|
||||
Old.Add(new Models.TextDiffLine());
|
||||
}
|
||||
else if (Old.Count > New.Count)
|
||||
{
|
||||
int diff = Old.Count - New.Count;
|
||||
for (int i = 0; i < diff; i++)
|
||||
New.Add(new Models.TextDiffLine());
|
||||
}
|
||||
}
|
||||
|
||||
private Vector _syncScrollOffset = Vector.Zero;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
<Button.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="IsTextDiff"/>
|
||||
<Binding Source="{x:Static vm:Preferences.Instance}" Path="UseBlockNavigationInDiffView" Mode="OneWay"/>
|
||||
<Binding Path="UseBlockNavigation"/>
|
||||
</MultiBinding>
|
||||
</Button.IsVisible>
|
||||
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Top}"/>
|
||||
@@ -73,11 +73,20 @@
|
||||
<Border.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="IsTextDiff"/>
|
||||
<Binding Source="{x:Static vm:Preferences.Instance}" Path="UseBlockNavigationInDiffView" Mode="OneWay"/>
|
||||
<Binding Path="UseBlockNavigation"/>
|
||||
</MultiBinding>
|
||||
</Border.IsVisible>
|
||||
|
||||
<TextBlock x:Name="BlockNavigationIndicator" Classes="primary" Margin="0,0,0,0" FontSize="11" Text="-/-"/>
|
||||
<ContentControl Content="{Binding Content, Mode=OneWay}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:TextDiffContext">
|
||||
<ContentControl Content="{Binding BlockNavigation}"/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:BlockNavigation">
|
||||
<TextBlock Classes="primary" Margin="0,0,0,0" FontSize="11" Text="{Binding Indicator}"/>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Border>
|
||||
|
||||
<Button Classes="icon_button"
|
||||
@@ -109,7 +118,7 @@
|
||||
<Button.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="IsTextDiff"/>
|
||||
<Binding Source="{x:Static vm:Preferences.Instance}" Path="UseBlockNavigationInDiffView" Mode="OneWay"/>
|
||||
<Binding Path="UseBlockNavigation"/>
|
||||
</MultiBinding>
|
||||
</Button.IsVisible>
|
||||
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Bottom}"/>
|
||||
@@ -117,7 +126,7 @@
|
||||
|
||||
<ToggleButton Classes="line_path"
|
||||
Width="28"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseBlockNavigationInDiffView, Mode=TwoWay}"
|
||||
IsChecked="{Binding UseBlockNavigation, Mode=TwoWay}"
|
||||
IsVisible="{Binding IsTextDiff}"
|
||||
ToolTip.Tip="{DynamicResource Text.Diff.UseBlockNavigation}">
|
||||
<Path Width="13" Height="13" Data="{StaticResource Icons.CodeBlock}" Margin="0,3,0,0"/>
|
||||
@@ -127,10 +136,8 @@
|
||||
Width="28"
|
||||
Command="{Binding IncrUnified}"
|
||||
IsVisible="{Binding IsTextDiff}"
|
||||
IsEnabled="{Binding ShowEntireFile, Mode=OneWay, Converter={x:Static BoolConverters.Not}}"
|
||||
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Incr}">
|
||||
<Button.IsEnabled>
|
||||
<Binding Source="{x:Static vm:Preferences.Instance}" Path="UseFullTextDiff" Mode="OneWay" Converter="{x:Static BoolConverters.Not}"/>
|
||||
</Button.IsEnabled>
|
||||
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Lines.Incr}"/>
|
||||
</Button>
|
||||
|
||||
@@ -142,7 +149,7 @@
|
||||
<Button.IsEnabled>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="UnifiedLines" Mode="OneWay" Converter="{x:Static c:IntConverters.IsGreaterThanFour}"/>
|
||||
<Binding Source="{x:Static vm:Preferences.Instance}" Path="UseFullTextDiff" Mode="OneWay" Converter="{x:Static BoolConverters.Not}"/>
|
||||
<Binding Path="ShowEntireFile" Mode="OneWay" Converter="{x:Static BoolConverters.Not}"/>
|
||||
</MultiBinding>
|
||||
</Button.IsEnabled>
|
||||
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Lines.Decr}"/>
|
||||
@@ -150,8 +157,7 @@
|
||||
|
||||
<ToggleButton Classes="line_path"
|
||||
Width="28"
|
||||
Click="OnUseFullTextDiffClicked"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseFullTextDiff, Mode=OneWay}"
|
||||
IsChecked="{Binding ShowEntireFile, Mode=TwoWay}"
|
||||
IsVisible="{Binding IsTextDiff}"
|
||||
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.All}">
|
||||
<Path Width="13" Height="13" Data="{StaticResource Icons.Lines.All}" Margin="0,3,0,0"/>
|
||||
@@ -172,8 +178,8 @@
|
||||
ToolTip.Tip="{DynamicResource Text.Diff.ToggleWordWrap}">
|
||||
<ToggleButton.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="IsTextDiff"/>
|
||||
<Binding Source="{x:Static vm:Preferences.Instance}" Path="UseSideBySideDiff" Mode="OneWay" Converter="{x:Static BoolConverters.Not}"/>
|
||||
<Binding Path="IsTextDiff" Mode="OneWay"/>
|
||||
<Binding Path="UseSideBySide" Mode="OneWay" Converter="{x:Static BoolConverters.Not}"/>
|
||||
</MultiBinding>
|
||||
</ToggleButton.IsVisible>
|
||||
|
||||
@@ -197,7 +203,7 @@
|
||||
|
||||
<ToggleButton Classes="line_path"
|
||||
Width="28" Height="18"
|
||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSideBySideDiff, Mode=TwoWay}"
|
||||
IsChecked="{Binding UseSideBySide, Mode=TwoWay}"
|
||||
IsVisible="{Binding IsTextDiff}"
|
||||
ToolTip.Tip="{DynamicResource Text.Diff.SideBySide}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.Layout}" Margin="0,2,0,0"/>
|
||||
@@ -361,10 +367,8 @@
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Text Diff -->
|
||||
<DataTemplate DataType="m:TextDiff">
|
||||
<v:TextDiffView UseSideBySideDiff="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"
|
||||
UseBlockNavigation="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseBlockNavigationInDiffView, Mode=OneWay}"
|
||||
BlockNavigationChanged="OnBlockNavigationChanged"/>
|
||||
<DataTemplate DataType="vm:TextDiffContext">
|
||||
<v:TextDiffView SelectedChunk="{Binding SelectedChunk, Mode=OneWay}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Empty or only EOL changes -->
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
@@ -12,50 +11,35 @@ namespace SourceGit.Views
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
|
||||
if (DataContext is ViewModels.DiffContext vm)
|
||||
vm.CheckSettings();
|
||||
}
|
||||
|
||||
private void OnGotoFirstChange(object _, RoutedEventArgs e)
|
||||
{
|
||||
this.FindDescendantOfType<TextDiffView>()?.GotoFirstChange();
|
||||
this.FindDescendantOfType<ThemedTextDiffPresenter>()?.GotoFirstChange();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGotoPrevChange(object _, RoutedEventArgs e)
|
||||
{
|
||||
this.FindDescendantOfType<TextDiffView>()?.GotoPrevChange();
|
||||
this.FindDescendantOfType<ThemedTextDiffPresenter>()?.GotoPrevChange();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGotoNextChange(object _, RoutedEventArgs e)
|
||||
{
|
||||
this.FindDescendantOfType<TextDiffView>()?.GotoNextChange();
|
||||
this.FindDescendantOfType<ThemedTextDiffPresenter>()?.GotoNextChange();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGotoLastChange(object _, RoutedEventArgs e)
|
||||
{
|
||||
this.FindDescendantOfType<TextDiffView>()?.GotoLastChange();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBlockNavigationChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TextDiffView textDiff)
|
||||
BlockNavigationIndicator.Text = textDiff.BlockNavigation?.Indicator ?? string.Empty;
|
||||
}
|
||||
|
||||
private void OnUseFullTextDiffClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var textDiffView = this.FindDescendantOfType<TextDiffView>();
|
||||
|
||||
var presenter = textDiffView?.FindDescendantOfType<ThemedTextDiffPresenter>();
|
||||
if (presenter == null)
|
||||
return;
|
||||
|
||||
if (presenter.DataContext is Models.TextDiff combined)
|
||||
combined.ScrollOffset = Vector.Zero;
|
||||
else if (presenter.DataContext is ViewModels.TwoSideTextDiff twoSides)
|
||||
twoSides.File = string.Empty; // Just to reset `SyncScrollOffset` without affect UI refresh.
|
||||
|
||||
(DataContext as ViewModels.DiffContext)?.ToggleFullTextDiff();
|
||||
this.FindDescendantOfType<ThemedTextDiffPresenter>()?.GotoLastChange();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,14 @@
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.TextDiffView"
|
||||
x:Name="ThisControl"
|
||||
x:DataType="vm:TextDiffContext"
|
||||
Background="{DynamicResource Brush.Contents}">
|
||||
<Grid>
|
||||
<ContentControl x:Name="Editor">
|
||||
<ContentControl Content="{Binding}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="m:TextDiff">
|
||||
<DataTemplate DataType="vm:CombinedTextDiff">
|
||||
<Grid ColumnDefinitions="*,1,8">
|
||||
<v:CombinedTextDiffPresenter Grid.Column="0"
|
||||
x:Name="CombinedPresenter"
|
||||
FileName="{Binding File}"
|
||||
Foreground="{DynamicResource Brush.FG1}"
|
||||
LineBrush="{DynamicResource Brush.Border2}"
|
||||
@@ -31,14 +30,14 @@
|
||||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}"
|
||||
WordWrap="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableDiffViewWordWrap}"
|
||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"
|
||||
BlockNavigation="{Binding #ThisControl.BlockNavigation, Mode=TwoWay}"/>
|
||||
EnableChunkSelection="{Binding EnableChunkOption}"
|
||||
SelectedChunk="{Binding SelectedChunk, Mode=TwoWay}"
|
||||
BlockNavigation="{Binding BlockNavigation, Mode=OneWay}"/>
|
||||
|
||||
<Rectangle Grid.Column="1" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
|
||||
|
||||
<v:TextDiffViewMinimap Grid.Column="2"
|
||||
DisplayRange="{Binding #CombinedPresenter.DisplayRange}"
|
||||
DisplayRange="{Binding DisplayRange, Mode=OneWay}"
|
||||
AddedLineBrush="{DynamicResource Brush.Diff.AddedBG}"
|
||||
DeletedLineBrush="{DynamicResource Brush.Diff.DeletedBG}"/>
|
||||
</Grid>
|
||||
@@ -47,7 +46,6 @@
|
||||
<DataTemplate DataType="vm:TwoSideTextDiff">
|
||||
<Grid ColumnDefinitions="*,1,*,1,12">
|
||||
<v:SingleSideTextDiffPresenter Grid.Column="0"
|
||||
x:Name="LeftSidePresenter"
|
||||
IsOld="True"
|
||||
FileName="{Binding File}"
|
||||
Foreground="{DynamicResource Brush.FG1}"
|
||||
@@ -64,9 +62,9 @@
|
||||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}"
|
||||
WordWrap="False"
|
||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"
|
||||
BlockNavigation="{Binding #ThisControl.BlockNavigation, Mode=TwoWay}"/>
|
||||
EnableChunkSelection="{Binding EnableChunkOption, Mode=OneWay}"
|
||||
SelectedChunk="{Binding SelectedChunk, Mode=TwoWay}"
|
||||
BlockNavigation="{Binding BlockNavigation, Mode=OneWay}"/>
|
||||
|
||||
<Rectangle Grid.Column="1" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
|
||||
|
||||
@@ -87,14 +85,14 @@
|
||||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}"
|
||||
WordWrap="False"
|
||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"
|
||||
BlockNavigation="{Binding #ThisControl.BlockNavigation, Mode=TwoWay}"/>
|
||||
EnableChunkSelection="{Binding EnableChunkOption, Mode=OneWay}"
|
||||
SelectedChunk="{Binding SelectedChunk, Mode=TwoWay}"
|
||||
BlockNavigation="{Binding BlockNavigation, Mode=OneWay}"/>
|
||||
|
||||
<Rectangle Grid.Column="3" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
|
||||
|
||||
<v:TextDiffViewMinimap Grid.Column="4"
|
||||
DisplayRange="{Binding #LeftSidePresenter.DisplayRange}"
|
||||
DisplayRange="{Binding DisplayRange, Mode=OneWay}"
|
||||
AddedLineBrush="{DynamicResource Brush.Diff.AddedBG}"
|
||||
DeletedLineBrush="{DynamicResource Brush.Diff.DeletedBG}"/>
|
||||
</Grid>
|
||||
@@ -103,7 +101,7 @@
|
||||
</ContentControl>
|
||||
|
||||
<StackPanel x:Name="Popup" IsVisible="False" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right" Effect="drop-shadow(0 0 8 #80000000)">
|
||||
<Button Classes="flat" Click="OnStageChunk" HotKey="{OnPlatform Ctrl+S, macOS=⌘+S}" IsVisible="{Binding #ThisControl.IsUnstagedChange}">
|
||||
<Button Classes="flat" Click="OnStageChunk" HotKey="{OnPlatform Ctrl+S, macOS=⌘+S}" IsVisible="{Binding IsUnstaged}">
|
||||
<TextBlock>
|
||||
<Run Text="{DynamicResource Text.Hunk.Stage}"/>
|
||||
<Run Text=" "/>
|
||||
@@ -114,7 +112,7 @@
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<Button Classes="flat" Click="OnUnstageChunk" HotKey="{OnPlatform Ctrl+U, macOS=⌘+U}" IsVisible="{Binding #ThisControl.IsUnstagedChange, Converter={x:Static BoolConverters.Not}}">
|
||||
<Button Classes="flat" Click="OnUnstageChunk" HotKey="{OnPlatform Ctrl+U, macOS=⌘+U}" IsVisible="{Binding IsUnstaged, Converter={x:Static BoolConverters.Not}}">
|
||||
<TextBlock>
|
||||
<Run Text="{DynamicResource Text.Hunk.Unstage}"/>
|
||||
<Run Text=" "/>
|
||||
@@ -125,7 +123,7 @@
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<Button Classes="flat" Margin="8,0,0,0" HotKey="{OnPlatform Ctrl+D, macOS=⌘+D}" Click="OnDiscardChunk" IsVisible="{Binding #ThisControl.IsUnstagedChange}">
|
||||
<Button Classes="flat" Margin="8,0,0,0" HotKey="{OnPlatform Ctrl+D, macOS=⌘+D}" Click="OnDiscardChunk" IsVisible="{Binding IsUnstaged}">
|
||||
<TextBlock>
|
||||
<Run Text="{DynamicResource Text.Hunk.Discard}"/>
|
||||
<Run Text=" "/>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
@@ -23,41 +24,6 @@ using AvaloniaEdit.Utils;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public class TextDiffViewChunk
|
||||
{
|
||||
public double Y { get; set; } = 0.0;
|
||||
public double Height { get; set; } = 0.0;
|
||||
public int StartIdx { get; set; } = 0;
|
||||
public int EndIdx { get; set; } = 0;
|
||||
public bool Combined { get; set; } = true;
|
||||
public bool IsOldSide { get; set; } = false;
|
||||
|
||||
public bool ShouldReplace(TextDiffViewChunk old)
|
||||
{
|
||||
if (old == null)
|
||||
return true;
|
||||
|
||||
return Math.Abs(Y - old.Y) > 0.001 ||
|
||||
Math.Abs(Height - old.Height) > 0.001 ||
|
||||
StartIdx != old.StartIdx ||
|
||||
EndIdx != old.EndIdx ||
|
||||
Combined != old.Combined ||
|
||||
IsOldSide != old.IsOldSide;
|
||||
}
|
||||
}
|
||||
|
||||
public record TextDiffViewRange
|
||||
{
|
||||
public int StartIdx { get; set; } = 0;
|
||||
public int EndIdx { get; set; } = 0;
|
||||
|
||||
public TextDiffViewRange(int startIdx, int endIdx)
|
||||
{
|
||||
StartIdx = startIdx;
|
||||
EndIdx = endIdx;
|
||||
}
|
||||
}
|
||||
|
||||
public class ThemedTextDiffPresenter : TextEditor
|
||||
{
|
||||
public class VerticalSeparatorMargin : AbstractMargin
|
||||
@@ -134,13 +100,12 @@ namespace SourceGit.Views
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var presenter = this.FindAncestorOfType<ThemedTextDiffPresenter>();
|
||||
if (presenter == null)
|
||||
if (presenter is not { DataContext: ViewModels.TextDiffContext ctx })
|
||||
return new Size(32, 0);
|
||||
|
||||
var maxLineNumber = presenter.GetMaxLineNumber();
|
||||
var typeface = TextView.CreateTypeface();
|
||||
var test = new FormattedText(
|
||||
$"{maxLineNumber}",
|
||||
$"{ctx.Data.MaxLineNumber}",
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
@@ -482,24 +447,15 @@ namespace SourceGit.Views
|
||||
set => SetValue(EnableChunkSelectionProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<TextDiffViewChunk> SelectedChunkProperty =
|
||||
AvaloniaProperty.Register<ThemedTextDiffPresenter, TextDiffViewChunk>(nameof(SelectedChunk));
|
||||
public static readonly StyledProperty<ViewModels.TextDiffSelectedChunk> SelectedChunkProperty =
|
||||
AvaloniaProperty.Register<ThemedTextDiffPresenter, ViewModels.TextDiffSelectedChunk>(nameof(SelectedChunk));
|
||||
|
||||
public TextDiffViewChunk SelectedChunk
|
||||
public ViewModels.TextDiffSelectedChunk SelectedChunk
|
||||
{
|
||||
get => GetValue(SelectedChunkProperty);
|
||||
set => SetValue(SelectedChunkProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<TextDiffViewRange> DisplayRangeProperty =
|
||||
AvaloniaProperty.Register<ThemedTextDiffPresenter, TextDiffViewRange>(nameof(DisplayRange), new TextDiffViewRange(0, 0));
|
||||
|
||||
public TextDiffViewRange DisplayRange
|
||||
{
|
||||
get => GetValue(DisplayRangeProperty);
|
||||
set => SetValue(DisplayRangeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<ViewModels.BlockNavigation> BlockNavigationProperty =
|
||||
AvaloniaProperty.Register<ThemedTextDiffPresenter, ViewModels.BlockNavigation>(nameof(BlockNavigation));
|
||||
|
||||
@@ -534,11 +490,6 @@ namespace SourceGit.Views
|
||||
return [];
|
||||
}
|
||||
|
||||
public virtual int GetMaxLineNumber()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public virtual void UpdateSelectedChunk(double y)
|
||||
{
|
||||
}
|
||||
@@ -569,7 +520,10 @@ namespace SourceGit.Views
|
||||
return;
|
||||
}
|
||||
|
||||
var firstLineIdx = DisplayRange.StartIdx;
|
||||
if (DataContext is not ViewModels.TextDiffContext { DisplayRange: { } range })
|
||||
return;
|
||||
|
||||
var firstLineIdx = range.Start;
|
||||
if (firstLineIdx <= 1)
|
||||
return;
|
||||
|
||||
@@ -625,8 +579,11 @@ namespace SourceGit.Views
|
||||
return;
|
||||
}
|
||||
|
||||
if (DataContext is not ViewModels.TextDiffContext { DisplayRange: { } range })
|
||||
return;
|
||||
|
||||
var lines = GetLines();
|
||||
var lastLineIdx = DisplayRange.EndIdx;
|
||||
var lastLineIdx = range.End;
|
||||
if (lastLineIdx >= lines.Count - 1)
|
||||
return;
|
||||
|
||||
@@ -746,12 +703,6 @@ namespace SourceGit.Views
|
||||
}
|
||||
else if (change.Property == BlockNavigationProperty)
|
||||
{
|
||||
if (change.OldValue is ViewModels.BlockNavigation oldValue)
|
||||
oldValue.PropertyChanged -= OnBlockNavigationPropertyChanged;
|
||||
|
||||
if (change.NewValue is ViewModels.BlockNavigation newValue)
|
||||
newValue.PropertyChanged += OnBlockNavigationPropertyChanged;
|
||||
|
||||
TextArea?.TextView?.Redraw();
|
||||
}
|
||||
}
|
||||
@@ -771,12 +722,6 @@ namespace SourceGit.Views
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
private void OnBlockNavigationPropertyChanged(object _1, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "Current")
|
||||
TextArea?.TextView?.Redraw();
|
||||
}
|
||||
|
||||
private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
var selection = TextArea.Selection;
|
||||
@@ -850,9 +795,15 @@ namespace SourceGit.Views
|
||||
|
||||
private void OnTextViewVisualLinesChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (DataContext is not ViewModels.TextDiffContext ctx)
|
||||
return;
|
||||
|
||||
if (ctx.IsSideBySide() && !IsOld)
|
||||
return;
|
||||
|
||||
if (!TextArea.TextView.VisualLinesValid)
|
||||
{
|
||||
SetCurrentValue(DisplayRangeProperty, new TextDiffViewRange(0, 0));
|
||||
ctx.DisplayRange = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -873,89 +824,16 @@ namespace SourceGit.Views
|
||||
start = index;
|
||||
}
|
||||
|
||||
SetCurrentValue(DisplayRangeProperty, new TextDiffViewRange(start, start + count));
|
||||
ctx.DisplayRange = new ViewModels.TextDiffDisplayRange(start, start + count);
|
||||
BlockNavigation?.AutoUpdate(start + 1, start + count);
|
||||
}
|
||||
|
||||
protected void TrySetChunk(TextDiffViewChunk chunk)
|
||||
protected void TrySetChunk(ViewModels.TextDiffSelectedChunk chunk)
|
||||
{
|
||||
var old = SelectedChunk;
|
||||
if (chunk == null)
|
||||
{
|
||||
if (old != null)
|
||||
SetCurrentValue(SelectedChunkProperty, null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk.ShouldReplace(old))
|
||||
if (ViewModels.TextDiffSelectedChunk.IsChanged(SelectedChunk, chunk))
|
||||
SetCurrentValue(SelectedChunkProperty, chunk);
|
||||
}
|
||||
|
||||
protected (int, int) FindRangeByIndex(List<Models.TextDiffLine> lines, int lineIdx)
|
||||
{
|
||||
var startIdx = -1;
|
||||
var endIdx = -1;
|
||||
|
||||
var normalLineCount = 0;
|
||||
var modifiedLineCount = 0;
|
||||
|
||||
for (int i = lineIdx; i >= 0; i--)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (line.Type == Models.TextDiffLineType.Indicator)
|
||||
{
|
||||
startIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.Type == Models.TextDiffLineType.Normal)
|
||||
{
|
||||
normalLineCount++;
|
||||
if (normalLineCount >= 2)
|
||||
{
|
||||
startIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
normalLineCount = 0;
|
||||
modifiedLineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
normalLineCount = lines[lineIdx].Type == Models.TextDiffLineType.Normal ? 1 : 0;
|
||||
for (int i = lineIdx + 1; i < lines.Count; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (line.Type == Models.TextDiffLineType.Indicator)
|
||||
{
|
||||
endIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.Type == Models.TextDiffLineType.Normal)
|
||||
{
|
||||
normalLineCount++;
|
||||
if (normalLineCount >= 2)
|
||||
{
|
||||
endIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
normalLineCount = 0;
|
||||
modifiedLineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIdx == -1)
|
||||
endIdx = lines.Count - 1;
|
||||
|
||||
return modifiedLineCount > 0 ? (startIdx, endIdx) : (-1, -1);
|
||||
}
|
||||
|
||||
private void UpdateTextMate()
|
||||
{
|
||||
if (UseSyntaxHighlighting)
|
||||
@@ -1072,21 +950,14 @@ namespace SourceGit.Views
|
||||
|
||||
public override List<Models.TextDiffLine> GetLines()
|
||||
{
|
||||
if (DataContext is Models.TextDiff diff)
|
||||
if (DataContext is ViewModels.CombinedTextDiff { Data: { } diff })
|
||||
return diff.Lines;
|
||||
return [];
|
||||
}
|
||||
|
||||
public override int GetMaxLineNumber()
|
||||
{
|
||||
if (DataContext is Models.TextDiff diff)
|
||||
return diff.MaxLineNumber;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override void UpdateSelectedChunk(double y)
|
||||
{
|
||||
if (DataContext is not Models.TextDiff diff)
|
||||
if (DataContext is not ViewModels.CombinedTextDiff { Data: { } diff } combined)
|
||||
return;
|
||||
|
||||
var view = TextArea.TextView;
|
||||
@@ -1134,15 +1005,7 @@ namespace SourceGit.Views
|
||||
endLine.GetTextLineVisualYPosition(endLine.TextLines[^1], VisualYPosition.TextBottom) - view.VerticalOffset :
|
||||
view.Bounds.Height;
|
||||
|
||||
TrySetChunk(new TextDiffViewChunk()
|
||||
{
|
||||
Y = rectStartY,
|
||||
Height = rectEndY - rectStartY,
|
||||
StartIdx = startIdx,
|
||||
EndIdx = endIdx,
|
||||
Combined = true,
|
||||
IsOldSide = false,
|
||||
});
|
||||
TrySetChunk(new(rectStartY, rectEndY - rectStartY, startIdx, endIdx, true, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1170,7 +1033,7 @@ namespace SourceGit.Views
|
||||
return;
|
||||
}
|
||||
|
||||
var (startIdx, endIdx) = FindRangeByIndex(diff.Lines, lineIdx);
|
||||
var (startIdx, endIdx) = combined.FindRangeByIndex(diff.Lines, lineIdx);
|
||||
if (startIdx == -1)
|
||||
{
|
||||
TrySetChunk(null);
|
||||
@@ -1187,15 +1050,7 @@ namespace SourceGit.Views
|
||||
endLine.GetTextLineVisualYPosition(endLine.TextLines[^1], VisualYPosition.TextBottom) - view.VerticalOffset :
|
||||
view.Bounds.Height;
|
||||
|
||||
TrySetChunk(new TextDiffViewChunk()
|
||||
{
|
||||
Y = rectStartY,
|
||||
Height = rectEndY - rectStartY,
|
||||
StartIdx = startIdx,
|
||||
EndIdx = endIdx,
|
||||
Combined = true,
|
||||
IsOldSide = false,
|
||||
});
|
||||
TrySetChunk(new(rectStartY, rectEndY - rectStartY, startIdx, endIdx, true, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1223,10 +1078,10 @@ namespace SourceGit.Views
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
|
||||
if (DataContext is Models.TextDiff textDiff)
|
||||
if (DataContext is ViewModels.CombinedTextDiff { Data: { } diff })
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var line in textDiff.Lines)
|
||||
foreach (var line in diff.Lines)
|
||||
{
|
||||
if (line.Content.Length > 10000)
|
||||
{
|
||||
@@ -1279,35 +1134,28 @@ namespace SourceGit.Views
|
||||
return [];
|
||||
}
|
||||
|
||||
public override int GetMaxLineNumber()
|
||||
{
|
||||
if (DataContext is ViewModels.TwoSideTextDiff diff)
|
||||
return diff.MaxLineNumber;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override void GotoFirstChange()
|
||||
{
|
||||
base.GotoFirstChange();
|
||||
DirectSyncScrollOffset();
|
||||
SyncScrollOffset();
|
||||
}
|
||||
|
||||
public override void GotoPrevChange()
|
||||
{
|
||||
base.GotoPrevChange();
|
||||
DirectSyncScrollOffset();
|
||||
SyncScrollOffset();
|
||||
}
|
||||
|
||||
public override void GotoNextChange()
|
||||
{
|
||||
base.GotoNextChange();
|
||||
DirectSyncScrollOffset();
|
||||
SyncScrollOffset();
|
||||
}
|
||||
|
||||
public override void GotoLastChange()
|
||||
{
|
||||
base.GotoLastChange();
|
||||
DirectSyncScrollOffset();
|
||||
SyncScrollOffset();
|
||||
}
|
||||
|
||||
public override void UpdateSelectedChunk(double y)
|
||||
@@ -1315,10 +1163,6 @@ namespace SourceGit.Views
|
||||
if (DataContext is not ViewModels.TwoSideTextDiff diff)
|
||||
return;
|
||||
|
||||
var parent = this.FindAncestorOfType<TextDiffView>();
|
||||
if (parent == null)
|
||||
return;
|
||||
|
||||
var view = TextArea.TextView;
|
||||
var lines = IsOld ? diff.Old : diff.New;
|
||||
var selection = TextArea.Selection;
|
||||
@@ -1365,22 +1209,10 @@ namespace SourceGit.Views
|
||||
endLine.GetTextLineVisualYPosition(endLine.TextLines[^1], VisualYPosition.TextBottom) - view.VerticalOffset :
|
||||
view.Bounds.Height;
|
||||
|
||||
diff.ConvertsToCombinedRange(parent.DataContext as Models.TextDiff, ref startIdx, ref endIdx, IsOld);
|
||||
|
||||
TrySetChunk(new TextDiffViewChunk()
|
||||
{
|
||||
Y = rectStartY,
|
||||
Height = rectEndY - rectStartY,
|
||||
StartIdx = startIdx,
|
||||
EndIdx = endIdx,
|
||||
Combined = false,
|
||||
IsOldSide = IsOld,
|
||||
});
|
||||
|
||||
return;
|
||||
diff.ConvertsToCombinedRange(ref startIdx, ref endIdx, IsOld);
|
||||
TrySetChunk(new (rectStartY, rectEndY - rectStartY, startIdx, endIdx, false, IsOld));
|
||||
}
|
||||
|
||||
if (this.FindAncestorOfType<TextDiffView>()?.DataContext is Models.TextDiff textDiff)
|
||||
else
|
||||
{
|
||||
var lineIdx = -1;
|
||||
foreach (var line in view.VisualLines)
|
||||
@@ -1406,7 +1238,7 @@ namespace SourceGit.Views
|
||||
return;
|
||||
}
|
||||
|
||||
var (startIdx, endIdx) = FindRangeByIndex(lines, lineIdx);
|
||||
var (startIdx, endIdx) = diff.FindRangeByIndex(lines, lineIdx);
|
||||
if (startIdx == -1)
|
||||
{
|
||||
TrySetChunk(null);
|
||||
@@ -1423,15 +1255,21 @@ namespace SourceGit.Views
|
||||
endLine.GetTextLineVisualYPosition(endLine.TextLines[^1], VisualYPosition.TextBottom) - view.VerticalOffset :
|
||||
view.Bounds.Height;
|
||||
|
||||
TrySetChunk(new TextDiffViewChunk()
|
||||
{
|
||||
Y = rectStartY,
|
||||
Height = rectEndY - rectStartY,
|
||||
StartIdx = textDiff.Lines.IndexOf(lines[startIdx]),
|
||||
EndIdx = endIdx == lines.Count - 1 ? textDiff.Lines.Count - 1 : textDiff.Lines.IndexOf(lines[endIdx]),
|
||||
Combined = true,
|
||||
IsOldSide = false,
|
||||
});
|
||||
diff.ConvertsToCombinedRange(ref startIdx, ref endIdx, IsOld);
|
||||
TrySetChunk(new (rectStartY, rectEndY - rectStartY, startIdx, endIdx, true, false));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
if (change.Property == BlockNavigationProperty)
|
||||
{
|
||||
if (change.OldValue is ViewModels.BlockNavigation oldValue)
|
||||
oldValue.PropertyChanged -= OnBlockNavigationPropertyChanged;
|
||||
if (change.NewValue is ViewModels.BlockNavigation newValue)
|
||||
newValue.PropertyChanged += OnBlockNavigationPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1443,7 +1281,7 @@ namespace SourceGit.Views
|
||||
if (_scrollViewer != null)
|
||||
{
|
||||
_scrollViewer.ScrollChanged += OnTextViewScrollChanged;
|
||||
_scrollViewer.Bind(ScrollViewer.OffsetProperty, new Binding("SyncScrollOffset", BindingMode.OneWay));
|
||||
_scrollViewer.Bind(ScrollViewer.OffsetProperty, new Binding("ScrollOffset", BindingMode.OneWay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1498,22 +1336,28 @@ namespace SourceGit.Views
|
||||
if (_scrollViewer == null || DataContext is not ViewModels.TwoSideTextDiff diff)
|
||||
return;
|
||||
|
||||
if (diff.SyncScrollOffset.NearlyEquals(_scrollViewer.Offset))
|
||||
if (diff.ScrollOffset.NearlyEquals(_scrollViewer.Offset))
|
||||
return;
|
||||
|
||||
if (IsPointerOver || !e.OffsetDelta.NearlyEquals(Vector.Zero))
|
||||
{
|
||||
diff.SyncScrollOffset = _scrollViewer.Offset;
|
||||
diff.ScrollOffset = _scrollViewer.Offset;
|
||||
|
||||
if (!TextArea.TextView.IsPointerOver)
|
||||
TrySetChunk(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void DirectSyncScrollOffset()
|
||||
private void OnBlockNavigationPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName.Equals("Current", StringComparison.Ordinal))
|
||||
TextArea?.TextView?.Redraw();
|
||||
}
|
||||
|
||||
private void SyncScrollOffset()
|
||||
{
|
||||
if (_scrollViewer is not null && DataContext is ViewModels.TwoSideTextDiff diff)
|
||||
diff.SyncScrollOffset = _scrollViewer.Offset;
|
||||
diff.ScrollOffset = _scrollViewer.Offset;
|
||||
}
|
||||
|
||||
private ScrollViewer _scrollViewer = null;
|
||||
@@ -1539,10 +1383,10 @@ namespace SourceGit.Views
|
||||
set => SetValue(DeletedLineBrushProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<TextDiffViewRange> DisplayRangeProperty =
|
||||
AvaloniaProperty.Register<TextDiffViewMinimap, TextDiffViewRange>(nameof(DisplayRange), new TextDiffViewRange(0, 0));
|
||||
public static readonly StyledProperty<ViewModels.TextDiffDisplayRange> DisplayRangeProperty =
|
||||
AvaloniaProperty.Register<TextDiffViewMinimap, ViewModels.TextDiffDisplayRange>(nameof(DisplayRange));
|
||||
|
||||
public TextDiffViewRange DisplayRange
|
||||
public ViewModels.TextDiffDisplayRange DisplayRange
|
||||
{
|
||||
get => GetValue(DisplayRangeProperty);
|
||||
set => SetValue(DisplayRangeProperty, value);
|
||||
@@ -1576,18 +1420,19 @@ namespace SourceGit.Views
|
||||
RenderSingleSide(context, twoSideDiff.Old, 0, halfWidth);
|
||||
RenderSingleSide(context, twoSideDiff.New, halfWidth, halfWidth);
|
||||
}
|
||||
else if (DataContext is Models.TextDiff diff)
|
||||
else if (DataContext is ViewModels.CombinedTextDiff combined)
|
||||
{
|
||||
total = diff.Lines.Count;
|
||||
RenderSingleSide(context, diff.Lines, 0, Bounds.Width);
|
||||
var data = combined.Data;
|
||||
total = data.Lines.Count;
|
||||
RenderSingleSide(context, data.Lines, 0, Bounds.Width);
|
||||
}
|
||||
|
||||
var range = DisplayRange;
|
||||
if (range.EndIdx == 0)
|
||||
if (range == null || range.End == 0)
|
||||
return;
|
||||
|
||||
var startY = range.StartIdx / (total * 1.0) * Bounds.Height;
|
||||
var endY = range.EndIdx / (total * 1.0) * Bounds.Height;
|
||||
var startY = range.Start / (total * 1.0) * Bounds.Height;
|
||||
var endY = range.End / (total * 1.0) * Bounds.Height;
|
||||
var color = DisplayRangeColor;
|
||||
var brush = new SolidColorBrush(color, 0.2);
|
||||
var pen = new Pen(color.ToUInt32());
|
||||
@@ -1639,189 +1484,53 @@ namespace SourceGit.Views
|
||||
|
||||
public partial class TextDiffView : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<bool> UseSideBySideDiffProperty =
|
||||
AvaloniaProperty.Register<TextDiffView, bool>(nameof(UseSideBySideDiff));
|
||||
public static readonly StyledProperty<ViewModels.TextDiffSelectedChunk> SelectedChunkProperty =
|
||||
AvaloniaProperty.Register<TextDiffView, ViewModels.TextDiffSelectedChunk>(nameof(SelectedChunk));
|
||||
|
||||
public bool UseSideBySideDiff
|
||||
{
|
||||
get => GetValue(UseSideBySideDiffProperty);
|
||||
set => SetValue(UseSideBySideDiffProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<TextDiffViewChunk> SelectedChunkProperty =
|
||||
AvaloniaProperty.Register<TextDiffView, TextDiffViewChunk>(nameof(SelectedChunk));
|
||||
|
||||
public TextDiffViewChunk SelectedChunk
|
||||
public ViewModels.TextDiffSelectedChunk SelectedChunk
|
||||
{
|
||||
get => GetValue(SelectedChunkProperty);
|
||||
set => SetValue(SelectedChunkProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsUnstagedChangeProperty =
|
||||
AvaloniaProperty.Register<TextDiffView, bool>(nameof(IsUnstagedChange));
|
||||
|
||||
public bool IsUnstagedChange
|
||||
{
|
||||
get => GetValue(IsUnstagedChangeProperty);
|
||||
set => SetValue(IsUnstagedChangeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> EnableChunkSelectionProperty =
|
||||
AvaloniaProperty.Register<TextDiffView, bool>(nameof(EnableChunkSelection));
|
||||
|
||||
public bool EnableChunkSelection
|
||||
{
|
||||
get => GetValue(EnableChunkSelectionProperty);
|
||||
set => SetValue(EnableChunkSelectionProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> UseBlockNavigationProperty =
|
||||
AvaloniaProperty.Register<TextDiffView, bool>(nameof(UseBlockNavigation));
|
||||
|
||||
public bool UseBlockNavigation
|
||||
{
|
||||
get => GetValue(UseBlockNavigationProperty);
|
||||
set => SetValue(UseBlockNavigationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<ViewModels.BlockNavigation> BlockNavigationProperty =
|
||||
AvaloniaProperty.Register<TextDiffView, ViewModels.BlockNavigation>(nameof(BlockNavigation));
|
||||
|
||||
public ViewModels.BlockNavigation BlockNavigation
|
||||
{
|
||||
get => GetValue(BlockNavigationProperty);
|
||||
set => SetValue(BlockNavigationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent<RoutedEventArgs> BlockNavigationChangedEvent =
|
||||
RoutedEvent.Register<TextDiffView, RoutedEventArgs>(nameof(BlockNavigationChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<RoutedEventArgs> BlockNavigationChanged
|
||||
{
|
||||
add { AddHandler(BlockNavigationChangedEvent, value); }
|
||||
remove { RemoveHandler(BlockNavigationChangedEvent, value); }
|
||||
}
|
||||
|
||||
static TextDiffView()
|
||||
{
|
||||
UseSideBySideDiffProperty.Changed.AddClassHandler<TextDiffView>((v, _) =>
|
||||
{
|
||||
v.RefreshContent(v.DataContext as Models.TextDiff, false);
|
||||
});
|
||||
|
||||
UseBlockNavigationProperty.Changed.AddClassHandler<TextDiffView>((v, _) =>
|
||||
{
|
||||
v.RefreshBlockNavigation();
|
||||
});
|
||||
|
||||
SelectedChunkProperty.Changed.AddClassHandler<TextDiffView>((v, _) =>
|
||||
{
|
||||
var chunk = v.SelectedChunk;
|
||||
if (chunk == null)
|
||||
{
|
||||
v.Popup.IsVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var top = chunk.Y + (chunk.Height >= 36 ? 8 : 2);
|
||||
var right = (chunk.Combined || !chunk.IsOldSide) ? 26 : (v.Bounds.Width * 0.5f) + 26;
|
||||
v.Popup.Margin = new Thickness(0, top, right, 0);
|
||||
v.Popup.IsVisible = true;
|
||||
});
|
||||
}
|
||||
|
||||
public TextDiffView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void GotoFirstChange()
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
this.FindDescendantOfType<ThemedTextDiffPresenter>()?.GotoFirstChange();
|
||||
TryRaiseBlockNavigationChanged();
|
||||
}
|
||||
base.OnPropertyChanged(change);
|
||||
|
||||
public void GotoPrevChange()
|
||||
{
|
||||
this.FindDescendantOfType<ThemedTextDiffPresenter>()?.GotoPrevChange();
|
||||
TryRaiseBlockNavigationChanged();
|
||||
}
|
||||
|
||||
public void GotoNextChange()
|
||||
{
|
||||
this.FindDescendantOfType<ThemedTextDiffPresenter>()?.GotoNextChange();
|
||||
TryRaiseBlockNavigationChanged();
|
||||
}
|
||||
|
||||
public void GotoLastChange()
|
||||
{
|
||||
this.FindDescendantOfType<ThemedTextDiffPresenter>()?.GotoLastChange();
|
||||
TryRaiseBlockNavigationChanged();
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
RefreshContent(DataContext as Models.TextDiff);
|
||||
if (change.Property == SelectedChunkProperty)
|
||||
{
|
||||
if (SelectedChunk is { } chunk)
|
||||
{
|
||||
var top = chunk.Y + (chunk.Height >= 36 ? 8 : 2);
|
||||
var right = (chunk.Combined || !chunk.IsOldSide) ? 26 : (Bounds.Width * 0.5f) + 26;
|
||||
Popup.Margin = new Thickness(0, top, right, 0);
|
||||
Popup.IsVisible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Popup.IsVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerExited(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerExited(e);
|
||||
|
||||
if (SelectedChunk != null)
|
||||
SetCurrentValue(SelectedChunkProperty, null);
|
||||
}
|
||||
|
||||
private void RefreshContent(Models.TextDiff diff, bool keepScrollOffset = true)
|
||||
{
|
||||
if (SelectedChunk != null)
|
||||
SetCurrentValue(SelectedChunkProperty, null);
|
||||
|
||||
if (diff == null)
|
||||
{
|
||||
Editor.Content = null;
|
||||
GC.Collect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (UseSideBySideDiff)
|
||||
{
|
||||
var previousContent = Editor.Content as ViewModels.TwoSideTextDiff;
|
||||
Editor.Content = new ViewModels.TwoSideTextDiff(diff, keepScrollOffset ? previousContent : null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!keepScrollOffset)
|
||||
diff.ScrollOffset = Vector.Zero;
|
||||
Editor.Content = diff;
|
||||
}
|
||||
|
||||
RefreshBlockNavigation();
|
||||
|
||||
IsUnstagedChange = diff.Option.IsUnstaged;
|
||||
EnableChunkSelection = diff.Option.WorkingCopyChange != null;
|
||||
}
|
||||
|
||||
private void RefreshBlockNavigation()
|
||||
{
|
||||
if (UseBlockNavigation)
|
||||
BlockNavigation = new ViewModels.BlockNavigation(Editor.Content);
|
||||
else
|
||||
BlockNavigation = null;
|
||||
|
||||
TryRaiseBlockNavigationChanged();
|
||||
if (DataContext is ViewModels.TextDiffContext ctx)
|
||||
ctx.SelectedChunk = null;
|
||||
}
|
||||
|
||||
private async void OnStageChunk(object _1, RoutedEventArgs _2)
|
||||
{
|
||||
var chunk = SelectedChunk;
|
||||
if (chunk == null)
|
||||
if (DataContext is not ViewModels.TextDiffContext { SelectedChunk: { } chunk, Data: { } diff })
|
||||
return;
|
||||
|
||||
var diff = DataContext as Models.TextDiff;
|
||||
|
||||
var change = diff?.Option.WorkingCopyChange;
|
||||
var change = diff.Option.WorkingCopyChange;
|
||||
if (change == null)
|
||||
return;
|
||||
|
||||
@@ -1830,7 +1539,6 @@ namespace SourceGit.Views
|
||||
return;
|
||||
|
||||
var repoView = this.FindAncestorOfType<Repository>();
|
||||
|
||||
if (repoView?.DataContext is not ViewModels.Repository repo)
|
||||
return;
|
||||
|
||||
@@ -1849,16 +1557,16 @@ namespace SourceGit.Views
|
||||
}
|
||||
else if (chunk.Combined)
|
||||
{
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync();
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync();
|
||||
diff.GeneratePatchFromSelection(change, treeGuid, selection, false, tmpFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync();
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync();
|
||||
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, false, chunk.IsOldSide, tmpFile);
|
||||
}
|
||||
|
||||
await new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index").ExecAsync();
|
||||
await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--cache --index").ExecAsync();
|
||||
File.Delete(tmpFile);
|
||||
}
|
||||
|
||||
@@ -1868,13 +1576,10 @@ namespace SourceGit.Views
|
||||
|
||||
private async void OnUnstageChunk(object _1, RoutedEventArgs _2)
|
||||
{
|
||||
var chunk = SelectedChunk;
|
||||
if (chunk == null)
|
||||
if (DataContext is not ViewModels.TextDiffContext { SelectedChunk: { } chunk, Data: { } diff })
|
||||
return;
|
||||
|
||||
var diff = DataContext as Models.TextDiff;
|
||||
|
||||
var change = diff?.Option.WorkingCopyChange;
|
||||
var change = diff.Option.WorkingCopyChange;
|
||||
if (change == null)
|
||||
return;
|
||||
|
||||
@@ -1883,7 +1588,6 @@ namespace SourceGit.Views
|
||||
return;
|
||||
|
||||
var repoView = this.FindAncestorOfType<Repository>();
|
||||
|
||||
if (repoView?.DataContext is not ViewModels.Repository repo)
|
||||
return;
|
||||
|
||||
@@ -1898,7 +1602,7 @@ namespace SourceGit.Views
|
||||
}
|
||||
else
|
||||
{
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync();
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync();
|
||||
var tmpFile = Path.GetTempFileName();
|
||||
if (change.Index == Models.ChangeState.Added)
|
||||
diff.GenerateNewPatchFromSelection(change, treeGuid, selection, true, tmpFile);
|
||||
@@ -1907,7 +1611,7 @@ namespace SourceGit.Views
|
||||
else
|
||||
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile);
|
||||
|
||||
await new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index --reverse").ExecAsync();
|
||||
await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--cache --index --reverse").ExecAsync();
|
||||
File.Delete(tmpFile);
|
||||
}
|
||||
|
||||
@@ -1917,13 +1621,10 @@ namespace SourceGit.Views
|
||||
|
||||
private async void OnDiscardChunk(object _1, RoutedEventArgs _2)
|
||||
{
|
||||
var chunk = SelectedChunk;
|
||||
if (chunk == null)
|
||||
if (DataContext is not ViewModels.TextDiffContext { SelectedChunk: { } chunk, Data: { } diff })
|
||||
return;
|
||||
|
||||
var diff = DataContext as Models.TextDiff;
|
||||
|
||||
var change = diff?.Option.WorkingCopyChange;
|
||||
var change = diff.Option.WorkingCopyChange;
|
||||
if (change == null)
|
||||
return;
|
||||
|
||||
@@ -1951,27 +1652,21 @@ namespace SourceGit.Views
|
||||
}
|
||||
else if (chunk.Combined)
|
||||
{
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync();
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync();
|
||||
diff.GeneratePatchFromSelection(change, treeGuid, selection, true, tmpFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).GetResultAsync();
|
||||
var treeGuid = await new Commands.QueryStagedFileBlobGuid(repo.FullPath, change.Path).GetResultAsync();
|
||||
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile);
|
||||
}
|
||||
|
||||
await new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--reverse").ExecAsync();
|
||||
await new Commands.Apply(repo.FullPath, tmpFile, true, "nowarn", "--reverse").ExecAsync();
|
||||
File.Delete(tmpFile);
|
||||
}
|
||||
|
||||
repo.MarkWorkingCopyDirtyManually();
|
||||
repo.SetWatcherEnabled(true);
|
||||
}
|
||||
|
||||
private void TryRaiseBlockNavigationChanged()
|
||||
{
|
||||
if (UseBlockNavigation)
|
||||
RaiseEvent(new RoutedEventArgs(BlockNavigationChangedEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user