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 ## 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 ## How to Use

View file

@ -1,12 +1,10 @@
### de_DE.axaml: 99.57% ### de_DE.axaml: 100.00%
<details> <details>
<summary>Missing Keys</summary> <summary>Missing Keys</summary>
- Text.Repository.FilterCommits.Default
- Text.Repository.FilterCommits.Exclude
- Text.Repository.FilterCommits.Include
</details> </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.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.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.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.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.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> <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" 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.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.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.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 einen Gitlab Merge Request 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.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.Regex" xml:space="preserve">Ticketnummer Regex-Ausdruck:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">Name:</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.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" 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" 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" 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.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> <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.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.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.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.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.FastForwardWithoutCheck" xml:space="preserve">Fast-Forward (ohne Auschecken)</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</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.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.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.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.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.Continue" xml:space="preserve">WEITER</x:String>
<x:String x:Key="Text.Repository.CustomActions" xml:space="preserve">Benutzerdefinierte Aktionen</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.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.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.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.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.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> <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.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" 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.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" 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.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> <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.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) => 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(); window.Show();
ev.Handled = true; ev.Handled = true;
}; };
@ -434,7 +434,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories"); history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) => 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(); window.Show();
ev.Handled = true; ev.Handled = true;
}; };

View file

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

View file

