Compare commits

...

7 commits

Author SHA1 Message Date
goran-w
408ece148e Added ToggleTwoSideDiff(), so we can refresh change-block indicator 2024-11-17 23:24:04 +01:00
goran-w
11a02343a0 Added safeguards for edge cases 2024-11-17 22:41:25 +01:00
goran-w
aeea77078b Merge remote-tracking branch 'origin/develop' into diff-prev-next-change-616 2024-11-17 22:21:12 +01:00
leo
3b09ea45f5
feature: add change minimap for text diff view
Some checks failed
Continuous Integration / Build (push) Waiting to run
Continuous Integration / Prepare version string (push) Waiting to run
Continuous Integration / Package (push) Blocked by required conditions
Localization Check / localization-check (push) Has been cancelled
Signed-off-by: leo <longshuang@msn.cn>
2024-11-17 21:49:33 +08:00
github-actions[bot]
b7abf2ee50 doc: Update translation status and missing keys 2024-11-17 03:16:44 +00:00
Nils van Rijsinge
6f256f6f5b
Add and improve de_DE keys (#709)
* localization: add missing de_DE keys

added for #690

* localization: improve de_DE keys

- mostly code review suggestions from #664
- ClearAllCommitsFilter is not an action of deleting (löschen)
2024-11-17 11:16:31 +08:00
Dmitrij D. Czarkoff
5301645f8b
fix: in commit view get file histories by commit (#708)
When file histories are accessed from the commit details view, run git log for the inspected commit.  Previously the log was ran against current branch regardless whether the inspected commit belongs to that branch.
2024-11-17 11:14:56 +08:00
10 changed files with 245 additions and 85 deletions

View file

@ -47,7 +47,7 @@
## Translation Status
[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.57%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.14%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-98.42%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.14%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md)
[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-100.00%25-brightgreen)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.14%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-98.42%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.14%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md)
## How to Use

View file

@ -1,12 +1,10 @@
### de_DE.axaml: 99.57%
### de_DE.axaml: 100.00%
<details>
<summary>Missing Keys</summary>
- Text.Repository.FilterCommits.Default
- Text.Repository.FilterCommits.Exclude
- Text.Repository.FilterCommits.Include
</details>

View file

@ -58,7 +58,7 @@
<x:String x:Key="Text.BranchCM.DeleteMultiBranches" xml:space="preserve">Lösche alle ausgewählten {0} Branches</x:String>
<x:String x:Key="Text.BranchCM.DiscardAll" xml:space="preserve">Alle Änderungen verwerfen</x:String>
<x:String x:Key="Text.BranchCM.FastForward" xml:space="preserve">Fast-Forward zu ${0}$</x:String>
<x:String x:Key="Text.BranchCM.FetchInto" xml:space="preserve">Fetche ${0}$ nach ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.FetchInto" xml:space="preserve">Fetche ${0}$ in ${1}$ hinein...</x:String>
<x:String x:Key="Text.BranchCM.Finish" xml:space="preserve">Git Flow - Abschließen ${0}$</x:String>
<x:String x:Key="Text.BranchCM.Merge" xml:space="preserve">Merge ${0}$ in ${1}$ hinein...</x:String>
<x:String x:Key="Text.BranchCM.Pull" xml:space="preserve">Pull ${0}$</x:String>
@ -162,8 +162,8 @@
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">TICKETSYSTEM</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGithub" xml:space="preserve">Beispiel für Github-Regel hinzufügen</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleJira" xml:space="preserve">Beispiel für Jira-Regel hinzufügen</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGitLabIssue" xml:space="preserve">Beispiel für eine Gitlab Issue Regel einfügen</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGitLabMergeRequest" xml:space="preserve">Beispiel für einen Gitlab Merge Request einfügen</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGitLabIssue" xml:space="preserve">Beispiel für Gitlab Issue Regel einfügen</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGitLabMergeRequest" xml:space="preserve">Beispiel für Gitlab Merge Request einfügen</x:String>
<x:String x:Key="Text.Configure.IssueTracker.NewRule" xml:space="preserve">Neue Regel</x:String>
<x:String x:Key="Text.Configure.IssueTracker.Regex" xml:space="preserve">Ticketnummer Regex-Ausdruck:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">Name:</x:String>
@ -171,7 +171,7 @@
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">Verwende bitte $1, $2 um auf Regex-Gruppenwerte zuzugreifen.</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered" xml:space="preserve">Bevorzugter Service:</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">Wenn der 'Bevorzugte Service' aktiviert ist, wird SourceGit nur dieses Repository nutzen. Ansonsten wird, wenn mehrere Services verfügbar sind, eine Kontextmenü zur Auswahl angezeigt.</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">Der ausgewählte 'Bevorzugte Service' wird nur in diesem Repository gesetzt und verwendet. Wenn keiner gesetzt ist und mehrere Servies verfügbar sind wird ein Kontextmenü zur Auswahl angezeigt.</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP Proxy</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP Proxy für dieses Repository</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">Benutzername</x:String>
@ -265,7 +265,7 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Ziel:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Ausgewählte Gruppe bearbeiten</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Ausgewähltes Repository bearbeiten</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Führe benutzerte Aktion aus</x:String>
<x:String x:Key="Text.ExecuteCustomAction" xml:space="preserve">Führe benutzerdefinierte Aktion aus</x:String>
<x:String x:Key="Text.ExecuteCustomAction.Name" xml:space="preserve">Name der Aktion:</x:String>
<x:String x:Key="Text.FastForwardWithoutCheck" xml:space="preserve">Fast-Forward (ohne Auschecken)</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</x:String>
@ -535,7 +535,7 @@
<x:String x:Key="Text.Repository.AutoFetching" xml:space="preserve">Änderungen automatisch von Remote fetchen...</x:String>
<x:String x:Key="Text.Repository.Clean" xml:space="preserve">Aufräumen (GC &amp; Prune)</x:String>
<x:String x:Key="Text.Repository.CleanTips" xml:space="preserve">Führt `git gc` auf diesem Repository aus.</x:String>
<x:String x:Key="Text.Repository.ClearAllCommitsFilter" xml:space="preserve">Alles löschen</x:String>
<x:String x:Key="Text.Repository.ClearAllCommitsFilter" xml:space="preserve">Filter aufheben</x:String>
<x:String x:Key="Text.Repository.Configure" xml:space="preserve">Repository Einstellungen</x:String>
<x:String x:Key="Text.Repository.Continue" xml:space="preserve">WEITER</x:String>
<x:String x:Key="Text.Repository.CustomActions" xml:space="preserve">Benutzerdefinierte Aktionen</x:String>
@ -543,6 +543,9 @@
<x:String x:Key="Text.Repository.EnableReflog" xml:space="preserve">Aktiviere '--reflog' Option</x:String>
<x:String x:Key="Text.Repository.Explore" xml:space="preserve">Öffne im Datei-Browser</x:String>
<x:String x:Key="Text.Repository.Filter" xml:space="preserve">Suche Branches/Tags/Submodule</x:String>
<x:String x:Key="Text.Repository.FilterCommits.Default" xml:space="preserve">Aufheben</x:String>
<x:String x:Key="Text.Repository.FilterCommits.Exclude" xml:space="preserve">Im Graph ausblenden</x:String>
<x:String x:Key="Text.Repository.FilterCommits.Include" xml:space="preserve">Im Graph filtern</x:String>
<x:String x:Key="Text.Repository.LocalBranches" xml:space="preserve">LOKALE BRANCHES</x:String>
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">Zum HEAD wechseln</x:String>
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">Aktiviere '--first-parent' Option</x:String>
@ -601,7 +604,7 @@
<x:String x:Key="Text.Start" xml:space="preserve">START</x:String>
<x:String x:Key="Text.Stash" xml:space="preserve">Stash</x:String>
<x:String x:Key="Text.Stash.IncludeUntracked" xml:space="preserve">Inklusive nicht-verfolgter Dateien</x:String>
<x:String x:Key="Text.Stash.KeepIndex" xml:space="preserve">Behalte Dateien des Stages</x:String>
<x:String x:Key="Text.Stash.KeepIndex" xml:space="preserve">Behalte gestagte Dateien</x:String>
<x:String x:Key="Text.Stash.Message" xml:space="preserve">Name:</x:String>
<x:String x:Key="Text.Stash.Message.Placeholder" xml:space="preserve">Optional. Name dieses Stashes</x:String>
<x:String x:Key="Text.Stash.OnlyStagedChanges" xml:space="preserve">Nur gestagte Änderungen</x:String>

View file

@ -293,7 +293,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) };
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path, _commit.SHA) };
window.Show();
ev.Handled = true;
};
@ -434,7 +434,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path) };
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path, _commit.SHA) };
window.Show();
ev.Handled = true;
};

