Compare commits

..

7 commits

Author SHA1 Message Date
Göran W
69f6042563
Merge cd946d2ba5 into 4b6bb70f20 2024-11-15 14:57:55 +01:00
goran-w
cd946d2ba5 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).
2024-11-15 13:43:05 +01:00
goran-w
0f852e2c24 Unset current change-block in RefreshContent() 2024-11-15 12:39:52 +01:00
goran-w
0fb89039ff Implemented change-block navigation 2024-11-15 11:46:52 +01:00
goran-w
096da3452e Corrected misspelled local variable nextHigh(t)light 2024-11-15 10:14:49 +01:00
goran-w
5c72e999ae 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.
2024-11-15 10:14:49 +01:00
goran-w
91af25d58a 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.
2024-11-15 10:14:49 +01:00
11 changed files with 60 additions and 234 deletions

View file

@ -32,7 +32,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
dotnet-version: 8.0.x
- name: Configure arm64 packages
if: ${{ matrix.runtime == 'linux-arm64' }}
run: |

View file

@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.0",
"version": "8.0.0",
"rollForward": "latestMajor",
"allowPrerelease": false
}

View file

@ -28,9 +28,9 @@ namespace SourceGit.Commands
Context = repo;
if (ignoreWhitespace)
Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}";
Args = $"diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}";
else
Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
Args = $"diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
}
public Models.DiffResult Result()

View file

@ -152,12 +152,18 @@ namespace SourceGit.Models
set;
} = "---";
public Dictionary<string, FilterMode> CollectHistoriesFilters()
public FilterMode GetHistoriesFilterMode(string pattern, FilterType type)
{
var map = new Dictionary<string, FilterMode>();
foreach (var filter in HistoriesFilters)
map.Add(filter.Pattern, filter.Mode);
return map;
{
if (filter.Type != type)
continue;
if (filter.Pattern.Equals(pattern, StringComparison.Ordinal))
return filter.Mode;
}
return FilterMode.None;
}
public bool UpdateHistoriesFilter(string pattern, FilterType type, FilterMode mode)

View file

@ -25,6 +25,8 @@
<StreamGeometry x:Key="Icons.Detached">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Detail">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Diff">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Diff.Prev">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Diff.Next">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</StreamGeometry>
<StreamGeometry x:Key="Icons.DoubleDown">M256 224l0 115L512 544l256-205 0-115-256 205L256 224zM512 685l-256-205L256 595 512 800 768 595l0-115L512 685z</StreamGeometry>
<StreamGeometry x:Key="Icons.DoubleUp">M768 800V685L512 480 256 685V800l256-205L768 800zM512 339 768 544V429L512 224 256 429V544l256-205z</StreamGeometry>
<StreamGeometry x:Key="Icons.Down">M509 546l271-271 91 91-348 349-1-1-13 13-363-361 91-91z</StreamGeometry>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>App.manifest</ApplicationManifest>
<ApplicationIcon>App.ico</ApplicationIcon>

View file

@ -51,12 +51,6 @@ 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;
@ -94,7 +88,6 @@ namespace SourceGit.ViewModels
textDiff.CurrentChangeBlockIdx = 0;
}
}
RefreshChangeBlockIndicator();
}
public void NextChange()
@ -111,22 +104,9 @@ namespace SourceGit.ViewModels
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;
@ -271,9 +251,7 @@ namespace SourceGit.ViewModels
FileModeChange = latest.FileModeChange;
Content = rs;
IsTextDiff = rs is Models.TextDiff;
RefreshChangeBlockIndicator();
});
});
});
}
@ -337,7 +315,6 @@ 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;

View file

