feature: add auto complete box for searching commits by file path

This commit is contained in:
leo 2024-07-30 15:59:54 +08:00
parent addfb449cc
commit 7f8b8a19a0
No known key found for this signature in database
4 changed files with 235 additions and 35 deletions

View file

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

View file

@ -4,6 +4,7 @@ using System.IO;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
@ -170,8 +171,15 @@ namespace SourceGit.ViewModels
{ {
SearchedCommits = new List<Models.Commit>(); SearchedCommits = new List<Models.Commit>();
SearchCommitFilter = string.Empty; SearchCommitFilter = string.Empty;
SearchCommitFilterSuggestion.Clear();
IsSearchCommitSuggestionOpen = false;
_revisionFiles.Clear();
if (value) if (value)
{
SelectedViewIndex = 0; SelectedViewIndex = 0;
UpdateCurrentRevisionFilesForSearchSuggestion();
}
} }
} }
} }
@ -185,14 +193,58 @@ namespace SourceGit.ViewModels
public int SearchCommitFilterType public int SearchCommitFilterType
{ {
get => _searchCommitFilterType; get => _searchCommitFilterType;
set => SetProperty(ref _searchCommitFilterType, value); set
{
if (SetProperty(ref _searchCommitFilterType, value))
UpdateCurrentRevisionFilesForSearchSuggestion();
}
} }
public string SearchCommitFilter public string SearchCommitFilter
{ {
get => _searchCommitFilter; get => _searchCommitFilter;
set => SetProperty(ref _searchCommitFilter, value); set
{
if (SetProperty(ref _searchCommitFilter, value) &&
_searchCommitFilterType == 3 &&
!string.IsNullOrEmpty(value) &&
value.Length >= 2 &&
_revisionFiles.Count > 0)
{
var suggestion = new List<string>();
foreach (var file in _revisionFiles)
{
if (file.Contains(value, StringComparison.OrdinalIgnoreCase) && file.Length != value.Length)
{
suggestion.Add(file);
if (suggestion.Count > 100)
break;
} }
}
SearchCommitFilterSuggestion.Clear();
SearchCommitFilterSuggestion.AddRange(suggestion);
IsSearchCommitSuggestionOpen = SearchCommitFilterSuggestion.Count > 0;
}
else if (SearchCommitFilterSuggestion.Count > 0)
{
SearchCommitFilterSuggestion.Clear();
IsSearchCommitSuggestionOpen = false;
}
}
}
public bool IsSearchCommitSuggestionOpen
{
get => _isSearchCommitSuggestionOpen;
set => SetProperty(ref _isSearchCommitSuggestionOpen, value);
}
public AvaloniaList<string> SearchCommitFilterSuggestion
{
get;
private set;
} = new AvaloniaList<string>();
public List<Models.Commit> SearchedCommits public List<Models.Commit> SearchedCommits
{ {
@ -306,6 +358,9 @@ namespace SourceGit.ViewModels
_visibleTags.Clear(); _visibleTags.Clear();
_submodules.Clear(); _submodules.Clear();
_searchedCommits.Clear(); _searchedCommits.Clear();
_revisionFiles.Clear();
SearchCommitFilterSuggestion.Clear();
} }
public void RefreshAll() public void RefreshAll()
@ -450,6 +505,8 @@ namespace SourceGit.ViewModels
return; return;
IsSearchLoadingVisible = true; IsSearchLoadingVisible = true;
IsSearchCommitSuggestionOpen = false;
SearchCommitFilterSuggestion.Clear();
Task.Run(() => Task.Run(() =>
{ {
@ -1886,6 +1943,20 @@ namespace SourceGit.ViewModels
return visible; return visible;
} }
private void UpdateCurrentRevisionFilesForSearchSuggestion()
{
_revisionFiles.Clear();
if (_searchCommitFilterType == 3)
{
Task.Run(() =>
{
var files = new Commands.QueryCurrentRevisionFiles(_fullpath).Result();
Dispatcher.UIThread.Invoke(() => _revisionFiles.AddRange(files));
});
}
}
private string _fullpath = string.Empty; private string _fullpath = string.Empty;
private string _gitDir = string.Empty; private string _gitDir = string.Empty;
private Models.RepositorySettings _settings = null; private Models.RepositorySettings _settings = null;
@ -1899,9 +1970,11 @@ namespace SourceGit.ViewModels
private bool _isSearching = false; private bool _isSearching = false;
private bool _isSearchLoadingVisible = false; private bool _isSearchLoadingVisible = false;
private bool _isSearchCommitSuggestionOpen = false;
private int _searchCommitFilterType = 0; private int _searchCommitFilterType = 0;
private string _searchCommitFilter = string.Empty; private string _searchCommitFilter = string.Empty;
private List<Models.Commit> _searchedCommits = new List<Models.Commit>(); private List<Models.Commit> _searchedCommits = new List<Models.Commit>();
private List<string> _revisionFiles = new List<string>();
private bool _isLocalBranchGroupExpanded = true; private bool _isLocalBranchGroupExpanded = true;
private bool _isRemoteGroupExpanded = false; private bool _isRemoteGroupExpanded = false;

View file

@ -453,10 +453,10 @@
</Grid> </Grid>
<!-- Commit Search Panel --> <!-- Commit Search Panel -->
<Grid Grid.Row="1" RowDefinitions="Auto,32,*" Margin="8,0,4,8" IsVisible="{Binding IsSearching}" PropertyChanged="OnSearchCommitPanelPropertyChanged"> <Grid Grid.Row="1" RowDefinitions="24,32,*" Margin="8,0,4,8" IsVisible="{Binding IsSearching}" PropertyChanged="OnSearchCommitPanelPropertyChanged">
<!-- Search Input Box --> <!-- Search Input Box -->
<TextBox Grid.Row="0" <Grid Grid.Row="0">
x:Name="TxtSearchCommitsBox" <TextBox x:Name="TxtSearchCommitsBox"
Height="24" Height="24"
BorderThickness="1" BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}" BorderBrush="{DynamicResource Brush.Border2}"
@ -488,6 +488,58 @@
</TextBox.InnerRightContent> </TextBox.InnerRightContent>
</TextBox> </TextBox>
<Popup PlacementTarget="#TxtSearchCommitsBox"
Placement="BottomEdgeAlignedLeft"
HorizontalOffset="-8" VerticalAlignment="-8"
IsOpen="{Binding IsSearchCommitSuggestionOpen}">
<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 SearchCommitFilterSuggestion}"
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>
</Popup>
</Grid>
<Grid Grid.Row="1" ColumnDefinitions="Auto,*"> <Grid Grid.Row="1" ColumnDefinitions="Auto,*">
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
Text="{DynamicResource Text.Repository.Search.By}" Text="{DynamicResource Text.Repository.Search.By}"

