mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-26 21:17:20 -08:00
Compare commits
19 commits
cd946d2ba5
...
760e240db7
Author | SHA1 | Date | |
---|---|---|---|
|
760e240db7 | ||
|
636be4a7a8 | ||
|
4882ad0ad6 | ||
|
dc5bd42477 | ||
|
5597d25313 | ||
|
882878dbe5 | ||
|
07cf4e6fe0 | ||
|
57e147e84c | ||
|
1a99ce54d3 | ||
|
96a9019487 | ||
|
e0c219b46d | ||
|
0007072789 | ||
|
d0dc9ac1fe | ||
|
fbb07cf75f | ||
|
875d4b5382 | ||
|
52c7388a38 | ||
|
134c71064e | ||
|
cd137e222c | ||
|
8d84d0f6a1 |
13 changed files with 494 additions and 83 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 9.0.x
|
||||||
- name: Configure arm64 packages
|
- name: Configure arm64 packages
|
||||||
if: ${{ matrix.runtime == 'linux-arm64' }}
|
if: ${{ matrix.runtime == 'linux-arm64' }}
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.0",
|
"version": "9.0.0",
|
||||||
"rollForward": "latestMajor",
|
"rollForward": "latestMajor",
|
||||||
"allowPrerelease": false
|
"allowPrerelease": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,9 @@ namespace SourceGit.Commands
|
||||||
Context = repo;
|
Context = repo;
|
||||||
|
|
||||||
if (ignoreWhitespace)
|
if (ignoreWhitespace)
|
||||||
Args = $"diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}";
|
Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}";
|
||||||
else
|
else
|
||||||
Args = $"diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
|
Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Models.DiffResult Result()
|
public Models.DiffResult Result()
|
||||||
|
@ -51,6 +51,9 @@ namespace SourceGit.Commands
|
||||||
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
|
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_result.TextDiff != null)
|
||||||
|
_result.TextDiff.ProcessChangeBlocks();
|
||||||
|
|
||||||
return _result;
|
return _result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Media.Imaging;
|
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 string File { get; set; } = string.Empty;
|
||||||
public List<TextDiffLine> Lines { get; set; } = new List<TextDiffLine>();
|
public List<TextDiffLine> Lines { get; set; } = new List<TextDiffLine>();
|
||||||
public Vector ScrollOffset { get; set; } = Vector.Zero;
|
public Vector ScrollOffset { get; set; } = Vector.Zero;
|
||||||
public int MaxLineNumber = 0;
|
public int MaxLineNumber = 0;
|
||||||
|
|
||||||
|
public int CurrentChangeBlockIdx
|
||||||
|
{
|
||||||
|
get => _currentChangeBlockIdx;
|
||||||
|
set => SetProperty(ref _currentChangeBlockIdx, value);
|
||||||
|
}
|
||||||
|
|
||||||
public string Repo { get; set; } = null;
|
public string Repo { get; set; } = null;
|
||||||
public DiffOption Option { get; set; } = null;
|
public DiffOption Option { get; set; } = null;
|
||||||
|
|
||||||
|
public List<TextDiffChangeBlock> 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)
|
public TextDiffSelection MakeSelection(int startLine, int endLine, bool isCombined, bool isOldSide)
|
||||||
{
|
{
|
||||||
var rs = new TextDiffSelection();
|
var rs = new TextDiffSelection();
|
||||||
|
@ -626,6 +682,8 @@ namespace SourceGit.Models
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int _currentChangeBlockIdx = -1; // NOTE: Use -1 as "not set".
|
||||||
|
|
||||||
[GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
|
[GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
|
||||||
private static partial Regex REG_INDICATOR();
|
private static partial Regex REG_INDICATOR();
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,18 +152,12 @@ namespace SourceGit.Models
|
||||||
set;
|
set;
|
||||||
} = "---";
|
} = "---";
|
||||||
|
|
||||||
public FilterMode GetHistoriesFilterMode(string pattern, FilterType type)
|
public Dictionary<string, FilterMode> CollectHistoriesFilters()
|
||||||
{
|
{
|
||||||
|
var map = new Dictionary<string, FilterMode>();
|
||||||
foreach (var filter in HistoriesFilters)
|
foreach (var filter in HistoriesFilters)
|
||||||
{
|
map.Add(filter.Pattern, filter.Mode);
|
||||||
if (filter.Type != type)
|
return map;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (filter.Pattern.Equals(pattern, StringComparison.Ordinal))
|
|
||||||
return filter.Mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FilterMode.None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UpdateHistoriesFilter(string pattern, FilterType type, FilterMode mode)
|
public bool UpdateHistoriesFilter(string pattern, FilterType type, FilterMode mode)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
<ApplicationManifest>App.manifest</ApplicationManifest>
|
<ApplicationManifest>App.manifest</ApplicationManifest>
|
||||||
<ApplicationIcon>App.ico</ApplicationIcon>
|
<ApplicationIcon>App.ico</ApplicationIcon>
|
||||||
|
|
|
@ -51,6 +51,12 @@ namespace SourceGit.ViewModels
|
||||||
private set => SetProperty(ref _unifiedLines, value);
|
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)
|
public DiffContext(string repo, Models.DiffOption option, DiffContext previous = null)
|
||||||
{
|
{
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
|
@ -73,6 +79,54 @@ namespace SourceGit.ViewModels
|
||||||
LoadDiffContent();
|
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()
|
public void ToggleFullTextDiff()
|
||||||
{
|
{
|
||||||
Preference.Instance.UseFullTextDiff = !Preference.Instance.UseFullTextDiff;
|
Preference.Instance.UseFullTextDiff = !Preference.Instance.UseFullTextDiff;
|
||||||
|
@ -217,7 +271,9 @@ namespace SourceGit.ViewModels
|
||||||
FileModeChange = latest.FileModeChange;
|
FileModeChange = latest.FileModeChange;
|
||||||
Content = rs;
|
Content = rs;
|
||||||
IsTextDiff = rs is Models.TextDiff;
|
IsTextDiff = rs is Models.TextDiff;
|
||||||
});
|
|
||||||
|
RefreshChangeBlockIndicator();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,6 +337,7 @@ namespace SourceGit.ViewModels
|
||||||
private string _title;
|
private string _title;
|
||||||
private string _fileModeChange = string.Empty;
|
private string _fileModeChange = string.Empty;
|
||||||
private int _unifiedLines = 4;
|
private int _unifiedLines = 4;
|
||||||
|
private string _changeBlockIndicator = "-/-";
|
||||||
private bool _isTextDiff = false;
|
private bool _isTextDiff = false;
|
||||||
private bool _ignoreWhitespace = false;
|
private bool _ignoreWhitespace = false;
|
||||||
private object _content = null;
|
private object _content = null;
|
||||||
|
|
|
@ -827,9 +827,6 @@ namespace SourceGit.ViewModels
|
||||||
public void RefreshTags()
|
public void RefreshTags()
|
||||||
{
|
{
|
||||||
var tags = new Commands.QueryTags(_fullpath).Result();
|
var tags = new Commands.QueryTags(_fullpath).Result();
|
||||||
foreach (var tag in tags)
|
|
||||||
tag.FilterMode = _settings.GetHistoriesFilterMode(tag.Name, Models.FilterType.Tag);
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Invoke(() =>
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
{
|
{
|
||||||
Tags = tags;
|
Tags = tags;
|
||||||
|
@ -2035,8 +2032,9 @@ namespace SourceGit.ViewModels
|
||||||
builder.Run(visibles, remotes, true);
|
builder.Run(visibles, remotes, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateBranchTreeFilterMode(builder.Locals, true);
|
var historiesFilters = _settings.CollectHistoriesFilters();
|
||||||
UpdateBranchTreeFilterMode(builder.Remotes, false);
|
UpdateBranchTreeFilterMode(builder.Locals, historiesFilters);
|
||||||
|
UpdateBranchTreeFilterMode(builder.Remotes, historiesFilters);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2056,7 +2054,8 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateTagFilterMode();
|
var historiesFilters = _settings.CollectHistoriesFilters();
|
||||||
|
UpdateTagFilterMode(historiesFilters);
|
||||||
return visible;
|
return visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2080,32 +2079,36 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
private void RefreshHistoriesFilters()
|
private void RefreshHistoriesFilters()
|
||||||
{
|
{
|
||||||
UpdateBranchTreeFilterMode(LocalBranchTrees, true);
|
var filters = _settings.CollectHistoriesFilters();
|
||||||
UpdateBranchTreeFilterMode(RemoteBranchTrees, false);
|
UpdateBranchTreeFilterMode(LocalBranchTrees, filters);
|
||||||
UpdateTagFilterMode();
|
UpdateBranchTreeFilterMode(RemoteBranchTrees, filters);
|
||||||
|
UpdateTagFilterMode(filters);
|
||||||
Task.Run(RefreshCommits);
|
Task.Run(RefreshCommits);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBranchTreeFilterMode(List<BranchTreeNode> nodes, bool isLocal)
|
private void UpdateBranchTreeFilterMode(List<BranchTreeNode> nodes, Dictionary<string, Models.FilterMode> filters)
|
||||||
{
|
{
|
||||||
foreach (var node in nodes)
|
foreach (var node in nodes)
|
||||||
{
|
{
|
||||||
if (node.IsBranch)
|
if (filters.TryGetValue(node.Path, out var value))
|
||||||
{
|
node.FilterMode = value;
|
||||||
node.FilterMode = _settings.GetHistoriesFilterMode(node.Path, isLocal ? Models.FilterType.LocalBranch : Models.FilterType.RemoteBranch);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
node.FilterMode = Models.FilterMode.None;
|
||||||
node.FilterMode = _settings.GetHistoriesFilterMode(node.Path, isLocal ? Models.FilterType.LocalBranchFolder : Models.FilterType.RemoteBranchFolder);
|
|
||||||
UpdateBranchTreeFilterMode(node.Children, isLocal);
|
if (!node.IsBranch)
|
||||||
}
|
UpdateBranchTreeFilterMode(node.Children, filters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTagFilterMode()
|
private void UpdateTagFilterMode(Dictionary<string, Models.FilterMode> filters)
|
||||||
{
|
{
|
||||||
foreach (var tag in _tags)
|
foreach (var tag in _tags)
|
||||||
tag.FilterMode = _settings.GetHistoriesFilterMode(tag.Name, Models.FilterType.Tag);
|
{
|
||||||
|
if (filters.TryGetValue(tag.Name, out var value))
|
||||||
|
tag.FilterMode = value;
|
||||||
|
else
|
||||||
|
tag.FilterMode = Models.FilterMode.None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetBranchTreeFilterMode(List<BranchTreeNode> nodes)
|
private void ResetBranchTreeFilterMode(List<BranchTreeNode> nodes)
|
||||||
|
|
|
@ -45,10 +45,43 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
FillEmptyLines();
|
FillEmptyLines();
|
||||||
|
|
||||||
|
ProcessChangeBlocks();
|
||||||
|
|
||||||
if (previous != null && previous.File == File)
|
if (previous != null && previous.File == File)
|
||||||
_syncScrollOffset = previous._syncScrollOffset;
|
_syncScrollOffset = previous._syncScrollOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Models.TextDiffChangeBlock> 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)
|
public void ConvertsToCombinedRange(Models.TextDiff combined, ref int startLine, ref int endLine, bool isOldSide)
|
||||||
{
|
{
|
||||||
endLine = Math.Min(endLine, combined.Lines.Count - 1);
|
endLine = Math.Min(endLine, combined.Lines.Count - 1);
|
||||||
|
|
|
@ -34,8 +34,31 @@
|
||||||
|
|
||||||
<!-- Toolbar Buttons -->
|
<!-- Toolbar Buttons -->
|
||||||
<StackPanel Grid.Column="3" Margin="8,0,0,0" Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Grid.Column="3" Margin="8,0,0,0" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<Button Classes="icon_button"
|
||||||
|
Width="28"
|
||||||
|
Click="OnGotoPrevChange"
|
||||||
|
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}"/>
|
||||||
|
</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"
|
||||||
|
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}"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button Classes="icon_button"
|
<Button Classes="icon_button"
|
||||||
Width="32"
|
Width="28"
|
||||||
Command="{Binding IncrUnified}"
|
Command="{Binding IncrUnified}"
|
||||||
IsVisible="{Binding IsTextDiff}"
|
IsVisible="{Binding IsTextDiff}"
|
||||||
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Incr}">
|
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Incr}">
|
||||||
|
@ -46,7 +69,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button Classes="icon_button"
|
<Button Classes="icon_button"
|
||||||
Width="32"
|
Width="28"
|
||||||
Command="{Binding DecrUnified}"
|
Command="{Binding DecrUnified}"
|
||||||
IsVisible="{Binding IsTextDiff}"
|
IsVisible="{Binding IsTextDiff}"
|
||||||
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Decr}">
|
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Decr}">
|
||||||
|
@ -60,9 +83,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ToggleButton Classes="line_path"
|
<ToggleButton Classes="line_path"
|
||||||
Width="32" Height="18"
|
Width="28"
|
||||||
Background="Transparent"
|
|
||||||
Padding="9,6"
|
|
||||||
Command="{Binding ToggleFullTextDiff}"
|
Command="{Binding ToggleFullTextDiff}"
|
||||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}"
|
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}"
|
||||||
IsVisible="{Binding IsTextDiff}"
|
IsVisible="{Binding IsTextDiff}"
|
||||||
|
@ -71,9 +92,8 @@
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
|
||||||
<ToggleButton Classes="line_path"
|
<ToggleButton Classes="line_path"
|
||||||
Width="32" Height="18"
|
Width="28"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Padding="9,6"
|
|
||||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting, Mode=TwoWay}"
|
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting, Mode=TwoWay}"
|
||||||
IsVisible="{Binding IsTextDiff}"
|
IsVisible="{Binding IsTextDiff}"
|
||||||
ToolTip.Tip="{DynamicResource Text.Diff.SyntaxHighlight}">
|
ToolTip.Tip="{DynamicResource Text.Diff.SyntaxHighlight}">
|
||||||
|
@ -81,9 +101,7 @@
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
|
||||||
<ToggleButton Classes="line_path"
|
<ToggleButton Classes="line_path"
|
||||||
Width="32" Height="18"
|
Width="28"
|
||||||
Background="Transparent"
|
|
||||||
Padding="9,6"
|
|
||||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap, Mode=TwoWay}"
|
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap, Mode=TwoWay}"
|
||||||
ToolTip.Tip="{DynamicResource Text.Diff.ToggleWordWrap}">
|
ToolTip.Tip="{DynamicResource Text.Diff.ToggleWordWrap}">
|
||||||
<ToggleButton.IsVisible>
|
<ToggleButton.IsVisible>
|
||||||
|
@ -97,14 +115,14 @@
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
|
||||||
<ToggleButton Classes="line_path"
|
<ToggleButton Classes="line_path"
|
||||||
Width="32"
|
Width="28"
|
||||||
IsChecked="{Binding IgnoreWhitespace, Mode=TwoWay}"
|
IsChecked="{Binding IgnoreWhitespace, Mode=TwoWay}"
|
||||||
ToolTip.Tip="{DynamicResource Text.Diff.IgnoreWhitespace}">
|
ToolTip.Tip="{DynamicResource Text.Diff.IgnoreWhitespace}">
|
||||||
<Path Width="14" Height="14" Stretch="Uniform" Data="{StaticResource Icons.Whitespace}"/>
|
<Path Width="14" Height="14" Stretch="Uniform" Data="{StaticResource Icons.Whitespace}"/>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
|
||||||
<ToggleButton Classes="line_path"
|
<ToggleButton Classes="line_path"
|
||||||
Width="32"
|
Width="28"
|
||||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView, Mode=TwoWay}"
|
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView, Mode=TwoWay}"
|
||||||
IsVisible="{Binding IsTextDiff}"
|
IsVisible="{Binding IsTextDiff}"
|
||||||
ToolTip.Tip="{DynamicResource Text.Diff.ShowHiddenSymbols}">
|
ToolTip.Tip="{DynamicResource Text.Diff.ShowHiddenSymbols}">
|
||||||
|
@ -112,16 +130,14 @@
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
|
||||||
<ToggleButton Classes="line_path"
|
<ToggleButton Classes="line_path"
|
||||||
Width="32" Height="18"
|
Width="28" Height="18"
|
||||||
Background="Transparent"
|
|
||||||
Padding="9,6"
|
|
||||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=TwoWay}"
|
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=TwoWay}"
|
||||||
IsVisible="{Binding IsTextDiff}"
|
IsVisible="{Binding IsTextDiff}"
|
||||||
ToolTip.Tip="{DynamicResource Text.Diff.SideBySide}">
|
ToolTip.Tip="{DynamicResource Text.Diff.SideBySide}">
|
||||||
<Path Width="12" Height="12" Data="{StaticResource Icons.LayoutHorizontal}" Margin="0,2,0,0"/>
|
<Path Width="12" Height="12" Data="{StaticResource Icons.LayoutHorizontal}" Margin="0,2,0,0"/>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
|
||||||
<Button Classes="icon_button" Width="32" Command="{Binding OpenExternalMergeTool}" ToolTip.Tip="{DynamicResource Text.Diff.UseMerger}">
|
<Button Classes="icon_button" Width="28" Command="{Binding OpenExternalMergeTool}" ToolTip.Tip="{DynamicResource Text.Diff.UseMerger}">
|
||||||
<Path Width="12" Height="12" Stretch="Uniform" Data="{StaticResource Icons.OpenWith}"/>
|
<Path Width="12" Height="12" Stretch="Uniform" Data="{StaticResource Icons.OpenWith}"/>
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
@ -232,7 +248,8 @@
|
||||||
<DataTemplate DataType="m:TextDiff">
|
<DataTemplate DataType="m:TextDiff">
|
||||||
<v:TextDiffView
|
<v:TextDiffView
|
||||||
UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"
|
UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"
|
||||||
UseFullTextDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}"/>
|
UseFullTextDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}"
|
||||||
|
CurrentChangeBlockIdx="{Binding CurrentChangeBlockIdx, Mode=OneWay}"/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<!-- Empty or only EOL changes -->
|
<!-- Empty or only EOL changes -->
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
namespace SourceGit.Views
|
namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
|
@ -8,5 +10,49 @@ namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
||||||
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
|
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
|
||||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||||
|
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}"
|
||||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
@ -49,6 +50,7 @@
|
||||||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
||||||
WordWrap="False"
|
WordWrap="False"
|
||||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||||
|
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}"
|
||||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
||||||
|
|
||||||
|
@ -70,6 +72,7 @@
|
||||||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
||||||
WordWrap="False"
|
WordWrap="False"
|
||||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||||
|
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}"
|
||||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
@ -243,6 +244,10 @@ namespace SourceGit.Views
|
||||||
if (_presenter.Document == null || !textView.VisualLinesValid)
|
if (_presenter.Document == null || !textView.VisualLinesValid)
|
||||||
return;
|
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 lines = _presenter.GetLines();
|
||||||
var width = textView.Bounds.Width;
|
var width = textView.Bounds.Width;
|
||||||
foreach (var line in textView.VisualLines)
|
foreach (var line in textView.VisualLines)
|
||||||
|
@ -255,51 +260,63 @@ namespace SourceGit.Views
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var info = lines[index - 1];
|
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 startY = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineTop) - textView.VerticalOffset;
|
||||||
var endY = line.GetTextLineVisualYPosition(line.TextLines[^1], VisualYPosition.LineBottom) - 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;
|
if (bg != null)
|
||||||
var processingIdxStart = 0;
|
drawingContext.DrawRectangle(bg, null, new Rect(0, startY, width, endY - startY));
|
||||||
var processingIdxEnd = 0;
|
|
||||||
var nextHightlight = 0;
|
|
||||||
|
|
||||||
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;
|
foreach (var tl in line.TextLines)
|
||||||
var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y;
|
|
||||||
|
|
||||||
while (nextHightlight < info.Highlights.Count)
|
|
||||||
{
|
{
|
||||||
var highlight = info.Highlights[nextHightlight];
|
processingIdxEnd += tl.Length;
|
||||||
if (highlight.Start >= processingIdxEnd)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var start = line.GetVisualColumn(highlight.Start < processingIdxStart ? processingIdxStart : highlight.Start);
|
var y = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) - textView.VerticalOffset;
|
||||||
var end = line.GetVisualColumn(highlight.End >= processingIdxEnd ? processingIdxEnd : highlight.End + 1);
|
var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y;
|
||||||
|
|
||||||
var x = line.GetTextLineVisualXPosition(tl, start) - textView.HorizontalOffset;
|
while (nextHighlight < info.Highlights.Count)
|
||||||
var w = line.GetTextLineVisualXPosition(tl, end) - textView.HorizontalOffset - x;
|
{
|
||||||
var rect = new Rect(x, y, w, h);
|
var highlight = info.Highlights[nextHighlight];
|
||||||
drawingContext.DrawRectangle(highlightBG, null, rect);
|
if (highlight.Start >= processingIdxEnd)
|
||||||
|
break;
|
||||||
|
|
||||||
if (highlight.End >= processingIdxEnd)
|
var start = line.GetVisualColumn(highlight.Start < processingIdxStart ? processingIdxStart : highlight.Start);
|
||||||
break;
|
var end = line.GetVisualColumn(highlight.End >= processingIdxEnd ? processingIdxEnd : highlight.End + 1);
|
||||||
|
|
||||||
nextHightlight++;
|
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);
|
set => SetValue(SelectedChunkProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<int> CurrentChangeBlockIdxProperty =
|
||||||
|
AvaloniaProperty.Register<ThemedTextDiffPresenter, int>(nameof(CurrentChangeBlockIdx));
|
||||||
|
|
||||||
|
public int CurrentChangeBlockIdx
|
||||||
|
{
|
||||||
|
get => GetValue(CurrentChangeBlockIdxProperty);
|
||||||
|
set => SetValue(CurrentChangeBlockIdxProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
protected override Type StyleKeyOverride => typeof(TextEditor);
|
protected override Type StyleKeyOverride => typeof(TextEditor);
|
||||||
|
|
||||||
public ThemedTextDiffPresenter(TextArea area, TextDocument doc) : base(area, doc)
|
public ThemedTextDiffPresenter(TextArea area, TextDocument doc) : base(area, doc)
|
||||||
|
@ -498,6 +524,127 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
public override void Render(DrawingContext context)
|
||||||
{
|
{
|
||||||
base.Render(context);
|
base.Render(context);
|
||||||
|
@ -895,6 +1042,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)
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnLoaded(e);
|
base.OnLoaded(e);
|
||||||
|
@ -968,6 +1125,12 @@ namespace SourceGit.Views
|
||||||
TextArea.LeftMargins.Add(new LineModifyTypeMargin());
|
TextArea.LeftMargins.Add(new LineModifyTypeMargin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ForceSyncScrollOffset()
|
||||||
|
{
|
||||||
|
if (DataContext is ViewModels.TwoSideTextDiff diff)
|
||||||
|
diff.SyncScrollOffset = _scrollViewer.Offset;
|
||||||
|
}
|
||||||
|
|
||||||
public override List<Models.TextDiffLine> GetLines()
|
public override List<Models.TextDiffLine> GetLines()
|
||||||
{
|
{
|
||||||
if (DataContext is ViewModels.TwoSideTextDiff diff)
|
if (DataContext is ViewModels.TwoSideTextDiff diff)
|
||||||
|
@ -1109,6 +1272,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)
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnLoaded(e);
|
base.OnLoaded(e);
|
||||||
|
@ -1241,6 +1414,15 @@ namespace SourceGit.Views
|
||||||
set => SetValue(EnableChunkSelectionProperty, value);
|
set => SetValue(EnableChunkSelectionProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<int> CurrentChangeBlockIdxProperty =
|
||||||
|
AvaloniaProperty.Register<TextDiffView, int>(nameof(CurrentChangeBlockIdx));
|
||||||
|
|
||||||
|
public int CurrentChangeBlockIdx
|
||||||
|
{
|
||||||
|
get => GetValue(CurrentChangeBlockIdxProperty);
|
||||||
|
set => SetValue(CurrentChangeBlockIdxProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
static TextDiffView()
|
static TextDiffView()
|
||||||
{
|
{
|
||||||
UseSideBySideDiffProperty.Changed.AddClassHandler<TextDiffView>((v, _) =>
|
UseSideBySideDiffProperty.Changed.AddClassHandler<TextDiffView>((v, _) =>
|
||||||
|
@ -1267,6 +1449,19 @@ namespace SourceGit.Views
|
||||||
v.Popup.Margin = new Thickness(0, top, right, 0);
|
v.Popup.Margin = new Thickness(0, top, right, 0);
|
||||||
v.Popup.IsVisible = true;
|
v.Popup.IsVisible = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CurrentChangeBlockIdxProperty.Changed.AddClassHandler<TextDiffView>((v, e) =>
|
||||||
|
{
|
||||||
|
if (v.Editor.Presenter != null)
|
||||||
|
{
|
||||||
|
foreach (var p in v.Editor.Presenter.GetVisualDescendants().OfType<ThemedTextDiffPresenter>())
|
||||||
|
{
|
||||||
|
p.JumpToChangeBlock((int)e.NewValue);
|
||||||
|
if (p is SingleSideTextDiffPresenter ssp)
|
||||||
|
ssp.ForceSyncScrollOffset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextDiffView()
|
public TextDiffView()
|
||||||
|
@ -1314,6 +1509,8 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
IsUnstagedChange = diff.Option.IsUnstaged;
|
IsUnstagedChange = diff.Option.IsUnstaged;
|
||||||
EnableChunkSelection = diff.Option.WorkingCopyChange != null;
|
EnableChunkSelection = diff.Option.WorkingCopyChange != null;
|
||||||
|
|
||||||
|
diff.CurrentChangeBlockIdx = -1; // Unset current change block.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStageChunk(object _1, RoutedEventArgs _2)
|
private void OnStageChunk(object _1, RoutedEventArgs _2)
|
||||||
|
|
Loading…
Reference in a new issue