@ -57,14 +57,14 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _viewContent, value); private set => SetProperty(ref _viewContent, value);
} }
public FileHistories(Repository repo, string file) public FileHistories(Repository repo, string file, string commit = null)
{ {
_repo = repo; _repo = repo;
_file = file; _file = file;
Task.Run(() => 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(() => Dispatcher.UIThread.Invoke(() =>
{ {
IsLoading = false; IsLoading = false;

View file

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

View file

@ -131,7 +131,8 @@
<ToggleButton Classes="line_path" <ToggleButton Classes="line_path"
Width="28" Height="18" 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}" 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"/>

View file

@ -13,28 +13,40 @@
<ContentControl x:Name="Editor"> <ContentControl x:Name="Editor">
<ContentControl.DataTemplates> <ContentControl.DataTemplates>
<DataTemplate DataType="m:TextDiff"> <DataTemplate DataType="m:TextDiff">
<v:CombinedTextDiffPresenter FileName="{Binding File}" <Grid ColumnDefinitions="*,1,8">
Foreground="{DynamicResource Brush.FG1}" <v:CombinedTextDiffPresenter Grid.Column="0"
LineBrush="{DynamicResource Brush.Border2}" x:Name="CombinedPresenter"
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}" FileName="{Binding File}"
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}" Foreground="{DynamicResource Brush.FG1}"
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}" LineBrush="{DynamicResource Brush.Border2}"
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}" EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}" AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
IndicatorForeground="{DynamicResource Brush.FG2}" DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
FontFamily="{DynamicResource Fonts.Monospace}" AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=EditorFontSize}" DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}" IndicatorForeground="{DynamicResource Brush.FG2}"
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}" FontFamily="{DynamicResource Fonts.Monospace}"
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=EditorFontSize}"
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}" UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}" WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/> ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
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>
<DataTemplate DataType="vm:TwoSideTextDiff"> <DataTemplate DataType="vm:TwoSideTextDiff">
<Grid ColumnDefinitions="*,1,*"> <Grid ColumnDefinitions="*,1,*,1,12">
<v:SingleSideTextDiffPresenter Grid.Column="0" <v:SingleSideTextDiffPresenter Grid.Column="0"
x:Name="LeftSidePresenter"
IsOld="True" IsOld="True"
FileName="{Binding File}" FileName="{Binding File}"
Foreground="{DynamicResource Brush.FG1}" Foreground="{DynamicResource Brush.FG1}"
@ -75,6 +87,13 @@
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}" CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}"
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}" EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/> 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> </Grid>
</DataTemplate> </DataTemplate>
</ContentControl.DataTemplates> </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 ThemedTextDiffPresenter : TextEditor
{ {
public class VerticalSeperatorMargin : AbstractMargin public class VerticalSeperatorMargin : AbstractMargin
@ -211,7 +223,6 @@ namespace SourceGit.Views
if (presenter == null) if (presenter == null)
return new Size(0, 0); return new Size(0, 0);
var maxLineNumber = presenter.GetMaxLineNumber();
var typeface = TextView.CreateTypeface(); var typeface = TextView.CreateTypeface();
var test = new FormattedText( var test = new FormattedText(
$"-", $"-",
@ -483,6 +494,15 @@ namespace SourceGit.Views
set => SetValue(SelectedChunkProperty, value); 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 = public static readonly StyledProperty<int> CurrentChangeBlockIdxProperty =
AvaloniaProperty.Register<ThemedTextDiffPresenter, int>(nameof(CurrentChangeBlockIdx)); AvaloniaProperty.Register<ThemedTextDiffPresenter, int>(nameof(CurrentChangeBlockIdx));
@ -526,25 +546,11 @@ namespace SourceGit.Views
public void GotoPrevChange() public void GotoPrevChange()
{ {
var view = TextArea.TextView; var firstLineIdx = DisplayRange.StartIdx;
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) if (firstLineIdx <= 1)
return; return;
var lines = GetLines();
var firstLineType = lines[firstLineIdx].Type; var firstLineType = lines[firstLineIdx].Type;
var prevLineType = lines[firstLineIdx - 1].Type; var prevLineType = lines[firstLineIdx - 1].Type;
var isChangeFirstLine = firstLineType != Models.TextDiffLineType.Normal && firstLineType != Models.TextDiffLineType.Indicator; var isChangeFirstLine = firstLineType != Models.TextDiffLineType.Normal && firstLineType != Models.TextDiffLineType.Indicator;
@ -583,22 +589,8 @@ namespace SourceGit.Views
public void GotoNextChange() public void GotoNextChange()
{ {
var view = TextArea.TextView;
var lines = GetLines(); var lines = GetLines();
var lastLineIdx = -1; var lastLineIdx = DisplayRange.EndIdx;
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) if (lastLineIdx >= lines.Count - 1)
return; return;
@ -671,6 +663,7 @@ namespace SourceGit.Views
TextArea.TextView.PointerEntered += OnTextViewPointerChanged; TextArea.TextView.PointerEntered += OnTextViewPointerChanged;
TextArea.TextView.PointerMoved += OnTextViewPointerChanged; TextArea.TextView.PointerMoved += OnTextViewPointerChanged;
TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged; TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged;
TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged;
UpdateTextMate(); UpdateTextMate();
} }
@ -683,6 +676,7 @@ namespace SourceGit.Views
TextArea.TextView.PointerEntered -= OnTextViewPointerChanged; TextArea.TextView.PointerEntered -= OnTextViewPointerChanged;
TextArea.TextView.PointerMoved -= OnTextViewPointerChanged; TextArea.TextView.PointerMoved -= OnTextViewPointerChanged;
TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged; TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged;
TextArea.TextView.VisualLinesChanged -= OnTextViewVisualLinesChanged;
if (_textMate != null) 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) protected void TrySetChunk(TextDiffViewChunk chunk)
{ {
var old = SelectedChunk; var old = SelectedChunk;
@ -1107,12 +1129,8 @@ namespace SourceGit.Views
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e) private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
{ {
if (EnableChunkSelection && sender is ScrollViewer viewer) if (EnableChunkSelection && !TextArea.IsPointerOver)
{ TrySetChunk(null);
var area = viewer.FindDescendantOfType<TextArea>();
if (!area.IsPointerOver)
TrySetChunk(null);
}
} }
} }
@ -1127,6 +1145,8 @@ namespace SourceGit.Views
public void ForceSyncScrollOffset() public void ForceSyncScrollOffset()
{ {
if (_scrollViewer == null)
return;
if (DataContext is ViewModels.TwoSideTextDiff diff) if (DataContext is ViewModels.TwoSideTextDiff diff)
diff.SyncScrollOffset = _scrollViewer.Offset; diff.SyncScrollOffset = _scrollViewer.Offset;
} }
@ -1344,12 +1364,8 @@ namespace SourceGit.Views
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e) private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
{ {
if (EnableChunkSelection && sender is ScrollViewer viewer) if (EnableChunkSelection && !TextArea.IsPointerOver)
{ TrySetChunk(null);
var area = viewer.FindDescendantOfType<TextArea>();
if (!area.IsPointerOver)
TrySetChunk(null);
}
} }
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e) private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
@ -1367,6 +1383,124 @@ namespace SourceGit.Views
private ScrollViewer _scrollViewer = null; 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 partial class TextDiffView : UserControl
{ {
public static readonly StyledProperty<bool> UseSideBySideDiffProperty = public static readonly StyledProperty<bool> UseSideBySideDiffProperty =
@ -1452,7 +1586,7 @@ namespace SourceGit.Views
CurrentChangeBlockIdxProperty.Changed.AddClassHandler<TextDiffView>((v, e) => 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>()) foreach (var p in v.Editor.Presenter.GetVisualDescendants().OfType<ThemedTextDiffPresenter>())
{ {
@ -1472,7 +1606,7 @@ namespace SourceGit.Views
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
base.OnDataContextChanged(e); base.OnDataContextChanged(e);
RefreshContent(DataContext as Models.TextDiff, true); RefreshContent(DataContext as Models.TextDiff);
} }
protected override void OnPointerExited(PointerEventArgs e) protected override void OnPointerExited(PointerEventArgs e)