mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2025-01-11 23:57:21 -08:00
feature: supports searching revision files (#775)
This commit is contained in:
parent
536f225867
commit
894f3e9b03
10 changed files with 350 additions and 15 deletions
|
@ -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 [];
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,68 @@ 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);
|
||||||
|
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 +202,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 +236,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 +609,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 +618,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 +786,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 +824,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}"/>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue