From 875d4b5382f240016b23ea0f4e9c10432f511dd0 Mon Sep 17 00:00:00 2001 From: goran-w Date: Fri, 1 Nov 2024 20:58:30 +0100 Subject: [PATCH 01/13] Added icons for "Previous/Next Difference" New StreamGeometry "Icons.Diff.Prev" / "Icons.Diff.Next" using SVG paths from "arrow_up_regular" / "arrow_down_regular" at https://avaloniaui.github.io/icons.html. --- src/Resources/Icons.axaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 53b2b3d3..7255be28 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -25,6 +25,8 @@ M128 183C128 154 154 128 183 128h521c30 0 55 26 55 55v38c0 17-17 34-34 34s-34-17-34-34v-26H196v495h26c17 0 34 17 34 34s-17 34-34 34h-38c-30 0-55-26-55-55V183zM380 896h-34c-26 0-47-21-47-47v-90h68V828h64V896H380c4 0 0 0 0 0zM759 828V896h90c26 0 47-21 47-47v-90h-68V828h-68zM828 435H896V346c0-26-21-47-47-47h-90v68H828v68zM435 299v68H367V439H299V346C299 320 320 299 346 299h90zM367 649H299v-107h68v107zM546 367V299h107v68h-107zM828 546H896v107h-68v-107zM649 828V896h-107v-68h107zM730 508v188c0 17-17 34-34 34h-188c-17 0-34-17-34-34s17-34 34-34h102l-124-124c-13-13-13-34 0-47 13-13 34-13 47 0l124 124V512c0-17 17-34 34-34 21-4 38 9 38 30z M889 0H135c-32 0-59 26-59 59v906c0 32 26 59 59 59h753c32 0 59-26 59-59v-906c1-33-26-59-58-59zm-165 177c31 0 56 25 56 56s-25 56-56 56-56-25-56-56 25-56 56-56zm-212 0c31 0 56 25 56 56S543 288 512 288s-56-25-56-56S481 177 512 177zm-212 0c31 0 56 25 56 56s-25 56-56 56-56-25-56-56 25-56 56-56zm209 606H285c-25 0-44-20-44-44 0-25 20-44 44-44h224c25 0 44 20 44 44 0 24-20 44-44 44zm230-212H285c-25 0-44-20-44-44 0-25 20-44 44-44h453c25 0 44 20 44 44 1 24-20 44-44 44z M854 307 611 73c-6-6-14-9-22-9H296c-4 0-8 4-8 8v56c0 4 4 8 8 8h277l219 211V824c0 4 4 8 8 8h56c4 0 8-4 8-8V330c0-9-4-17-10-23zM553 201c-6-6-14-9-23-9H192c-18 0-32 14-32 32v704c0 18 14 32 32 32h512c18 0 32-14 32-32V397c0-9-3-17-9-23L553 201zM568 753c0 4-3 7-8 7h-225c-4 0-8-3-8-7v-42c0-4 3-7 8-7h225c4 0 8 3 8 7v42zm0-220c0 4-3 7-8 7H476v85c0 4-3 7-7 7h-42c-4 0-7-3-7-7V540h-85c-4 0-8-3-8-7v-42c0-4 3-7 8-7H420v-85c0-4 3-7 7-7h42c4 0 7 3 7 7V484h85c4 0 8 3 8 7v42z + M4.21157,12.7326 C3.9244,13.0312 3.93361,13.5059 4.23213,13.7931 C4.53064,14.0803 5.00543,14.0711 5.29259,13.7725 L13.2521,5.49831 L13.2521,24.2511 C13.2521,24.6653 13.5879,25.0011 14.0021,25.0011 C14.4163,25.0011 14.7521,24.6653 14.7521,24.2511 L14.7521,5.4993 L22.7106,13.7725 C22.9978,14.0711 23.4726,14.0803 23.7711,13.7931 C24.0696,13.5059 24.0788,13.0312 23.7916,12.7326 L14.7223,3.30466 C14.3289,2.89568 13.6743,2.89568 13.2809,3.30466 L4.21157,12.7326 Z + M23.7916,15.2664 C24.0788,14.9679 24.0696,14.4931 23.7711,14.206 C23.4726,13.9188 22.9978,13.928 22.7106,14.2265 L14.7511,22.5007 L14.7511,3.74792 C14.7511,3.33371 14.4153,2.99792 14.0011,2.99792 C13.5869,2.99792 13.2511,3.33371 13.2511,3.74793 L13.2511,22.4998 L5.29259,14.2265 C5.00543,13.928 4.53064,13.9188 4.23213,14.206 C3.93361,14.4931 3.9244,14.9679 4.21157,15.2664 L13.2809,24.6944 C13.6743,25.1034 14.3289,25.1034 14.7223,24.6944 L23.7916,15.2664 Z M256 224l0 115L512 544l256-205 0-115-256 205L256 224zM512 685l-256-205L256 595 512 800 768 595l0-115L512 685z M768 800V685L512 480 256 685V800l256-205L768 800zM512 339 768 544V429L512 224 256 429V544l256-205z M509 546l271-271 91 91-348 349-1-1-13 13-363-361 91-91z From fbb07cf75f73cf06453abdfb13451378efe46656 Mon Sep 17 00:00:00 2001 From: goran-w Date: Mon, 4 Nov 2024 10:24:44 +0100 Subject: [PATCH 02/13] Added 2 new buttons for prev/next change in Diff These new buttons in DiffView toolbar are visible when IsTextDiff. They invoke new (and currently empty) methods PrevChange() / NextChange() in DiffContext. --- src/ViewModels/DiffContext.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 87b1a6de..e9bb703d 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -73,6 +73,16 @@ namespace SourceGit.ViewModels LoadDiffContent(); } + public void PrevChange() + { + // To be implemented... + } + + public void NextChange() + { + // To be implemented... + } + public void ToggleFullTextDiff() { Preference.Instance.UseFullTextDiff = !Preference.Instance.UseFullTextDiff; From d0dc9ac1fe48cc1144e2ca4500b6e81fdcb8b414 Mon Sep 17 00:00:00 2001 From: goran-w Date: Tue, 5 Nov 2024 16:55:03 +0100 Subject: [PATCH 03/13] Corrected misspelled local variable nextHigh(t)light --- src/Views/TextDiffView.axaml.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index fb2693a8..19e7ea31 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -268,7 +268,7 @@ namespace SourceGit.Views var highlightBG = info.Type == Models.TextDiffLineType.Added ? _presenter.AddedHighlightBrush : _presenter.DeletedHighlightBrush; var processingIdxStart = 0; var processingIdxEnd = 0; - var nextHightlight = 0; + var nextHighlight = 0; foreach (var tl in line.TextLines) { @@ -277,9 +277,9 @@ namespace SourceGit.Views var y = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) - textView.VerticalOffset; var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y; - while (nextHightlight < info.Highlights.Count) + while (nextHighlight < info.Highlights.Count) { - var highlight = info.Highlights[nextHightlight]; + var highlight = info.Highlights[nextHighlight]; if (highlight.Start >= processingIdxEnd) break; @@ -294,7 +294,7 @@ namespace SourceGit.Views if (highlight.End >= processingIdxEnd) break; - nextHightlight++; + nextHighlight++; } processingIdxStart = processingIdxEnd; From 000707278929cfe72ab8a85de37343ea5021bb84 Mon Sep 17 00:00:00 2001 From: goran-w Date: Thu, 7 Nov 2024 16:32:50 +0100 Subject: [PATCH 04/13] Implemented change-block navigation --- src/Commands/Diff.cs | 3 + src/Models/DiffResult.cs | 60 ++++++++++++- src/ViewModels/DiffContext.cs | 14 ++- src/ViewModels/TwoSideTextDiff.cs | 33 +++++++ src/Views/DiffView.axaml | 3 +- src/Views/TextDiffView.axaml | 3 + src/Views/TextDiffView.axaml.cs | 145 ++++++++++++++++++++++++------ 7 files changed, 228 insertions(+), 33 deletions(-) diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index dea15592..02ad7319 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 e9bb703d..a87e0bd3 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -75,12 +75,22 @@ namespace SourceGit.ViewModels public void PrevChange() { - // To be implemented... + if (_content is Models.TextDiff textDiff) + { + if (textDiff.CurrentChangeBlockIdx > 0) + textDiff.CurrentChangeBlockIdx--; + else if (textDiff.CurrentChangeBlockIdx == -1 && textDiff.ChangeBlocks.Count > 0) + textDiff.CurrentChangeBlockIdx = 0; // Jump to first change block + } } public void NextChange() { - // To be implemented... + if (_content is Models.TextDiff textDiff) + { + if (textDiff.CurrentChangeBlockIdx < textDiff.ChangeBlocks.Count - 1) + textDiff.CurrentChangeBlockIdx++; + } } public void ToggleFullTextDiff() 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..360ac8fc 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -241,7 +241,8 @@ + UseFullTextDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}" + CurrentChangeBlockIdx="{Binding CurrentChangeBlockIdx, Mode=OneWay}"/> diff --git a/src/Views/TextDiffView.axaml b/src/Views/TextDiffView.axaml index 57427321..52b0d084 100644 --- a/src/Views/TextDiffView.axaml +++ b/src/Views/TextDiffView.axaml @@ -27,6 +27,7 @@ UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}" ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" + CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}" EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}" SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/> @@ -49,6 +50,7 @@ UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" WordWrap="False" ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" + CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}" EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}" SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/> @@ -70,6 +72,7 @@ UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" WordWrap="False" ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" + CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}" EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}" SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/> diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 19e7ea31..42e4bb5a 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Text; using Avalonia; @@ -243,6 +244,10 @@ namespace SourceGit.Views if (_presenter.Document == null || !textView.VisualLinesValid) return; + var changeBlock = _presenter.GetCurrentChangeBlock(); + Brush changeBlockBG = new SolidColorBrush(Colors.Gray, 0.25); + Pen changeBlockFG = new Pen(Brushes.Gray, 1); + var lines = _presenter.GetLines(); var width = textView.Bounds.Width; foreach (var line in textView.VisualLines) @@ -255,51 +260,63 @@ namespace SourceGit.Views break; var info = lines[index - 1]; - var bg = GetBrushByLineType(info.Type); - if (bg == null) - continue; var startY = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineTop) - textView.VerticalOffset; var endY = line.GetTextLineVisualYPosition(line.TextLines[^1], VisualYPosition.LineBottom) - textView.VerticalOffset; - drawingContext.DrawRectangle(bg, null, new Rect(0, startY, width, endY - startY)); - if (info.Highlights.Count > 0) + var bg = GetBrushByLineType(info.Type); + if (bg != null) { - var highlightBG = info.Type == Models.TextDiffLineType.Added ? _presenter.AddedHighlightBrush : _presenter.DeletedHighlightBrush; - var processingIdxStart = 0; - var processingIdxEnd = 0; - var nextHighlight = 0; + if (bg != null) + drawingContext.DrawRectangle(bg, null, new Rect(0, startY, width, endY - startY)); - foreach (var tl in line.TextLines) + if (info.Highlights.Count > 0) { - processingIdxEnd += tl.Length; + var highlightBG = info.Type == Models.TextDiffLineType.Added ? _presenter.AddedHighlightBrush : _presenter.DeletedHighlightBrush; + var processingIdxStart = 0; + var processingIdxEnd = 0; + var nextHighlight = 0; - var y = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) - textView.VerticalOffset; - var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y; - - while (nextHighlight < info.Highlights.Count) + foreach (var tl in line.TextLines) { - var highlight = info.Highlights[nextHighlight]; - if (highlight.Start >= processingIdxEnd) - break; + processingIdxEnd += tl.Length; - var start = line.GetVisualColumn(highlight.Start < processingIdxStart ? processingIdxStart : highlight.Start); - var end = line.GetVisualColumn(highlight.End >= processingIdxEnd ? processingIdxEnd : highlight.End + 1); + var y = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) - textView.VerticalOffset; + var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y; - var x = line.GetTextLineVisualXPosition(tl, start) - textView.HorizontalOffset; - var w = line.GetTextLineVisualXPosition(tl, end) - textView.HorizontalOffset - x; - var rect = new Rect(x, y, w, h); - drawingContext.DrawRectangle(highlightBG, null, rect); + while (nextHighlight < info.Highlights.Count) + { + var highlight = info.Highlights[nextHighlight]; + if (highlight.Start >= processingIdxEnd) + break; - if (highlight.End >= processingIdxEnd) - break; + var start = line.GetVisualColumn(highlight.Start < processingIdxStart ? processingIdxStart : highlight.Start); + var end = line.GetVisualColumn(highlight.End >= processingIdxEnd ? processingIdxEnd : highlight.End + 1); - nextHighlight++; + var x = line.GetTextLineVisualXPosition(tl, start) - textView.HorizontalOffset; + var w = line.GetTextLineVisualXPosition(tl, end) - textView.HorizontalOffset - x; + var rect = new Rect(x, y, w, h); + drawingContext.DrawRectangle(highlightBG, null, rect); + + if (highlight.End >= processingIdxEnd) + break; + + nextHighlight++; + } + + processingIdxStart = processingIdxEnd; } - - processingIdxStart = processingIdxEnd; } } + + if (changeBlock != null && changeBlock.IsInRange(index)) + { + drawingContext.DrawRectangle(changeBlockBG, null, new Rect(0, startY, width, endY - startY)); + if (index == changeBlock.StartLine) + drawingContext.DrawLine(changeBlockFG, new Point(0, startY), new Point(width, startY)); + if (index == changeBlock.EndLine) + drawingContext.DrawLine(changeBlockFG, new Point(0, endY), new Point(width, endY)); + } } } @@ -466,6 +483,15 @@ namespace SourceGit.Views set => SetValue(SelectedChunkProperty, value); } + public static readonly StyledProperty CurrentChangeBlockIdxProperty = + AvaloniaProperty.Register(nameof(CurrentChangeBlockIdx)); + + public int CurrentChangeBlockIdx + { + get => GetValue(CurrentChangeBlockIdxProperty); + set => SetValue(CurrentChangeBlockIdxProperty, value); + } + protected override Type StyleKeyOverride => typeof(TextEditor); public ThemedTextDiffPresenter(TextArea area, TextDocument doc) : base(area, doc) @@ -600,6 +626,27 @@ namespace SourceGit.Views } } + public Models.TextDiffChangeBlock GetCurrentChangeBlock() + { + return GetChangeBlock(CurrentChangeBlockIdx); + } + + public virtual Models.TextDiffChangeBlock GetChangeBlock(int changeBlockIdx) + { + return null; + } + + public void JumpToChangeBlock(int changeBlockIdx) + { + var changeBlock = GetChangeBlock(changeBlockIdx); + if (changeBlock != null) + { + TextArea.Caret.Line = changeBlock.StartLine; + //TextArea.Caret.BringCaretToView(); // NOTE: Brings caret line (barely) into view. + ScrollToLine(changeBlock.StartLine); // NOTE: Brings specified line into center of view. + } + } + public override void Render(DrawingContext context) { base.Render(context); @@ -997,6 +1044,16 @@ namespace SourceGit.Views } } + public override Models.TextDiffChangeBlock GetChangeBlock(int changeBlockIdx) + { + if (DataContext is Models.TextDiff diff) + { + if (changeBlockIdx >= 0 && changeBlockIdx < diff.ChangeBlocks.Count) + return diff.ChangeBlocks[changeBlockIdx]; + } + return null; + } + protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); @@ -1217,6 +1274,16 @@ namespace SourceGit.Views } } + public override Models.TextDiffChangeBlock GetChangeBlock(int changeBlockIdx) + { + if (DataContext is ViewModels.TwoSideTextDiff diff) + { + if (changeBlockIdx >= 0 && changeBlockIdx < diff.ChangeBlocks.Count) + return diff.ChangeBlocks[changeBlockIdx]; + } + return null; + } + protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); @@ -1349,6 +1416,15 @@ namespace SourceGit.Views set => SetValue(EnableChunkSelectionProperty, value); } + public static readonly StyledProperty CurrentChangeBlockIdxProperty = + AvaloniaProperty.Register(nameof(CurrentChangeBlockIdx)); + + public int CurrentChangeBlockIdx + { + get => GetValue(CurrentChangeBlockIdxProperty); + set => SetValue(CurrentChangeBlockIdxProperty, value); + } + static TextDiffView() { UseSideBySideDiffProperty.Changed.AddClassHandler((v, _) => @@ -1375,6 +1451,17 @@ namespace SourceGit.Views v.Popup.Margin = new Thickness(0, top, right, 0); v.Popup.IsVisible = true; }); + + CurrentChangeBlockIdxProperty.Changed.AddClassHandler((v, e) => + { + if (v.Editor.Presenter != null) + { + foreach (var p in v.Editor.Presenter.GetVisualDescendants().OfType()) + { + p.JumpToChangeBlock((int)e.NewValue); + } + } + }); } public TextDiffView() From e0c219b46db260dbe5fad75fd6d5acf309389fbc Mon Sep 17 00:00:00 2001 From: goran-w Date: Fri, 15 Nov 2024 12:03:18 +0100 Subject: [PATCH 05/13] Unset current change-block in RefreshContent() --- src/Views/TextDiffView.axaml.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 42e4bb5a..4f819f0a 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1509,6 +1509,8 @@ namespace SourceGit.Views IsUnstagedChange = diff.Option.IsUnstaged; EnableChunkSelection = diff.Option.WorkingCopyChange != null; + + diff.CurrentChangeBlockIdx = -1; // Unset current change block. } private void OnStageChunk(object _1, RoutedEventArgs _2) From 96a9019487d78e4cea4d700ce1a8f2b81beb99e6 Mon Sep 17 00:00:00 2001 From: goran-w Date: Fri, 15 Nov 2024 13:43:05 +0100 Subject: [PATCH 06/13] Prev/next will (re-)scroll to first/last change-block in edge-cases I.e when unset or already at first/last change-block (or the only one). --- src/ViewModels/DiffContext.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index a87e0bd3..51418baf 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -78,9 +78,15 @@ namespace SourceGit.ViewModels if (_content is Models.TextDiff textDiff) { if (textDiff.CurrentChangeBlockIdx > 0) + { textDiff.CurrentChangeBlockIdx--; - else if (textDiff.CurrentChangeBlockIdx == -1 && textDiff.ChangeBlocks.Count > 0) - textDiff.CurrentChangeBlockIdx = 0; // Jump to first change block + } + else if (textDiff.ChangeBlocks.Count > 0) + { + // Force property value change and (re-)jump to first change block + textDiff.CurrentChangeBlockIdx = -1; + textDiff.CurrentChangeBlockIdx = 0; + } } } @@ -89,7 +95,15 @@ namespace SourceGit.ViewModels 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; + } } } From 1a99ce54d39dfa31fda063e2bde282e93a2723d7 Mon Sep 17 00:00:00 2001 From: goran-w Date: Sat, 16 Nov 2024 10:34:31 +0100 Subject: [PATCH 07/13] Cherrypick - feature: add buttons to go to prev/next change in text diff view (#616) Signed-off-by: leo (cherry picked from commit 134c71064ece232d9253c45bde10c8fc7daa7494) # Conflicts: # src/Views/DiffView.axaml # src/Views/TextDiffView.axaml.cs --- src/Views/TextDiffView.axaml.cs | 102 ++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 4f819f0a..f9f7ea3f 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -647,6 +647,108 @@ namespace SourceGit.Views } } + public void GotoPrevChange() + { + var view = TextArea.TextView; + var lines = GetLines(); + var firstLineIdx = lines.Count; + foreach (var line in view.VisualLines) + { + if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted) + continue; + + var index = line.FirstDocumentLine.LineNumber - 1; + if (index >= lines.Count) + continue; + + if (firstLineIdx > index) + firstLineIdx = index; + } + + var firstLineType = lines[firstLineIdx].Type; + var isChangeFirstLine = firstLineType != Models.TextDiffLineType.Normal && firstLineType != Models.TextDiffLineType.Indicator; + if (isChangeFirstLine) + { + for (var i = firstLineIdx - 1; i >= 0; i--) + { + var prevType = lines[i].Type; + if (prevType == Models.TextDiffLineType.Normal || prevType == Models.TextDiffLineType.Indicator) + { + ScrollToLine(i + 2); + return; + } + } + } + else + { + var prevChangeEnd = -1; + for (var i = firstLineIdx - 1; i >= 0; i--) + { + var prevType = lines[i].Type; + if (prevType == Models.TextDiffLineType.None || + prevType == Models.TextDiffLineType.Added || + prevType == Models.TextDiffLineType.Deleted) + { + prevChangeEnd = i; + break; + } + } + + if (prevChangeEnd <= 0) + return; + + for (var i = prevChangeEnd - 1; i >= 0; i--) + { + var prevType = lines[i].Type; + if (prevType == Models.TextDiffLineType.Normal || prevType == Models.TextDiffLineType.Indicator) + { + ScrollToLine(i + 2); + return; + } + } + } + } + + public void GotoNextChange() + { + var view = TextArea.TextView; + var lines = GetLines(); + var lastLineIdx = -1; + foreach (var line in view.VisualLines) + { + if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted) + continue; + + var index = line.FirstDocumentLine.LineNumber - 1; + if (index >= lines.Count) + continue; + + if (lastLineIdx < index) + lastLineIdx = index; + } + + var lastLineType = lines[lastLineIdx].Type; + var findNormalLine = lastLineType == Models.TextDiffLineType.Normal || lastLineType == Models.TextDiffLineType.Indicator; + for (var idx = lastLineIdx + 1; idx < lines.Count; idx++) + { + var nextType = lines[idx].Type; + if (nextType == Models.TextDiffLineType.None || + nextType == Models.TextDiffLineType.Added || + nextType == Models.TextDiffLineType.Deleted) + { + if (findNormalLine) + { + ScrollToLine(idx + 1); + return; + } + } + else if (!findNormalLine) + { + findNormalLine = true; + } + } + } + public override void Render(DrawingContext context) { base.Render(context); From 57e147e84c5c2e629b5f11d43f2b31f2ebe38009 Mon Sep 17 00:00:00 2001 From: goran-w Date: Sat, 16 Nov 2024 10:35:16 +0100 Subject: [PATCH 08/13] Revert "Added icons for "Previous/Next Difference"" This reverts commit 1f8dc29de20708a78cba26a341d3451d11304ef9. --- src/Resources/Icons.axaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 7255be28..53b2b3d3 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -25,8 +25,6 @@ M128 183C128 154 154 128 183 128h521c30 0 55 26 55 55v38c0 17-17 34-34 34s-34-17-34-34v-26H196v495h26c17 0 34 17 34 34s-17 34-34 34h-38c-30 0-55-26-55-55V183zM380 896h-34c-26 0-47-21-47-47v-90h68V828h64V896H380c4 0 0 0 0 0zM759 828V896h90c26 0 47-21 47-47v-90h-68V828h-68zM828 435H896V346c0-26-21-47-47-47h-90v68H828v68zM435 299v68H367V439H299V346C299 320 320 299 346 299h90zM367 649H299v-107h68v107zM546 367V299h107v68h-107zM828 546H896v107h-68v-107zM649 828V896h-107v-68h107zM730 508v188c0 17-17 34-34 34h-188c-17 0-34-17-34-34s17-34 34-34h102l-124-124c-13-13-13-34 0-47 13-13 34-13 47 0l124 124V512c0-17 17-34 34-34 21-4 38 9 38 30z M889 0H135c-32 0-59 26-59 59v906c0 32 26 59 59 59h753c32 0 59-26 59-59v-906c1-33-26-59-58-59zm-165 177c31 0 56 25 56 56s-25 56-56 56-56-25-56-56 25-56 56-56zm-212 0c31 0 56 25 56 56S543 288 512 288s-56-25-56-56S481 177 512 177zm-212 0c31 0 56 25 56 56s-25 56-56 56-56-25-56-56 25-56 56-56zm209 606H285c-25 0-44-20-44-44 0-25 20-44 44-44h224c25 0 44 20 44 44 0 24-20 44-44 44zm230-212H285c-25 0-44-20-44-44 0-25 20-44 44-44h453c25 0 44 20 44 44 1 24-20 44-44 44z M854 307 611 73c-6-6-14-9-22-9H296c-4 0-8 4-8 8v56c0 4 4 8 8 8h277l219 211V824c0 4 4 8 8 8h56c4 0 8-4 8-8V330c0-9-4-17-10-23zM553 201c-6-6-14-9-23-9H192c-18 0-32 14-32 32v704c0 18 14 32 32 32h512c18 0 32-14 32-32V397c0-9-3-17-9-23L553 201zM568 753c0 4-3 7-8 7h-225c-4 0-8-3-8-7v-42c0-4 3-7 8-7h225c4 0 8 3 8 7v42zm0-220c0 4-3 7-8 7H476v85c0 4-3 7-7 7h-42c-4 0-7-3-7-7V540h-85c-4 0-8-3-8-7v-42c0-4 3-7 8-7H420v-85c0-4 3-7 7-7h42c4 0 7 3 7 7V484h85c4 0 8 3 8 7v42z - M4.21157,12.7326 C3.9244,13.0312 3.93361,13.5059 4.23213,13.7931 C4.53064,14.0803 5.00543,14.0711 5.29259,13.7725 L13.2521,5.49831 L13.2521,24.2511 C13.2521,24.6653 13.5879,25.0011 14.0021,25.0011 C14.4163,25.0011 14.7521,24.6653 14.7521,24.2511 L14.7521,5.4993 L22.7106,13.7725 C22.9978,14.0711 23.4726,14.0803 23.7711,13.7931 C24.0696,13.5059 24.0788,13.0312 23.7916,12.7326 L14.7223,3.30466 C14.3289,2.89568 13.6743,2.89568 13.2809,3.30466 L4.21157,12.7326 Z - M23.7916,15.2664 C24.0788,14.9679 24.0696,14.4931 23.7711,14.206 C23.4726,13.9188 22.9978,13.928 22.7106,14.2265 L14.7511,22.5007 L14.7511,3.74792 C14.7511,3.33371 14.4153,2.99792 14.0011,2.99792 C13.5869,2.99792 13.2511,3.33371 13.2511,3.74793 L13.2511,22.4998 L5.29259,14.2265 C5.00543,13.928 4.53064,13.9188 4.23213,14.206 C3.93361,14.4931 3.9244,14.9679 4.21157,15.2664 L13.2809,24.6944 C13.6743,25.1034 14.3289,25.1034 14.7223,24.6944 L23.7916,15.2664 Z M256 224l0 115L512 544l256-205 0-115-256 205L256 224zM512 685l-256-205L256 595 512 800 768 595l0-115L512 685z M768 800V685L512 480 256 685V800l256-205L768 800zM512 339 768 544V429L512 224 256 429V544l256-205z M509 546l271-271 91 91-348 349-1-1-13 13-363-361 91-91z From 07cf4e6fe0f79957774313cf70ebd22c5699f2e1 Mon Sep 17 00:00:00 2001 From: goran-w Date: Sat, 16 Nov 2024 10:47:10 +0100 Subject: [PATCH 09/13] Corrected method duplication mistake, from rebase conflict resolve --- src/Views/TextDiffView.axaml.cs | 102 -------------------------------- 1 file changed, 102 deletions(-) diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index f9f7ea3f..4f819f0a 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -647,108 +647,6 @@ namespace SourceGit.Views } } - public void GotoPrevChange() - { - var view = TextArea.TextView; - var lines = GetLines(); - var firstLineIdx = lines.Count; - foreach (var line in view.VisualLines) - { - if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted) - continue; - - var index = line.FirstDocumentLine.LineNumber - 1; - if (index >= lines.Count) - continue; - - if (firstLineIdx > index) - firstLineIdx = index; - } - - var firstLineType = lines[firstLineIdx].Type; - var isChangeFirstLine = firstLineType != Models.TextDiffLineType.Normal && firstLineType != Models.TextDiffLineType.Indicator; - if (isChangeFirstLine) - { - for (var i = firstLineIdx - 1; i >= 0; i--) - { - var prevType = lines[i].Type; - if (prevType == Models.TextDiffLineType.Normal || prevType == Models.TextDiffLineType.Indicator) - { - ScrollToLine(i + 2); - return; - } - } - } - else - { - var prevChangeEnd = -1; - for (var i = firstLineIdx - 1; i >= 0; i--) - { - var prevType = lines[i].Type; - if (prevType == Models.TextDiffLineType.None || - prevType == Models.TextDiffLineType.Added || - prevType == Models.TextDiffLineType.Deleted) - { - prevChangeEnd = i; - break; - } - } - - if (prevChangeEnd <= 0) - return; - - for (var i = prevChangeEnd - 1; i >= 0; i--) - { - var prevType = lines[i].Type; - if (prevType == Models.TextDiffLineType.Normal || prevType == Models.TextDiffLineType.Indicator) - { - ScrollToLine(i + 2); - return; - } - } - } - } - - public void GotoNextChange() - { - var view = TextArea.TextView; - var lines = GetLines(); - var lastLineIdx = -1; - foreach (var line in view.VisualLines) - { - if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted) - continue; - - var index = line.FirstDocumentLine.LineNumber - 1; - if (index >= lines.Count) - continue; - - if (lastLineIdx < index) - lastLineIdx = index; - } - - var lastLineType = lines[lastLineIdx].Type; - var findNormalLine = lastLineType == Models.TextDiffLineType.Normal || lastLineType == Models.TextDiffLineType.Indicator; - for (var idx = lastLineIdx + 1; idx < lines.Count; idx++) - { - var nextType = lines[idx].Type; - if (nextType == Models.TextDiffLineType.None || - nextType == Models.TextDiffLineType.Added || - nextType == Models.TextDiffLineType.Deleted) - { - if (findNormalLine) - { - ScrollToLine(idx + 1); - return; - } - } - else if (!findNormalLine) - { - findNormalLine = true; - } - } - } - public override void Render(DrawingContext context) { base.Render(context); From 5597d2531384edcd75bc349df82cc07361d5ef08 Mon Sep 17 00:00:00 2001 From: goran-w Date: Sat, 16 Nov 2024 11:36:10 +0100 Subject: [PATCH 10/13] Re-enabled my implementation, after merge from pushed alternative --- src/Views/DiffView.axaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index 360ac8fc..425018f8 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -36,7 +36,7 @@ + +