@ -827,6 +827,9 @@ namespace SourceGit.ViewModels
public void RefreshTags()
{
var tags = new Commands.QueryTags(_fullpath).Result();
foreach (var tag in tags)
tag.FilterMode = _settings.GetHistoriesFilterMode(tag.Name, Models.FilterType.Tag);
Dispatcher.UIThread.Invoke(() =>
{
Tags = tags;
@ -2032,9 +2035,8 @@ namespace SourceGit.ViewModels
builder.Run(visibles, remotes, true);
}
var historiesFilters = _settings.CollectHistoriesFilters();
UpdateBranchTreeFilterMode(builder.Locals, historiesFilters);
UpdateBranchTreeFilterMode(builder.Remotes, historiesFilters);
UpdateBranchTreeFilterMode(builder.Locals, true);
UpdateBranchTreeFilterMode(builder.Remotes, false);
return builder;
}
@ -2054,8 +2056,7 @@ namespace SourceGit.ViewModels
}
}
var historiesFilters = _settings.CollectHistoriesFilters();
UpdateTagFilterMode(historiesFilters);
UpdateTagFilterMode();
return visible;
}
@ -2079,36 +2080,32 @@ namespace SourceGit.ViewModels
private void RefreshHistoriesFilters()
{
var filters = _settings.CollectHistoriesFilters();
UpdateBranchTreeFilterMode(LocalBranchTrees, filters);
UpdateBranchTreeFilterMode(RemoteBranchTrees, filters);
UpdateTagFilterMode(filters);
UpdateBranchTreeFilterMode(LocalBranchTrees, true);
UpdateBranchTreeFilterMode(RemoteBranchTrees, false);
UpdateTagFilterMode();
Task.Run(RefreshCommits);
}
private void UpdateBranchTreeFilterMode(List<BranchTreeNode> nodes, Dictionary<string, Models.FilterMode> filters)
private void UpdateBranchTreeFilterMode(List<BranchTreeNode> nodes, bool isLocal)
{
foreach (var node in nodes)
{
if (filters.TryGetValue(node.Path, out var value))
node.FilterMode = value;
if (node.IsBranch)
{
node.FilterMode = _settings.GetHistoriesFilterMode(node.Path, isLocal ? Models.FilterType.LocalBranch : Models.FilterType.RemoteBranch);
}
else
node.FilterMode = Models.FilterMode.None;
if (!node.IsBranch)
UpdateBranchTreeFilterMode(node.Children, filters);
{
node.FilterMode = _settings.GetHistoriesFilterMode(node.Path, isLocal ? Models.FilterType.LocalBranchFolder : Models.FilterType.RemoteBranchFolder);
UpdateBranchTreeFilterMode(node.Children, isLocal);
}
}
}
private void UpdateTagFilterMode(Dictionary<string, Models.FilterMode> filters)
private void UpdateTagFilterMode()
{
foreach (var tag in _tags)
{
if (filters.TryGetValue(tag.Name, out var value))
tag.FilterMode = value;
else
tag.FilterMode = Models.FilterMode.None;
}
tag.FilterMode = _settings.GetHistoriesFilterMode(tag.Name, Models.FilterType.Tag);
}
private void ResetBranchTreeFilterMode(List<BranchTreeNode> nodes)

View file

@ -35,30 +35,21 @@
<!-- Toolbar Buttons -->
<StackPanel Grid.Column="3" Margin="8,0,0,0" Orientation="Horizontal" VerticalAlignment="Center">
<Button Classes="icon_button"
Width="28"
Click="OnGotoPrevChange"
Width="32"
Command="{Binding PrevChange}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.Prev}">
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Up}"/>
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Diff.Prev}"/>
</Button>
<TextBlock Classes="primary"
Margin="0,0,0,0"
Text="{Binding ChangeBlockIndicator}"
FontSize="11"
TextTrimming="CharacterEllipsis"
IsVisible="{Binding IsTextDiff}"/>
<Button Classes="icon_button"
Width="28"
Click="OnGotoNextChange"
Width="32"
Command="{Binding NextChange}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.Next}">
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Down}"/>
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Diff.Next}"/>
</Button>
<Button Classes="icon_button"
Width="28"
Width="32"
Command="{Binding IncrUnified}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Incr}">
@ -69,7 +60,7 @@
</Button>
<Button Classes="icon_button"
Width="28"
Width="32"
Command="{Binding DecrUnified}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Decr}">
@ -83,7 +74,9 @@
</Button>
<ToggleButton Classes="line_path"
Width="28"
Width="32" Height="18"
Background="Transparent"
Padding="9,6"
Command="{Binding ToggleFullTextDiff}"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}"
IsVisible="{Binding IsTextDiff}"
@ -92,8 +85,9 @@
</ToggleButton>
<ToggleButton Classes="line_path"
Width="28"
Width="32" Height="18"
Background="Transparent"
Padding="9,6"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting, Mode=TwoWay}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.SyntaxHighlight}">
@ -101,7 +95,9 @@
</ToggleButton>
<ToggleButton Classes="line_path"
Width="28"
Width="32" Height="18"
Background="Transparent"
Padding="9,6"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap, Mode=TwoWay}"
ToolTip.Tip="{DynamicResource Text.Diff.ToggleWordWrap}">
<ToggleButton.IsVisible>
@ -115,14 +111,14 @@
</ToggleButton>
<ToggleButton Classes="line_path"
Width="28"
Width="32"
IsChecked="{Binding IgnoreWhitespace, Mode=TwoWay}"
ToolTip.Tip="{DynamicResource Text.Diff.IgnoreWhitespace}">
<Path Width="14" Height="14" Stretch="Uniform" Data="{StaticResource Icons.Whitespace}"/>
</ToggleButton>
<ToggleButton Classes="line_path"
Width="28"
Width="32"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView, Mode=TwoWay}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.ShowHiddenSymbols}">
@ -130,14 +126,16 @@
</ToggleButton>
<ToggleButton Classes="line_path"
Width="28" Height="18"
Width="32" Height="18"
Background="Transparent"
Padding="9,6"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=TwoWay}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.SideBySide}">
<Path Width="12" Height="12" Data="{StaticResource Icons.LayoutHorizontal}" Margin="0,2,0,0"/>
</ToggleButton>
<Button Classes="icon_button" Width="28" Command="{Binding OpenExternalMergeTool}" ToolTip.Tip="{DynamicResource Text.Diff.UseMerger}">
<Button Classes="icon_button" Width="32" Command="{Binding OpenExternalMergeTool}" ToolTip.Tip="{DynamicResource Text.Diff.UseMerger}">
<Path Width="12" Height="12" Stretch="Uniform" Data="{StaticResource Icons.OpenWith}"/>
</Button>
</StackPanel>

