diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index da971e58..f1bef7b7 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -51,6 +51,9 @@ namespace SourceGit.Commands _result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine); } + if (_result.TextDiff != null) + _result.TextDiff.ProcessChangeBlocks(); + return _result; } diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index e0ae82e0..afe22ad4 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -2,6 +2,8 @@ using System.Text; using System.Text.RegularExpressions; +using CommunityToolkit.Mvvm.ComponentModel; + using Avalonia; using Avalonia.Media.Imaging; @@ -59,16 +61,70 @@ namespace SourceGit.Models } } - public partial class TextDiff + public class TextDiffChangeBlock + { + public TextDiffChangeBlock(int startLine, int endLine) + { + StartLine = startLine; + EndLine = endLine; + } + + public int StartLine { get; set; } = 0; + public int EndLine { get; set; } = 0; + + public bool IsInRange(int line) + { + return line >= StartLine && line <= EndLine; + } + } + + public partial class TextDiff : ObservableObject { public string File { get; set; } = string.Empty; public List Lines { get; set; } = new List(); public Vector ScrollOffset { get; set; } = Vector.Zero; public int MaxLineNumber = 0; + public int CurrentChangeBlockIdx + { + get => _currentChangeBlockIdx; + set => SetProperty(ref _currentChangeBlockIdx, value); + } + public string Repo { get; set; } = null; public DiffOption Option { get; set; } = null; + public List ChangeBlocks { get; set; } = []; + + public void ProcessChangeBlocks() + { + ChangeBlocks.Clear(); + int lineIdx = 0, blockStartIdx = 0; + bool isNewBlock = true; + foreach (var line in Lines) + { + lineIdx++; + if (line.Type == Models.TextDiffLineType.Added || + line.Type == Models.TextDiffLineType.Deleted || + line.Type == Models.TextDiffLineType.None) // Empty + { + if (isNewBlock) + { + isNewBlock = false; + blockStartIdx = lineIdx; + } + } + else + { + if (!isNewBlock) + { + ChangeBlocks.Add(new TextDiffChangeBlock(blockStartIdx, lineIdx - 1)); + isNewBlock = true; + } + } + } + } + public TextDiffSelection MakeSelection(int startLine, int endLine, bool isCombined, bool isOldSide) { var rs = new TextDiffSelection(); @@ -626,6 +682,8 @@ namespace SourceGit.Models return true; } + private int _currentChangeBlockIdx = -1; // NOTE: Use -1 as "not set". + [GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")] private static partial Regex REG_INDICATOR(); } diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 87b1a6de..05e9d833 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -51,6 +51,12 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _unifiedLines, value); } + public string ChangeBlockIndicator + { + get => _changeBlockIndicator; + private set => SetProperty(ref _changeBlockIndicator, value); + } + public DiffContext(string repo, Models.DiffOption option, DiffContext previous = null) { _repo = repo; @@ -73,6 +79,54 @@ namespace SourceGit.ViewModels LoadDiffContent(); } + public void PrevChange() + { + if (_content is Models.TextDiff textDiff) + { + if (textDiff.CurrentChangeBlockIdx > 0) + { + textDiff.CurrentChangeBlockIdx--; + } + else if (textDiff.ChangeBlocks.Count > 0) + { + // Force property value change and (re-)jump to first change block + textDiff.CurrentChangeBlockIdx = -1; + textDiff.CurrentChangeBlockIdx = 0; + } + } + RefreshChangeBlockIndicator(); + } + + public void NextChange() + { + if (_content is Models.TextDiff textDiff) + { + if (textDiff.CurrentChangeBlockIdx < textDiff.ChangeBlocks.Count - 1) + { + textDiff.CurrentChangeBlockIdx++; + } + else if (textDiff.ChangeBlocks.Count > 0) + { + // Force property value change and (re-)jump to last change block + textDiff.CurrentChangeBlockIdx = -1; + textDiff.CurrentChangeBlockIdx = textDiff.ChangeBlocks.Count - 1; + } + RefreshChangeBlockIndicator(); + } + } + + public void RefreshChangeBlockIndicator() + { + string curr = "-", tot = "-"; + if (_content is Models.TextDiff textDiff) + { + if (textDiff.CurrentChangeBlockIdx >= 0) + curr = (textDiff.CurrentChangeBlockIdx + 1).ToString(); + tot = (textDiff.ChangeBlocks.Count).ToString(); + } + ChangeBlockIndicator = curr + "/" + tot; + } + public void ToggleFullTextDiff() { Preference.Instance.UseFullTextDiff = !Preference.Instance.UseFullTextDiff; @@ -91,6 +145,12 @@ namespace SourceGit.ViewModels LoadDiffContent(); } + public void ToggleTwoSideDiff() + { + Preference.Instance.UseSideBySideDiff = !Preference.Instance.UseSideBySideDiff; + RefreshChangeBlockIndicator(); + } + public void OpenExternalMergeTool() { var toolType = Preference.Instance.ExternalMergeToolType; @@ -217,7 +277,9 @@ namespace SourceGit.ViewModels FileModeChange = latest.FileModeChange; Content = rs; IsTextDiff = rs is Models.TextDiff; - }); + + RefreshChangeBlockIndicator(); + }); }); } @@ -281,6 +343,7 @@ namespace SourceGit.ViewModels private string _title; private string _fileModeChange = string.Empty; private int _unifiedLines = 4; + private string _changeBlockIndicator = "-/-"; private bool _isTextDiff = false; private bool _ignoreWhitespace = false; private object _content = null; diff --git a/src/ViewModels/TwoSideTextDiff.cs b/src/ViewModels/TwoSideTextDiff.cs index 3fb1e63b..493174e0 100644 --- a/src/ViewModels/TwoSideTextDiff.cs +++ b/src/ViewModels/TwoSideTextDiff.cs @@ -45,10 +45,43 @@ namespace SourceGit.ViewModels FillEmptyLines(); + ProcessChangeBlocks(); + if (previous != null && previous.File == File) _syncScrollOffset = previous._syncScrollOffset; } + public List ChangeBlocks { get; set; } = []; + + public void ProcessChangeBlocks() + { + ChangeBlocks.Clear(); + int lineIdx = 0, blockStartIdx = 0; + bool isNewBlock = true; + foreach (var line in Old) // NOTE: Same block size in both Old and New lines. + { + lineIdx++; + if (line.Type == Models.TextDiffLineType.Added || + line.Type == Models.TextDiffLineType.Deleted || + line.Type == Models.TextDiffLineType.None) // Empty + { + if (isNewBlock) + { + isNewBlock = false; + blockStartIdx = lineIdx; + } + } + else + { + if (!isNewBlock) + { + ChangeBlocks.Add(new Models.TextDiffChangeBlock(blockStartIdx, lineIdx - 1)); + isNewBlock = true; + } + } + } + } + public void ConvertsToCombinedRange(Models.TextDiff combined, ref int startLine, ref int endLine, bool isOldSide) { endLine = Math.Min(endLine, combined.Lines.Count - 1); diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index e0627ad8..c555e903 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -42,6 +42,13 @@ + +