View file

@ -145,6 +145,12 @@ namespace SourceGit.ViewModels
LoadDiffContent();
}
public void ToggleTwoSideDiff()
{
Preference.Instance.UseSideBySideDiff = !Preference.Instance.UseSideBySideDiff;
RefreshChangeBlockIndicator();
}
public void OpenExternalMergeTool()
{
var toolType = Preference.Instance.ExternalMergeToolType;

View file

@ -57,14 +57,14 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _viewContent, value);
}
public FileHistories(Repository repo, string file)
public FileHistories(Repository repo, string file, string commit = null)
{
_repo = repo;
_file = file;
Task.Run(() =>
{
var commits = new Commands.QueryCommits(_repo.FullPath, $"-n 10000 -- \"{file}\"", false).Result();
var commits = new Commands.QueryCommits(_repo.FullPath, $"-n 10000 {commit} -- \"{file}\"", false).Result();
Dispatcher.UIThread.Invoke(() =>
{
IsLoading = false;

View file

@ -38,7 +38,7 @@ namespace SourceGit.Views
}
public static readonly StyledProperty<IBrush> BackgroundProperty =
AvaloniaProperty.Register<CommitRefsPresenter, IBrush>(nameof(Background), null);
AvaloniaProperty.Register<CommitRefsPresenter, IBrush>(nameof(Background), Brushes.Transparent);
public IBrush Background
{
@ -56,7 +56,7 @@ namespace SourceGit.Views
}
public static readonly StyledProperty<bool> UseGraphColorProperty =
AvaloniaProperty.Register<CommitRefsPresenter, bool>(nameof(UseGraphColor), false);
AvaloniaProperty.Register<CommitRefsPresenter, bool>(nameof(UseGraphColor));
public bool UseGraphColor
{
@ -96,7 +96,6 @@ namespace SourceGit.Views
var x = 1.0;
foreach (var item in _items)
{
var iconRect = new RoundedRect(new Rect(x, 0, 16, 16), new CornerRadius(2, 0, 0, 2));
var entireRect = new RoundedRect(new Rect(x, 0, item.Width, 16), new CornerRadius(2));
if (item.IsHead)

View file

@ -131,7 +131,8 @@
<ToggleButton Classes="line_path"
Width="28" Height="18"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=TwoWay}"
Command="{Binding ToggleTwoSideDiff}"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.SideBySide}">
<Path Width="12" Height="12" Data="{StaticResource Icons.LayoutHorizontal}" Margin="0,2,0,0"/>

View file

@ -13,7 +13,10 @@
<ContentControl x:Name="Editor">
<ContentControl.DataTemplates>
<DataTemplate DataType="m:TextDiff">
<v:CombinedTextDiffPresenter FileName="{Binding File}"
<Grid ColumnDefinitions="*,1,8">
<v:CombinedTextDiffPresenter Grid.Column="0"
x:Name="CombinedPresenter"
FileName="{Binding File}"
Foreground="{DynamicResource Brush.FG1}"
LineBrush="{DynamicResource Brush.Border2}"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
@ -30,11 +33,20 @@
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}"
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
<Rectangle Grid.Column="1" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
<v:TextDiffViewMinimap Grid.Column="2"
DisplayRange="{Binding #CombinedPresenter.DisplayRange}"
AddedLineBrush="{DynamicResource Brush.Diff.AddedBG}"
DeletedLineBrush="{DynamicResource Brush.Diff.DeletedBG}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="vm:TwoSideTextDiff">
<Grid ColumnDefinitions="*,1,*">
<Grid ColumnDefinitions="*,1,*,1,12">
<v:SingleSideTextDiffPresenter Grid.Column="0"
x:Name="LeftSidePresenter"
IsOld="True"
FileName="{Binding File}"
Foreground="{DynamicResource Brush.FG1}"
@ -75,6 +87,13 @@
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}"
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
<Rectangle Grid.Column="3" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
<v:TextDiffViewMinimap Grid.Column="4"
DisplayRange="{Binding #LeftSidePresenter.DisplayRange}"
AddedLineBrush="{DynamicResource Brush.Diff.AddedBG}"
DeletedLineBrush="{DynamicResource Brush.Diff.DeletedBG}"/>
</Grid>
</DataTemplate>
</ContentControl.DataTemplates>

View file

@ -46,6 +46,18 @@ namespace SourceGit.Views
}
}
public record TextDiffViewRange
{
public int StartIdx { get; set; } = 0;
public int EndIdx { get; set; } = 0;
public TextDiffViewRange(int startIdx, int endIdx)
{
StartIdx = startIdx;
EndIdx = endIdx;
}
}
public class ThemedTextDiffPresenter : TextEditor
{
public class VerticalSeperatorMargin : AbstractMargin
@ -211,7 +223,6 @@ namespace SourceGit.Views
if (presenter == null)
return new Size(0, 0);
var maxLineNumber = presenter.GetMaxLineNumber();
var typeface = TextView.CreateTypeface();
var test = new FormattedText(
$"-",
@ -483,6 +494,15 @@ namespace SourceGit.Views
set => SetValue(SelectedChunkProperty, value);
}
public static readonly StyledProperty<TextDiffViewRange> DisplayRangeProperty =
AvaloniaProperty.Register<ThemedTextDiffPresenter, TextDiffViewRange>(nameof(DisplayRange), new TextDiffViewRange(0, 0));
public TextDiffViewRange DisplayRange
{
get => GetValue(DisplayRangeProperty);
set => SetValue(DisplayRangeProperty, value);
}
public static readonly StyledProperty<int> CurrentChangeBlockIdxProperty =
AvaloniaProperty.Register<ThemedTextDiffPresenter, int>(nameof(CurrentChangeBlockIdx));
@ -526,25 +546,11 @@ 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 firstLineIdx = DisplayRange.StartIdx;
if (firstLineIdx <= 1)
return;
var lines = GetLines();
var firstLineType = lines[firstLineIdx].Type;
var prevLineType = lines[firstLineIdx - 1].Type;
var isChangeFirstLine = firstLineType != Models.TextDiffLineType.Normal && firstLineType != Models.TextDiffLineType.Indicator;
@ -583,22 +589,8 @@ namespace SourceGit.Views
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 lastLineIdx = DisplayRange.EndIdx;
if (lastLineIdx >= lines.Count - 1)
return;
@ -671,6 +663,7 @@ namespace SourceGit.Views
TextArea.TextView.PointerEntered += OnTextViewPointerChanged;
TextArea.TextView.PointerMoved += OnTextViewPointerChanged;
TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged;
TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged;
UpdateTextMate();
}
@ -683,6 +676,7 @@ namespace SourceGit.Views
TextArea.TextView.PointerEntered -= OnTextViewPointerChanged;
TextArea.TextView.PointerMoved -= OnTextViewPointerChanged;
TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged;
TextArea.TextView.VisualLinesChanged -= OnTextViewVisualLinesChanged;
if (_textMate != null)
{
@ -790,6 +784,34 @@ namespace SourceGit.Views
}
}
private void OnTextViewVisualLinesChanged(object sender, EventArgs e)
{
if (!TextArea.TextView.VisualLinesValid)
{
SetCurrentValue(DisplayRangeProperty, new TextDiffViewRange(0, 0));
return;
}
var lines = GetLines();
var start = int.MaxValue;
var count = 0;
foreach (var line in TextArea.TextView.VisualLines)
{
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
continue;
var index = line.FirstDocumentLine.LineNumber - 1;
if (index >= lines.Count)
continue;
count++;
if (start > index)
start = index;
}
SetCurrentValue(DisplayRangeProperty, new TextDiffViewRange(start, start + count));
}
protected void TrySetChunk(TextDiffViewChunk chunk)
{
var old = SelectedChunk;
@ -1107,14 +1129,10 @@ namespace SourceGit.Views
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
{
if (EnableChunkSelection && sender is ScrollViewer viewer)
{
var area = viewer.FindDescendantOfType<TextArea>();
if (!area.IsPointerOver)
if (EnableChunkSelection && !TextArea.IsPointerOver)
TrySetChunk(null);
}
}
}
public class SingleSideTextDiffPresenter : ThemedTextDiffPresenter
{
@ -1127,6 +1145,8 @@ namespace SourceGit.Views
public void ForceSyncScrollOffset()
{
if (_scrollViewer == null)
return;
if (DataContext is ViewModels.TwoSideTextDiff diff)
diff.SyncScrollOffset = _scrollViewer.Offset;
}
@ -1344,13 +1364,9 @@ namespace SourceGit.Views
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
{
if (EnableChunkSelection && sender is ScrollViewer viewer)
{
var area = viewer.FindDescendantOfType<TextArea>();
if (!area.IsPointerOver)
if (EnableChunkSelection && !TextArea.IsPointerOver)
TrySetChunk(null);
}
}
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
{
@ -1367,6 +1383,124 @@ namespace SourceGit.Views
private ScrollViewer _scrollViewer = null;
}
public class TextDiffViewMinimap : Control
{
public static readonly StyledProperty<IBrush> AddedLineBrushProperty =
AvaloniaProperty.Register<TextDiffViewMinimap, IBrush>(nameof(AddedLineBrush), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
public IBrush AddedLineBrush
{
get => GetValue(AddedLineBrushProperty);
set => SetValue(AddedLineBrushProperty, value);
}
public static readonly StyledProperty<IBrush> DeletedLineBrushProperty =
AvaloniaProperty.Register<TextDiffViewMinimap, IBrush>(nameof(DeletedLineBrush), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
public IBrush DeletedLineBrush
{
get => GetValue(DeletedLineBrushProperty);
set => SetValue(DeletedLineBrushProperty, value);
}
public static readonly StyledProperty<TextDiffViewRange> DisplayRangeProperty =
AvaloniaProperty.Register<TextDiffViewMinimap, TextDiffViewRange>(nameof(DisplayRange), new TextDiffViewRange(0, 0));
public TextDiffViewRange DisplayRange
{
get => GetValue(DisplayRangeProperty);
set => SetValue(DisplayRangeProperty, value);
}
public static readonly StyledProperty<Color> DisplayRangeColorProperty =
AvaloniaProperty.Register<TextDiffViewMinimap, Color>(nameof(DisplayRangeColor), Colors.RoyalBlue);
public Color DisplayRangeColor
{
get => GetValue(DisplayRangeColorProperty);
set => SetValue(DisplayRangeColorProperty, value);
}
static TextDiffViewMinimap()
{
AffectsRender<TextDiffViewMinimap>(
AddedLineBrushProperty,
DeletedLineBrushProperty,
DisplayRangeProperty,
DisplayRangeColorProperty);
}
public override void Render(DrawingContext context)
{
var total = 0;
if (DataContext is ViewModels.TwoSideTextDiff twoSideDiff)
{
var halfWidth = Bounds.Width * 0.5;
total = Math.Max(twoSideDiff.Old.Count, twoSideDiff.New.Count);
RenderSingleSide(context, twoSideDiff.Old, 0, halfWidth);
RenderSingleSide(context, twoSideDiff.New, halfWidth, halfWidth);
}
else if (DataContext is Models.TextDiff diff)
{
total = diff.Lines.Count;
RenderSingleSide(context, diff.Lines, 0, Bounds.Width);
}
var range = DisplayRange;
if (range.EndIdx == 0)
return;
var startY = range.StartIdx / (total * 1.0) * Bounds.Height;
var endY = range.EndIdx / (total * 1.0) * Bounds.Height;
var color = DisplayRangeColor;
var brush = new SolidColorBrush(color, 0.2);
var pen = new Pen(color.ToUInt32());
var rect = new Rect(0, startY, Bounds.Width, endY - startY);
context.DrawRectangle(brush, null, rect);
context.DrawLine(pen, rect.TopLeft, rect.TopRight);
context.DrawLine(pen, rect.BottomLeft, rect.BottomRight);
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
InvalidateVisual();
}
private void RenderSingleSide(DrawingContext context, List<Models.TextDiffLine> lines, double x, double width)
{
var total = lines.Count;
var lastLineType = Models.TextDiffLineType.Indicator;
var lastLineTypeStart = 0;
for (int i = 0; i < total; i++)
{
var line = lines[i];
if (line.Type != lastLineType)
{
RenderBlock(context, lastLineType, lastLineTypeStart, i - lastLineTypeStart, total, x, width);
lastLineType = line.Type;
lastLineTypeStart = i;
}
}
RenderBlock(context, lastLineType, lastLineTypeStart, total - lastLineTypeStart, total, x, width);
}
private void RenderBlock(DrawingContext context, Models.TextDiffLineType type, int start, int count, int total, double x, double width)
{
if (type == Models.TextDiffLineType.Added || type == Models.TextDiffLineType.Deleted)
{
var brush = type == Models.TextDiffLineType.Added ? AddedLineBrush : DeletedLineBrush;
var y = start / (total * 1.0) * Bounds.Height;
var h = count / (total * 1.0) * Bounds.Height;
context.DrawRectangle(brush, null, new Rect(x, y, width, h));
}
}
}
public partial class TextDiffView : UserControl
{
public static readonly StyledProperty<bool> UseSideBySideDiffProperty =
@ -1452,7 +1586,7 @@ namespace SourceGit.Views
CurrentChangeBlockIdxProperty.Changed.AddClassHandler<TextDiffView>((v, e) =>
{
if (v.Editor.Presenter != null)
if ((int)e.NewValue >= 0 && v.Editor.Presenter != null)
{
foreach (var p in v.Editor.Presenter.GetVisualDescendants().OfType<ThemedTextDiffPresenter>())
{
@ -1472,7 +1606,7 @@ namespace SourceGit.Views
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
RefreshContent(DataContext as Models.TextDiff, true);
RefreshContent(DataContext as Models.TextDiff);
}
protected override void OnPointerExited(PointerEventArgs e)