View file

@ -29,11 +29,33 @@ namespace SourceGit.Views
private void OnSearchKeyDown(object _, KeyEventArgs e) private void OnSearchKeyDown(object _, KeyEventArgs e)
{ {
var repo = DataContext as ViewModels.Repository;
if (e.Key == Key.Enter) if (e.Key == Key.Enter)
{ {
if (DataContext is ViewModels.Repository repo && !string.IsNullOrWhiteSpace(repo.SearchCommitFilter)) if (!string.IsNullOrWhiteSpace(repo.SearchCommitFilter))
repo.StartSearchCommits(); repo.StartSearchCommits();
e.Handled = true;
}
else if (e.Key == Key.Down)
{
if (repo.IsSearchCommitSuggestionOpen)
{
SearchSuggestionBox.Focus(NavigationMethod.Tab);
SearchSuggestionBox.SelectedIndex = 0;
}
e.Handled = true;
}
else if (e.Key == Key.Escape)
{
if (repo.IsSearchCommitSuggestionOpen)
{
repo.SearchCommitFilterSuggestion.Clear();
repo.IsSearchCommitSuggestionOpen = false;
}
e.Handled = true; e.Handled = true;
} }
} }
@ -272,5 +294,37 @@ namespace SourceGit.Views
} }
} }
} }
private void OnSearchSuggestionBoxKeyDown(object sender, KeyEventArgs e)
{
var repo = DataContext as ViewModels.Repository;
if (e.Key == Key.Escape)
{
repo.IsSearchCommitSuggestionOpen = false;
repo.SearchCommitFilterSuggestion.Clear();
e.Handled = true;
}
else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content)
{
repo.SearchCommitFilter = content;
TxtSearchCommitsBox.CaretIndex = content.Length;
repo.StartSearchCommits();
e.Handled = true;
}
}
private void OnSearchSuggestionDoubleTapped(object sender, TappedEventArgs e)
{
var repo = DataContext as ViewModels.Repository;
var content = (sender as StackPanel)?.DataContext as string;
if (!string.IsNullOrEmpty(content))
{
repo.SearchCommitFilter = content;
TxtSearchCommitsBox.CaretIndex = content.Length;
repo.StartSearchCommits();
}
e.Handled = true;
}
} }
} }