View file

@ -1,6 +1,4 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace SourceGit.Views
{
@ -10,49 +8,5 @@ namespace SourceGit.Views
{
InitializeComponent();
}
public bool UseChangeBlocks { get; set; } = true;
private void OnGotoPrevChange(object _, RoutedEventArgs e)
{
if (UseChangeBlocks)
{
if (DataContext is ViewModels.DiffContext diffCtx)
diffCtx.PrevChange();
}
else
{
var textDiff = this.FindDescendantOfType<ThemedTextDiffPresenter>();
if (textDiff == null)
return;
textDiff.GotoPrevChange();
if (textDiff is SingleSideTextDiffPresenter presenter)
presenter.ForceSyncScrollOffset();
e.Handled = true;
}
}
private void OnGotoNextChange(object _, RoutedEventArgs e)
{
if (UseChangeBlocks)
{
if (DataContext is ViewModels.DiffContext diffCtx)
diffCtx.NextChange();
}
else
{
var textDiff = this.FindDescendantOfType<ThemedTextDiffPresenter>();
if (textDiff == null)
return;
textDiff.GotoNextChange();
if (textDiff is SingleSideTextDiffPresenter presenter)
presenter.ForceSyncScrollOffset();
e.Handled = true;
}
}
}
}

View file

@ -524,106 +524,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;
}
if (firstLineIdx <= 1)
return;
var firstLineType = lines[firstLineIdx].Type;
var prevLineType = lines[firstLineIdx - 1].Type;
var isChangeFirstLine = firstLineType != Models.TextDiffLineType.Normal && firstLineType != Models.TextDiffLineType.Indicator;
var isChangePrevLine = prevLineType != Models.TextDiffLineType.Normal && prevLineType != Models.TextDiffLineType.Indicator;
if (isChangeFirstLine && isChangePrevLine)
{
for (var i = firstLineIdx - 2; i >= 0; i--)
{
var prevType = lines[i].Type;
if (prevType == Models.TextDiffLineType.Normal || prevType == Models.TextDiffLineType.Indicator)
{
ScrollToLine(i + 2);
return;
}
}
}
var findChange = false;
for (var i = firstLineIdx - 1; i >= 0; i--)
{
var prevType = lines[i].Type;
if (prevType == Models.TextDiffLineType.Normal || prevType == Models.TextDiffLineType.Indicator)
{
if (findChange)
{
ScrollToLine(i + 2);
return;
}
}
else if (!findChange)
{
findChange = true;
}
}
}
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;
}
if (lastLineIdx >= lines.Count - 1)
return;
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 Models.TextDiffChangeBlock GetCurrentChangeBlock()
{
return GetChangeBlock(CurrentChangeBlockIdx);
@ -1125,12 +1025,6 @@ namespace SourceGit.Views
TextArea.LeftMargins.Add(new LineModifyTypeMargin());
}
public void ForceSyncScrollOffset()
{
if (DataContext is ViewModels.TwoSideTextDiff diff)
diff.SyncScrollOffset = _scrollViewer.Offset;
}
public override List<Models.TextDiffLine> GetLines()
{
if (DataContext is ViewModels.TwoSideTextDiff diff)
@ -1457,8 +1351,6 @@ namespace SourceGit.Views
foreach (var p in v.Editor.Presenter.GetVisualDescendants().OfType<ThemedTextDiffPresenter>())
{
p.JumpToChangeBlock((int)e.NewValue);
if (p is SingleSideTextDiffPresenter ssp)
ssp.ForceSyncScrollOffset();
}
}
});