refactor: move searching commit code from Repository to SearchCommitContext and keep inputs remained while repo is opened (#1884)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2025-11-05 11:38:03 +08:00
parent 642f576c14
commit 75c667f7b2
5 changed files with 283 additions and 252 deletions

View File

@@ -157,14 +157,14 @@ namespace SourceGit.ViewModels
{
if (commits.Count == 0)
{
_repo.SelectedSearchedCommit = null;
_repo.SearchCommitContext.Selected = null;
DetailContext = null;
}
else if (commits.Count == 1)
{
var commit = (commits[0] as Models.Commit)!;
if (_repo.SelectedSearchedCommit == null || _repo.SelectedSearchedCommit.SHA != commit.SHA)
_repo.SelectedSearchedCommit = _repo.SearchedCommits.Find(x => x.SHA == commit.SHA);
if (_repo.SearchCommitContext.Selected == null || _repo.SearchCommitContext.Selected.SHA != commit.SHA)
_repo.SearchCommitContext.Selected = _repo.SearchCommitContext.Results?.Find(x => x.SHA == commit.SHA);
AutoSelectedCommit = commit;
NavigationId = _navigationId + 1;
@@ -182,7 +182,7 @@ namespace SourceGit.ViewModels
}
else if (commits.Count == 2)
{
_repo.SelectedSearchedCommit = null;
_repo.SearchCommitContext.Selected = null;
var end = commits[0] as Models.Commit;
var start = commits[1] as Models.Commit;
@@ -190,7 +190,7 @@ namespace SourceGit.ViewModels
}
else
{
_repo.SelectedSearchedCommit = null;
_repo.SearchCommitContext.Selected = null;
DetailContext = new Models.Count(commits.Count);
}
}
@@ -379,7 +379,7 @@ namespace SourceGit.ViewModels
var head = _commits.Find(x => x.IsCurrentHead);
if (head == null)
{
_repo.SelectedSearchedCommit = null;
_repo.SearchCommitContext.Selected = null;
head = await new Commands.QuerySingleCommit(_repo.FullPath, "HEAD").GetResultAsync();
if (head != null)
DetailContext = new RevisionCompare(_repo.FullPath, commit, head);

View File

@@ -271,83 +271,16 @@ namespace SourceGit.ViewModels
if (SetProperty(ref _isSearching, value))
{
if (value)
{
SelectedViewIndex = 0;
CalcWorktreeFilesForSearching();
}
else
{
SearchedCommits = new List<Models.Commit>();
SelectedSearchedCommit = null;
SearchCommitFilter = string.Empty;
MatchedFilesForSearching = null;
_requestingWorktreeFiles = false;
_worktreeFiles = null;
}
_searchCommitContext.EndSearch();
}
}
}
public bool IsSearchLoadingVisible
public SearchCommitContext SearchCommitContext
{
get => _isSearchLoadingVisible;
private set => SetProperty(ref _isSearchLoadingVisible, value);
}
public bool OnlySearchCommitsInCurrentBranch
{
get => _onlySearchCommitsInCurrentBranch;
set
{
if (SetProperty(ref _onlySearchCommitsInCurrentBranch, value) && !string.IsNullOrEmpty(_searchCommitFilter))
StartSearchCommits();
}
}
public int SearchCommitFilterType
{
get => _searchCommitFilterType;
set
{
if (SetProperty(ref _searchCommitFilterType, value))
{
CalcWorktreeFilesForSearching();
if (!string.IsNullOrEmpty(_searchCommitFilter))
StartSearchCommits();
}
}
}
public string SearchCommitFilter
{
get => _searchCommitFilter;
set
{
if (SetProperty(ref _searchCommitFilter, value) && IsSearchingCommitsByFilePath())
CalcMatchedFilesForSearching();
}
}
public List<string> MatchedFilesForSearching
{
get => _matchedFilesForSearching;
private set => SetProperty(ref _matchedFilesForSearching, value);
}
public List<Models.Commit> SearchedCommits
{
get => _searchedCommits;
set => SetProperty(ref _searchedCommits, value);
}
public Models.Commit SelectedSearchedCommit
{
get => _selectedSearchedCommit;
set
{
if (SetProperty(ref _selectedSearchedCommit, value) && value != null)
NavigateToCommit(value.SHA);
}
get => _searchCommitContext;
}
public bool IsLocalBranchGroupExpanded
@@ -562,6 +495,7 @@ namespace SourceGit.ViewModels
_histories = new Histories(this);
_workingCopy = new WorkingCopy(this) { CommitMessage = _settings.LastCommitMessage };
_stashesPage = new StashesPage(this);
_searchCommitContext = new SearchCommitContext(this);
if (Preferences.Instance.ShowLocalChangesByDefault)
{
@@ -633,6 +567,7 @@ namespace SourceGit.ViewModels
_histories.Dispose();
_workingCopy.Dispose();
_stashesPage.Dispose();
_searchCommitContext.Dispose();
_watcher = null;
_histories = null;
@@ -650,12 +585,6 @@ namespace SourceGit.ViewModels
_visibleTags = null;
_submodules.Clear();
_visibleSubmodules = null;
_searchedCommits.Clear();
_selectedSearchedCommit = null;
_requestingWorktreeFiles = false;
_worktreeFiles = null;
_matchedFilesForSearching = null;
}
public bool CanCreatePopup()
@@ -904,83 +833,6 @@ namespace SourceGit.ViewModels
Filter = string.Empty;
}
public void ClearSearchCommitFilter()
{
SearchCommitFilter = string.Empty;
}
public void ClearMatchedFilesForSearching()
{
MatchedFilesForSearching = null;
}
public void StartSearchCommits()
{
if (_histories == null)
return;
IsSearchLoadingVisible = true;
SelectedSearchedCommit = null;
MatchedFilesForSearching = null;
Task.Run(async () =>
{
var visible = new List<Models.Commit>();
var method = (Models.CommitSearchMethod)_searchCommitFilterType;
if (method == Models.CommitSearchMethod.BySHA)
{
var isCommitSHA = await new Commands.IsCommitSHA(FullPath, _searchCommitFilter)
.GetResultAsync()
.ConfigureAwait(false);
if (isCommitSHA)
{
var commit = await new Commands.QuerySingleCommit(FullPath, _searchCommitFilter)
.GetResultAsync()
.ConfigureAwait(false);
commit.IsMerged = await new Commands.IsAncestor(FullPath, commit.SHA, "HEAD")
.GetResultAsync()
.ConfigureAwait(false);
visible.Add(commit);
}
}
else if (_onlySearchCommitsInCurrentBranch)
{
visible = await new Commands.QueryCommits(FullPath, _searchCommitFilter, method, true)
.GetResultAsync()
.ConfigureAwait(false);
foreach (var c in visible)
c.IsMerged = true;
}
else
{
visible = await new Commands.QueryCommits(FullPath, _searchCommitFilter, method, false)
.GetResultAsync()
.ConfigureAwait(false);
if (visible.Count > 0)
{
var set = await new Commands.QueryCurrentBranchCommitHashes(FullPath, visible[^1].CommitterTime)
.GetResultAsync()
.ConfigureAwait(false);
foreach (var c in visible)
c.IsMerged = set.Contains(c.SHA);
}
}
Dispatcher.UIThread.Post(() =>
{
SearchedCommits = visible;
IsSearchLoadingVisible = false;
});
});
}
public IDisposable LockWatcher()
{
return _watcher?.Lock();
@@ -1559,7 +1411,7 @@ namespace SourceGit.ViewModels
{
if (_histories != null)
{
SelectedSearchedCommit = null;
_searchCommitContext.Selected = null;
var target = await new Commands.QuerySingleCommit(FullPath, branch.Head).GetResultAsync();
_histories.AutoSelectedCommit = null;
@@ -1956,65 +1808,6 @@ namespace SourceGit.ViewModels
return null;
}
private bool IsSearchingCommitsByFilePath()
{
return _isSearching && _searchCommitFilterType == (int)Models.CommitSearchMethod.ByPath;
}
private void CalcWorktreeFilesForSearching()
{
if (!IsSearchingCommitsByFilePath())
{
_requestingWorktreeFiles = false;
_worktreeFiles = null;
MatchedFilesForSearching = null;
GC.Collect();
return;
}
if (_requestingWorktreeFiles)
return;
_requestingWorktreeFiles = true;
Task.Run(async () =>
{
_worktreeFiles = await new Commands.QueryRevisionFileNames(FullPath, "HEAD")
.GetResultAsync()
.ConfigureAwait(false);
Dispatcher.UIThread.Post(() =>
{
if (IsSearchingCommitsByFilePath() && _requestingWorktreeFiles)
CalcMatchedFilesForSearching();
_requestingWorktreeFiles = false;
});
});
}
private void CalcMatchedFilesForSearching()
{
if (_worktreeFiles == null || _worktreeFiles.Count == 0 || _searchCommitFilter.Length < 3)
{
MatchedFilesForSearching = null;
return;
}
var matched = new List<string>();
foreach (var file in _worktreeFiles)
{
if (file.Contains(_searchCommitFilter, StringComparison.OrdinalIgnoreCase) && file.Length != _searchCommitFilter.Length)
{
matched.Add(file);
if (matched.Count > 100)
break;
}
}
MatchedFilesForSearching = matched;
}
private void FetchInBackground(object sender)
{
Dispatcher.UIThread.Invoke(async Task () =>
@@ -2081,15 +1874,7 @@ namespace SourceGit.ViewModels
private int _stashesCount = 0;
private bool _isSearching = false;
private bool _isSearchLoadingVisible = false;
private int _searchCommitFilterType = (int)Models.CommitSearchMethod.ByMessage;
private bool _onlySearchCommitsInCurrentBranch = false;
private string _searchCommitFilter = string.Empty;
private List<Models.Commit> _searchedCommits = [];
private Models.Commit _selectedSearchedCommit = null;
private bool _requestingWorktreeFiles = false;
private List<string> _worktreeFiles = null;
private List<string> _matchedFilesForSearching = null;
private SearchCommitContext _searchCommitContext = null;
private string _filter = string.Empty;
private List<Models.Remote> _remotes = [];

View File

@@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class SearchCommitContext : ObservableObject, IDisposable
{
public int Method
{
get => _method;
set
{
if (SetProperty(ref _method, value))
{
UpdateSuggestions();
StartSearch();
}
}
}
public string Filter
{
get => _filter;
set
{
if (SetProperty(ref _filter, value))
UpdateSuggestions();
}
}
public bool OnlySearchCurrentBranch
{
get => _onlySearchCurrentBranch;
set
{
if (SetProperty(ref _onlySearchCurrentBranch, value))
StartSearch();
}
}
public List<string> Suggestions
{
get => _suggestions;
private set => SetProperty(ref _suggestions, value);
}
public bool IsQuerying
{
get => _isQuerying;
private set => SetProperty(ref _isQuerying, value);
}
public List<Models.Commit> Results
{
get => _results;
private set => SetProperty(ref _results, value);
}
public Models.Commit Selected
{
get => _selected;
set
{
if (SetProperty(ref _selected, value) && value != null)
_repo.NavigateToCommit(value.SHA);
}
}
public SearchCommitContext(Repository repo)
{
_repo = repo;
}
public void Dispose()
{
_repo = null;
_suggestions?.Clear();
_results?.Clear();
_worktreeFiles?.Clear();
}
public void ClearFilter()
{
Filter = string.Empty;
}
public void ClearSuggestions()
{
Suggestions = null;
}
public void StartSearch()
{
Results = null;
Selected = null;
Suggestions = null;
if (string.IsNullOrEmpty(_filter))
return;
IsQuerying = true;
Task.Run(async () =>
{
var result = new List<Models.Commit>();
var method = (Models.CommitSearchMethod)_method;
var repoPath = _repo.FullPath;
if (method == Models.CommitSearchMethod.BySHA)
{
var isCommitSHA = await new Commands.IsCommitSHA(repoPath, _filter)
.GetResultAsync()
.ConfigureAwait(false);
if (isCommitSHA)
{
var commit = await new Commands.QuerySingleCommit(repoPath, _filter)
.GetResultAsync()
.ConfigureAwait(false);
commit.IsMerged = await new Commands.IsAncestor(repoPath, commit.SHA, "HEAD")
.GetResultAsync()
.ConfigureAwait(false);
result.Add(commit);
}
}
else if (_onlySearchCurrentBranch)
{
result = await new Commands.QueryCommits(repoPath, _filter, method, true)
.GetResultAsync()
.ConfigureAwait(false);
foreach (var c in result)
c.IsMerged = true;
}
else
{
result = await new Commands.QueryCommits(repoPath, _filter, method, false)
.GetResultAsync()
.ConfigureAwait(false);
if (result.Count > 0)
{
var set = await new Commands.QueryCurrentBranchCommitHashes(repoPath, result[^1].CommitterTime)
.GetResultAsync()
.ConfigureAwait(false);
foreach (var c in result)
c.IsMerged = set.Contains(c.SHA);
}
}
Dispatcher.UIThread.Post(() =>
{
IsQuerying = false;
if (_repo.IsSearching)
Results = result;
});
});
}
public void EndSearch()
{
_worktreeFiles = null;
Suggestions = null;
Results = null;
GC.Collect();
}
private void UpdateSuggestions()
{
if (_method != (int)Models.CommitSearchMethod.ByPath || _requestingWorktreeFiles)
{
Suggestions = null;
return;
}
if (_worktreeFiles == null)
{
_requestingWorktreeFiles = true;
Task.Run(async () =>
{
var files = await new Commands.QueryRevisionFileNames(_repo.FullPath, "HEAD")
.GetResultAsync()
.ConfigureAwait(false);
Dispatcher.UIThread.Post(() =>
{
_requestingWorktreeFiles = false;
if (_repo.IsSearching)
{
_worktreeFiles = files;
UpdateSuggestions();
}
});
});
return;
}
if (_worktreeFiles.Count == 0 || _filter.Length < 3)
{
Suggestions = null;
return;
}
var matched = new List<string>();
foreach (var file in _worktreeFiles)
{
if (file.Contains(_filter, StringComparison.OrdinalIgnoreCase) && file.Length != _filter.Length)
{
matched.Add(file);
if (matched.Count > 100)
break;
}
}
Suggestions = matched;
}
private Repository _repo = null;
private int _method = (int)Models.CommitSearchMethod.ByMessage;
private string _filter = string.Empty;
private bool _onlySearchCurrentBranch = false;
private List<string> _suggestions = null;
private bool _isQuerying = false;
private List<Models.Commit> _results = null;
private Models.Commit _selected = null;
private bool _requestingWorktreeFiles = false;
private List<string> _worktreeFiles = null;
}
}

View File

@@ -447,7 +447,7 @@
Background="{DynamicResource Brush.Contents}"
CornerRadius="12"
Watermark="{DynamicResource Text.Repository.Search}"
Text="{Binding SearchCommitFilter, Mode=TwoWay}"
Text="{Binding SearchCommitContext.Filter, Mode=TwoWay}"
VerticalContentAlignment="Center"
KeyDown="OnSearchKeyDown">
<TextBox.InnerLeftContent>
@@ -461,8 +461,8 @@
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Command="{Binding ClearSearchCommitFilter}"
IsVisible="{Binding SearchCommitFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Click="OnClearSearchCommitFilter"
IsVisible="{Binding SearchCommitContext.Filter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
@@ -477,7 +477,7 @@
HorizontalOffset="-8" VerticalAlignment="-8">
<Popup.IsOpen>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="MatchedFilesForSearching" Converter="{x:Static c:ListConverters.IsNotNullOrEmpty}"/>
<Binding Path="SearchCommitContext.Suggestions" Converter="{x:Static c:ListConverters.IsNotNullOrEmpty}"/>
<Binding Path="$parent[Window].IsActive"/>
</MultiBinding>
</Popup.IsOpen>
@@ -487,7 +487,7 @@
<ListBox x:Name="SearchSuggestionBox"
Background="Transparent"
SelectionMode="Single"
ItemsSource="{Binding MatchedFilesForSearching}"
ItemsSource="{Binding SearchCommitContext.Suggestions}"
MaxHeight="400"
Focusable="True"
KeyDown="OnSearchSuggestionBoxKeyDown">
@@ -509,7 +509,7 @@
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
@@ -535,7 +535,7 @@
Padding="4,0"
Background="Transparent"
BorderThickness="0"
SelectedIndex="{Binding SearchCommitFilterType, Mode=TwoWay}">
SelectedIndex="{Binding SearchCommitContext.Method, Mode=TwoWay}">
<ComboBox.Items>
<TextBlock Text="{DynamicResource Text.Repository.Search.BySHA}"/>
<TextBlock Text="{DynamicResource Text.Repository.Search.ByAuthor}"/>
@@ -548,8 +548,8 @@
<CheckBox Height="24"
Margin="4,0,0,0"
IsChecked="{Binding OnlySearchCommitsInCurrentBranch, Mode=TwoWay}"
IsVisible="{Binding SearchCommitFilterType, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
IsChecked="{Binding SearchCommitContext.OnlySearchCurrentBranch, Mode=TwoWay}"
IsVisible="{Binding SearchCommitContext.Method, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
<TextBlock Text="{DynamicResource Text.Repository.Search.InCurrentBranch}"/>
</CheckBox>
</StackPanel>
@@ -557,9 +557,9 @@
<ListBox Grid.Row="2"
Margin="0,8,0,0"
Padding="4"
ItemsSource="{Binding SearchedCommits}"
ItemsSource="{Binding SearchCommitContext.Results}"
SelectionMode="Single"
SelectedItem="{Binding SelectedSearchedCommit, Mode=TwoWay}"
SelectedItem="{Binding SearchCommitContext.Selected, Mode=TwoWay}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"
Background="{DynamicResource Brush.Contents}"
@@ -613,8 +613,8 @@
Fill="{DynamicResource Brush.FG2}">
<Path.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="SearchedCommits.Count" Converter="{x:Static c:IntConverters.IsZero}"/>
<Binding Path="IsSearchLoadingVisible" Converter="{x:Static BoolConverters.Not}"/>
<Binding Path="SearchCommitContext.Results" Converter="{x:Static c:ListConverters.IsNullOrEmpty}"/>
<Binding Path="SearchCommitContext.IsQuerying" Converter="{x:Static BoolConverters.Not}"/>
</MultiBinding>
</Path.IsVisible>
</Path>
@@ -622,7 +622,7 @@
<v:LoadingIcon Grid.Row="2"
Width="48" Height="48"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding IsSearchLoadingVisible}"/>
IsVisible="{Binding SearchCommitContext.IsQuerying}"/>
</Grid>
</Grid>

View File

@@ -33,14 +33,12 @@ namespace SourceGit.Views
if (e.Key == Key.Enter)
{
if (!string.IsNullOrWhiteSpace(repo.SearchCommitFilter))
repo.StartSearchCommits();
repo.SearchCommitContext.StartSearch();
e.Handled = true;
}
else if (e.Key == Key.Down)
{
if (repo.MatchedFilesForSearching is { Count: > 0 })
if (repo.SearchCommitContext.Suggestions is { Count: > 0 })
{
SearchSuggestionBox.Focus(NavigationMethod.Tab);
SearchSuggestionBox.SelectedIndex = 0;
@@ -50,11 +48,20 @@ namespace SourceGit.Views
}
else if (e.Key == Key.Escape)
{
repo.ClearMatchedFilesForSearching();
repo.SearchCommitContext.ClearSuggestions();
e.Handled = true;
}
}
private void OnClearSearchCommitFilter(object _, RoutedEventArgs e)
{
if (DataContext is not ViewModels.Repository repo)
return;
repo.SearchCommitContext.ClearFilter();
e.Handled = true;
}
private void OnBranchTreeRowsChanged(object _, RoutedEventArgs e)
{
UpdateLeftSidebarLayout();
@@ -310,14 +317,14 @@ namespace SourceGit.Views
if (e.Key == Key.Escape)
{
repo.ClearMatchedFilesForSearching();
repo.SearchCommitContext.ClearSuggestions();
e.Handled = true;
}
else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content)
{
repo.SearchCommitFilter = content;
repo.SearchCommitContext.Filter = content;
TxtSearchCommitsBox.CaretIndex = content.Length;
repo.StartSearchCommits();
repo.SearchCommitContext.StartSearch();
e.Handled = true;
}
}
@@ -330,9 +337,9 @@ namespace SourceGit.Views
var content = (sender as StackPanel)?.DataContext as string;
if (!string.IsNullOrEmpty(content))
{
repo.SearchCommitFilter = content;
repo.SearchCommitContext.Filter = content;
TxtSearchCommitsBox.CaretIndex = content.Length;
repo.StartSearchCommits();
repo.SearchCommitContext.StartSearch();
}
e.Handled = true;
}