Compare commits

...

9 commits

Author SHA1 Message Date
leo
d1a1b4b2b9
enhance: do NOT show search suggestion if input string is empty (#775)
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
2024-12-02 21:51:05 +08:00
github-actions[bot]
a52977baf3 doc: Update translation status and missing keys 2024-12-02 13:44:50 +00:00
leo
894f3e9b03
feature: supports searching revision files (#775) 2024-12-02 21:44:15 +08:00
leo
536f225867
feature: allow using Amend while rebasing (#773) 2024-12-02 10:38:40 +08:00
leo
f9c55a94c9
Merge branch 'master' into develop 2024-12-02 09:34:19 +08:00
leo
790e1f6c4b
Merge branch 'release/v8.41' 2024-12-02 09:31:46 +08:00
leo
dc5cbd05ef
version: 8.41 2024-12-02 09:31:38 +08:00
github-actions[bot]
e5ba097141 doc: Update translation status and missing keys 2024-12-02 01:27:03 +00:00
AquariusStar
d4c5306333
localiztion: update (#772) 2024-12-02 09:26:49 +08:00
15 changed files with 366 additions and 24 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.86%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.87%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.30%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.73%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.15%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.86%25-yellow)](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-99.72%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.73%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.17%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.59%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.01%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.86%25-yellow)](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,19 +1,21 @@
### de_DE.axaml: 99.86% ### de_DE.axaml: 99.72%
<details> <details>
<summary>Missing Keys</summary> <summary>Missing Keys</summary>
- Text.CommitDetail.Files.Search
- Text.WorkingCopy.CommitToEdit - Text.WorkingCopy.CommitToEdit
</details> </details>
### es_ES.axaml: 97.87% ### es_ES.axaml: 97.73%
<details> <details>
<summary>Missing Keys</summary> <summary>Missing Keys</summary>
- Text.CommitDetail.Files.Search
- Text.CommitDetail.Info.Children - Text.CommitDetail.Info.Children
- Text.Fetch.Force - Text.Fetch.Force
- Text.Preference.Appearance.FontSize - Text.Preference.Appearance.FontSize
@ -32,7 +34,7 @@
</details> </details>
### fr_FR.axaml: 97.30% ### fr_FR.axaml: 97.17%
<details> <details>
@ -41,6 +43,7 @@
- Text.CherryPick.AppendSourceToMessage - Text.CherryPick.AppendSourceToMessage
- Text.CherryPick.Mainline.Tips - Text.CherryPick.Mainline.Tips
- Text.CommitCM.CherryPickMultiple - Text.CommitCM.CherryPickMultiple
- Text.CommitDetail.Files.Search
- Text.Fetch.Force - Text.Fetch.Force
- Text.Preference.Appearance.FontSize - Text.Preference.Appearance.FontSize
- Text.Preference.Appearance.FontSize.Default - Text.Preference.Appearance.FontSize.Default
@ -60,12 +63,13 @@
</details> </details>
### it_IT.axaml: 97.73% ### it_IT.axaml: 97.59%
<details> <details>
<summary>Missing Keys</summary> <summary>Missing Keys</summary>
- Text.CommitDetail.Files.Search
- Text.CommitDetail.Info.Children - Text.CommitDetail.Info.Children
- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest - Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
- Text.Configure.OpenAI.Preferred - Text.Configure.OpenAI.Preferred
@ -85,12 +89,13 @@
</details> </details>
### pt_BR.axaml: 99.15% ### pt_BR.axaml: 99.01%
<details> <details>
<summary>Missing Keys</summary> <summary>Missing Keys</summary>
- Text.CommitDetail.Files.Search
- Text.CommitDetail.Info.Children - Text.CommitDetail.Info.Children
- Text.Fetch.Force - Text.Fetch.Force
- Text.Preference.General.ShowChildren - Text.Preference.General.ShowChildren
@ -106,7 +111,7 @@
<details> <details>
<summary>Missing Keys</summary> <summary>Missing Keys</summary>
- Text.WorkingCopy.CommitToEdit - Text.CommitDetail.Files.Search
</details> </details>

View file

@ -1 +1 @@
8.40 8.41

View file

@ -1,19 +1,19 @@
namespace SourceGit.Commands namespace SourceGit.Commands
{ {
public class QueryCurrentRevisionFiles : Command public class QueryRevisionFileNames : Command
{ {
public QueryCurrentRevisionFiles(string repo) public QueryRevisionFileNames(string repo, string revision)
{ {
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
Args = "ls-tree -r --name-only HEAD"; Args = $"ls-tree -r -z --name-only {revision}";
} }
public string[] Result() public string[] Result()
{ {
var rs = ReadToEnd(); var rs = ReadToEnd();
if (rs.IsSuccess) if (rs.IsSuccess)
return rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries); return rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
return []; return [];
} }

View file

@ -12,7 +12,7 @@ namespace SourceGit.Commands
{ {
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
Args = $"ls-tree {sha}"; Args = $"ls-tree -z {sha}";
if (!string.IsNullOrEmpty(parentFolder)) if (!string.IsNullOrEmpty(parentFolder))
Args += $" -- \"{parentFolder}\""; Args += $" -- \"{parentFolder}\"";
@ -20,11 +20,27 @@ namespace SourceGit.Commands
public List<Models.Object> Result() public List<Models.Object> Result()
{ {
Exec(); var rs = ReadToEnd();
if (rs.IsSuccess)
{
var start = 0;
var end = rs.StdOut.IndexOf('\0', start);
while (end > 0)
{
var line = rs.StdOut.Substring(start, end - start);
Parse(line);
start = end + 1;
end = rs.StdOut.IndexOf('\0', start);
}
if (start < rs.StdOut.Length)
Parse(rs.StdOut.Substring(start));
}
return _objects; return _objects;
} }
protected override void OnReadline(string line) private void Parse(string line)
{ {
var match = REG_FORMAT().Match(line); var match = REG_FORMAT().Match(line);
if (!match.Success) if (!match.Success)

View file

@ -121,6 +121,7 @@
<x:String x:Key="Text.CommitDetail.Changes.Search" xml:space="preserve">Search Changes...</x:String> <x:String x:Key="Text.CommitDetail.Changes.Search" xml:space="preserve">Search Changes...</x:String>
<x:String x:Key="Text.CommitDetail.Files" xml:space="preserve">FILES</x:String> <x:String x:Key="Text.CommitDetail.Files" xml:space="preserve">FILES</x:String>
<x:String x:Key="Text.CommitDetail.Files.LFS" xml:space="preserve">LFS File</x:String> <x:String x:Key="Text.CommitDetail.Files.LFS" xml:space="preserve">LFS File</x:String>
<x:String x:Key="Text.CommitDetail.Files.Search" xml:space="preserve">Search Files...</x:String>
<x:String x:Key="Text.CommitDetail.Files.Submodule" xml:space="preserve">Submodule</x:String> <x:String x:Key="Text.CommitDetail.Files.Submodule" xml:space="preserve">Submodule</x:String>
<x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">INFORMATION</x:String> <x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">INFORMATION</x:String>
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">AUTHOR</x:String> <x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">AUTHOR</x:String>

View file

@ -685,6 +685,7 @@
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">ЗАФИКСИРОВАТЬ и ОТПРАВИТЬ</x:String> <x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">ЗАФИКСИРОВАТЬ и ОТПРАВИТЬ</x:String>
<x:String x:Key="Text.WorkingCopy.CommitMessageHelper" xml:space="preserve">Шаблон/Истории</x:String> <x:String x:Key="Text.WorkingCopy.CommitMessageHelper" xml:space="preserve">Шаблон/Истории</x:String>
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">Запустить событие щелчка</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">Запустить событие щелчка</x:String>
<x:String x:Key="Text.WorkingCopy.CommitToEdit" xml:space="preserve">Зафиксировать (Редактировать)</x:String>
<x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">Подготовить все изменения и зафиксировать</x:String> <x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">Подготовить все изменения и зафиксировать</x:String>
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithoutFiles" xml:space="preserve">Обнаружена пустая фиксация! Вы хотите продолжить (--allow-empty)?</x:String> <x:String x:Key="Text.WorkingCopy.ConfirmCommitWithoutFiles" xml:space="preserve">Обнаружена пустая фиксация! Вы хотите продолжить (--allow-empty)?</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">ОБНАРУЖЕНЫ КОНФЛИКТЫ</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">ОБНАРУЖЕНЫ КОНФЛИКТЫ</x:String>

View file

@ -124,6 +124,7 @@
<x:String x:Key="Text.CommitDetail.Changes.Search" xml:space="preserve">查找变更...</x:String> <x:String x:Key="Text.CommitDetail.Changes.Search" xml:space="preserve">查找变更...</x:String>
<x:String x:Key="Text.CommitDetail.Files" xml:space="preserve">文件列表</x:String> <x:String x:Key="Text.CommitDetail.Files" xml:space="preserve">文件列表</x:String>
<x:String x:Key="Text.CommitDetail.Files.LFS" xml:space="preserve">LFS文件</x:String> <x:String x:Key="Text.CommitDetail.Files.LFS" xml:space="preserve">LFS文件</x:String>
<x:String x:Key="Text.CommitDetail.Files.Search" xml:space="preserve">查找文件...</x:String>
<x:String x:Key="Text.CommitDetail.Files.Submodule" xml:space="preserve">子模块</x:String> <x:String x:Key="Text.CommitDetail.Files.Submodule" xml:space="preserve">子模块</x:String>
<x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">基本信息</x:String> <x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">基本信息</x:String>
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">修改者</x:String> <x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">修改者</x:String>

View file

@ -124,6 +124,7 @@
<x:String x:Key="Text.CommitDetail.Changes.Search" xml:space="preserve">搜尋變更...</x:String> <x:String x:Key="Text.CommitDetail.Changes.Search" xml:space="preserve">搜尋變更...</x:String>
<x:String x:Key="Text.CommitDetail.Files" xml:space="preserve">檔案列表</x:String> <x:String x:Key="Text.CommitDetail.Files" xml:space="preserve">檔案列表</x:String>
<x:String x:Key="Text.CommitDetail.Files.LFS" xml:space="preserve">LFS 檔案</x:String> <x:String x:Key="Text.CommitDetail.Files.LFS" xml:space="preserve">LFS 檔案</x:String>
<x:String x:Key="Text.CommitDetail.Files.Search" xml:space="preserve">搜尋檔案...</x:String>
<x:String x:Key="Text.CommitDetail.Files.Submodule" xml:space="preserve">子模組</x:String> <x:String x:Key="Text.CommitDetail.Files.Submodule" xml:space="preserve">子模組</x:String>
<x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">基本資訊</x:String> <x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">基本資訊</x:String>
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">作者</x:String> <x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">作者</x:String>

View file

@ -82,7 +82,7 @@ namespace SourceGit.ViewModels
{ {
get; get;
private set; private set;
} = new AvaloniaList<string>(); } = [];
public string SearchChangeFilter public string SearchChangeFilter
{ {
@ -106,13 +106,70 @@ namespace SourceGit.ViewModels
{ {
get; get;
private set; private set;
} = new AvaloniaList<Models.CommitLink>(); } = [];
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{ {
get => _repo.Settings?.IssueTrackerRules; get => _repo.Settings?.IssueTrackerRules;
} }
public string RevisionFileSearchFilter
{
get => _revisionFileSearchFilter;
set
{
if (SetProperty(ref _revisionFileSearchFilter, value))
{
RevisionFileSearchSuggestion.Clear();
if (!string.IsNullOrEmpty(value))
{
if (_revisionFiles.Count == 0)
{
var sha = Commit.SHA;
Task.Run(() =>
{
var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result();
Dispatcher.UIThread.Invoke(() => {
if (sha == Commit.SHA)
{
_revisionFiles.Clear();
_revisionFiles.AddRange(files);
if (!string.IsNullOrEmpty(_revisionFileSearchFilter))
UpdateRevisionFileSearchSuggestion();
}
});
});
}
else
{
UpdateRevisionFileSearchSuggestion();
}
}
else
{
IsRevisionFileSearchSuggestionOpen = false;
GC.Collect();
}
}
}
}
public AvaloniaList<string> RevisionFileSearchSuggestion
{
get;
private set;
} = [];
public bool IsRevisionFileSearchSuggestionOpen
{
get => _isRevisionFileSearchSuggestionOpen;
set => SetProperty(ref _isRevisionFileSearchSuggestionOpen, value);
}
public CommitDetail(Repository repo) public CommitDetail(Repository repo)
{ {
_repo = repo; _repo = repo;
@ -147,17 +204,23 @@ namespace SourceGit.ViewModels
{ {
_repo = null; _repo = null;
_commit = null; _commit = null;
if (_changes != null) if (_changes != null)
_changes.Clear(); _changes.Clear();
if (_visibleChanges != null) if (_visibleChanges != null)
_visibleChanges.Clear(); _visibleChanges.Clear();
if (_selectedChanges != null) if (_selectedChanges != null)
_selectedChanges.Clear(); _selectedChanges.Clear();
_signInfo = null; _signInfo = null;
_searchChangeFilter = null; _searchChangeFilter = null;
_diffContext = null; _diffContext = null;
_viewRevisionFileContent = null; _viewRevisionFileContent = null;
_cancelToken = null; _cancelToken = null;
WebLinks.Clear();
_revisionFiles.Clear();
RevisionFileSearchSuggestion.Clear();
} }
public void NavigateTo(string commitSHA) public void NavigateTo(string commitSHA)
@ -175,6 +238,11 @@ namespace SourceGit.ViewModels
SearchChangeFilter = string.Empty; SearchChangeFilter = string.Empty;
} }
public void ClearRevisionFileSearchFilter()
{
RevisionFileSearchFilter = string.Empty;
}
public Models.Commit GetParent(string sha) public Models.Commit GetParent(string sha)
{ {
return new Commands.QuerySingleCommit(_repo.FullPath, sha).Result(); return new Commands.QuerySingleCommit(_repo.FullPath, sha).Result();
@ -543,6 +611,8 @@ namespace SourceGit.ViewModels
private void Refresh() private void Refresh()
{ {
_changes = null; _changes = null;
_revisionFiles.Clear();
FullMessage = string.Empty; FullMessage = string.Empty;
SignInfo = null; SignInfo = null;
Changes = []; Changes = [];
@ -550,6 +620,8 @@ namespace SourceGit.ViewModels
SelectedChanges = null; SelectedChanges = null;
ViewRevisionFileContent = null; ViewRevisionFileContent = null;
Children.Clear(); Children.Clear();
RevisionFileSearchFilter = string.Empty;
IsRevisionFileSearchSuggestionOpen = false;
if (_commit == null) if (_commit == null)
return; return;
@ -716,6 +788,24 @@ namespace SourceGit.ViewModels
menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(new MenuItem() { Header = "-" });
} }
private void UpdateRevisionFileSearchSuggestion()
{
var suggestion = new List<string>();
foreach (var file in _revisionFiles)
{
if (file.Contains(_revisionFileSearchFilter, StringComparison.OrdinalIgnoreCase) &&
file.Length != _revisionFileSearchFilter.Length)
suggestion.Add(file);
if (suggestion.Count >= 100)
break;
}
RevisionFileSearchSuggestion.Clear();
RevisionFileSearchSuggestion.AddRange(suggestion);
IsRevisionFileSearchSuggestionOpen = suggestion.Count > 0;
}
[GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")] [GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")]
private static partial Regex REG_LFS_FORMAT(); private static partial Regex REG_LFS_FORMAT();
@ -736,5 +826,8 @@ namespace SourceGit.ViewModels
private DiffContext _diffContext = null; private DiffContext _diffContext = null;
private object _viewRevisionFileContent = null; private object _viewRevisionFileContent = null;
private Commands.Command.CancelToken _cancelToken = null; private Commands.Command.CancelToken _cancelToken = null;
private List<string> _revisionFiles = [];
private string _revisionFileSearchFilter = string.Empty;
private bool _isRevisionFileSearchSuggestionOpen = false;
} }
} }

View file

@ -2147,7 +2147,7 @@ namespace SourceGit.ViewModels
{ {
Task.Run(() => Task.Run(() =>
{ {
var files = new Commands.QueryCurrentRevisionFiles(_fullpath).Result(); var files = new Commands.QueryRevisionFileNames(_fullpath, "HEAD").Result();
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.Invoke(() =>
{ {
if (_searchCommitFilterType != 3) if (_searchCommitFilterType != 3)

View file

@ -144,6 +144,68 @@ namespace SourceGit.Views
InitializeComponent(); InitializeComponent();
} }
public void SetSearchResult(string file)
{
_rows.Clear();
_searchResult.Clear();
var rows = new List<ViewModels.RevisionFileTreeNode>();
if (string.IsNullOrEmpty(file))
{
MakeRows(rows, _tree, 0);
}
else
{
var vm = DataContext as ViewModels.CommitDetail;
if (vm == null || vm.Commit == null)
return;
var objects = vm.GetRevisionFilesUnderFolder(file);
if (objects == null || objects.Count != 1)
return;
var routes = file.Split('/', StringSplitOptions.None);
if (routes.Length == 1)
{
_searchResult.Add(new ViewModels.RevisionFileTreeNode
{
Backend = objects[0]
});
}
else
{
var last = _searchResult;
var prefix = string.Empty;
for (var i = 0; i < routes.Length - 1; i++)
{
var folder = new ViewModels.RevisionFileTreeNode
{
Backend = new Models.Object
{
Type = Models.ObjectType.Tree,
Path = prefix + routes[i],
},
IsExpanded = true,
};
last.Add(folder);
last = folder.Children;
prefix = folder.Backend + "/";
}
last.Add(new ViewModels.RevisionFileTreeNode
{
Backend = objects[0]
});
}
MakeRows(rows, _searchResult, 0);
}
_rows.AddRange(rows);
GC.Collect();
}
public void ToggleNodeIsExpanded(ViewModels.RevisionFileTreeNode node) public void ToggleNodeIsExpanded(ViewModels.RevisionFileTreeNode node)
{ {
_disableSelectionChangingEvent = true; _disableSelectionChangingEvent = true;
@ -189,6 +251,7 @@ namespace SourceGit.Views
{ {
_tree.Clear(); _tree.Clear();
_rows.Clear(); _rows.Clear();
_searchResult.Clear();
var vm = DataContext as ViewModels.CommitDetail; var vm = DataContext as ViewModels.CommitDetail;
if (vm == null || vm.Commit == null) if (vm == null || vm.Commit == null)
@ -308,5 +371,6 @@ namespace SourceGit.Views
private List<ViewModels.RevisionFileTreeNode> _tree = []; private List<ViewModels.RevisionFileTreeNode> _tree = [];
private AvaloniaList<ViewModels.RevisionFileTreeNode> _rows = []; private AvaloniaList<ViewModels.RevisionFileTreeNode> _rows = [];
private bool _disableSelectionChangingEvent = false; private bool _disableSelectionChangingEvent = false;
private List<ViewModels.RevisionFileTreeNode> _searchResult = [];
} }
} }

View file

@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels" xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views" xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.RevisionFiles" x:Class="SourceGit.Views.RevisionFiles"
x:DataType="vm:CommitDetail"> x:DataType="vm:CommitDetail">
@ -14,17 +15,96 @@
<ColumnDefinition Width="*" MinWidth="100"/> <ColumnDefinition Width="*" MinWidth="100"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- File Tree --> <!-- Left -->
<Border Grid.Column="0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}"> <Grid Grid.Column="0" RowDefinitions="26,*">
<v:RevisionFileTreeView Revision="{Binding Commit.SHA}"/> <Grid Grid.Row="0" Height="26">
<TextBox Grid.Row="0"
x:Name="TxtSearchRevisionFiles"
Height="26"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
Background="Transparent"
CornerRadius="4"
Watermark="{DynamicResource Text.CommitDetail.Files.Search}"
Text="{Binding RevisionFileSearchFilter, Mode=TwoWay}"
KeyDown="OnSearchBoxKeyDown"
TextChanged="OnSearchBoxTextChanged">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14" Margin="4,0,0,0" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
IsVisible="{Binding RevisionFileSearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Command="{Binding ClearRevisionFileSearchFilter}">
<Path Width="14" Height="14" Fill="{DynamicResource Brush.FG2}" Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<Popup PlacementTarget="{Binding #TxtSearchRevisionFiles}"
Placement="BottomEdgeAlignedLeft"
HorizontalOffset="-8" VerticalAlignment="-8"
IsOpen="{Binding IsRevisionFileSearchSuggestionOpen}">
<Border Margin="8" VerticalAlignment="Top" Effect="drop-shadow(0 0 8 #80000000)">
<Border Background="{DynamicResource Brush.Popup}" CornerRadius="4" Padding="4" BorderThickness="0.65" BorderBrush="{DynamicResource Brush.Accent}">
<ListBox x:Name="SearchSuggestionBox"
Background="Transparent"
SelectionMode="Single"
ItemsSource="{Binding RevisionFileSearchSuggestion}"
MaxHeight="400"
Focusable="True"
KeyDown="OnSearchSuggestionBoxKeyDown">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="MinHeight" Value="26"/>
<Setter Property="CornerRadius" Value="4"/>
</Style>
<Style Selector="ListBox">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Grid/>
</FocusAdornerTemplate>
</Setter>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="x:String">
<StackPanel Background="Transparent" Orientation="Vertical" Margin="8,4" DoubleTapped="OnSearchSuggestionDoubleTapped">
<StackPanel Orientation="Horizontal">
<Path Width="12" Height="12" Data="{StaticResource Icons.File}"/>
<TextBlock Classes="primary" Margin="6,0,0,0" Text="{Binding Converter={x:Static c:PathConverters.PureFileName}}"/>
</StackPanel>
<TextBlock Classes="primary" FontSize="12" Margin="18,2,0,0" Foreground="{DynamicResource Brush.FG2}" Text="{Binding Converter={x:Static c:PathConverters.PureDirectoryName}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border> </Border>
</Border>
</Popup>
</Grid>
<!-- File Tree -->
<Border Grid.Row="1" Margin="0,4,0,0" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1" Background="{DynamicResource Brush.Contents}">
<v:RevisionFileTreeView x:Name="FileTree" Revision="{Binding Commit.SHA}"/>
</Border>
</Grid>
<GridSplitter Grid.Column="1" <GridSplitter Grid.Column="1"
MinWidth="1" MinWidth="1"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Background="Transparent"/> Background="Transparent"/>
<!-- File Content Viewer --> <!-- Right: File Content Viewer -->
<Grid Grid.Column="2"> <Grid Grid.Column="2">
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"> <Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
<v:RevisionFileContentViewer Content="{Binding ViewRevisionFileContent}"/> <v:RevisionFileContentViewer Content="{Binding ViewRevisionFileContent}"/>

View file

@ -3,6 +3,7 @@ using System;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
@ -118,5 +119,85 @@ namespace SourceGit.Views
{ {
InitializeComponent(); InitializeComponent();
} }
private void OnSearchBoxKeyDown(object _, KeyEventArgs e)
{
var vm = DataContext as ViewModels.CommitDetail;
if (vm == null)
return;
if (e.Key == Key.Enter)
{
FileTree.SetSearchResult(vm.RevisionFileSearchFilter);
e.Handled = true;
}
else if (e.Key == Key.Down || e.Key == Key.Up)
{
if (vm.IsRevisionFileSearchSuggestionOpen)
{
SearchSuggestionBox.Focus(NavigationMethod.Tab);
SearchSuggestionBox.SelectedIndex = 0;
}
e.Handled = true;
}
else if (e.Key == Key.Escape)
{
if (vm.IsRevisionFileSearchSuggestionOpen)
{
vm.RevisionFileSearchSuggestion.Clear();
vm.IsRevisionFileSearchSuggestionOpen = false;
}
e.Handled = true;
}
}
private void OnSearchBoxTextChanged(object _, TextChangedEventArgs e)
{
var vm = DataContext as ViewModels.CommitDetail;
if (vm == null)
return;
if (string.IsNullOrEmpty(vm.RevisionFileSearchFilter))
FileTree.SetSearchResult(null);
}
private void OnSearchSuggestionBoxKeyDown(object _, KeyEventArgs e)
{
var vm = DataContext as ViewModels.CommitDetail;
if (vm == null)
return;
if (e.Key == Key.Escape)
{
vm.RevisionFileSearchSuggestion.Clear();
e.Handled = true;
}
else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content)
{
vm.RevisionFileSearchFilter = content;
TxtSearchRevisionFiles.CaretIndex = content.Length;
FileTree.SetSearchResult(vm.RevisionFileSearchFilter);
e.Handled = true;
}
}
private void OnSearchSuggestionDoubleTapped(object sender, TappedEventArgs e)
{
var vm = DataContext as ViewModels.CommitDetail;
if (vm == null)
return;
var content = (sender as StackPanel)?.DataContext as string;
if (!string.IsNullOrEmpty(content))
{
vm.RevisionFileSearchFilter = content;
TxtSearchRevisionFiles.CaretIndex = content.Length;
FileTree.SetSearchResult(vm.RevisionFileSearchFilter);
}
e.Handled = true;
}
} }
} }

View file

@ -239,7 +239,6 @@
Margin="8,0,0,0" Margin="8,0,0,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
IsChecked="{Binding UseAmend, Mode=TwoWay}" IsChecked="{Binding UseAmend, Mode=TwoWay}"
IsVisible="{Binding InProgressContext, Converter={x:Static ObjectConverters.IsNull}}"
Content="{DynamicResource Text.WorkingCopy.Amend}"/> Content="{DynamicResource Text.WorkingCopy.Amend}"/>
<v:LoadingIcon Grid.Column="5" Width="18" Height="18" IsVisible="{Binding IsCommitting}"/> <v:LoadingIcon Grid.Column="5" Width="18" Height="18" IsVisible="{Binding IsCommitting}"/>