feature: add hotkey Ctrl+Shift+P/⌘+⇧+P to open repository command palette

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2025-10-27 20:22:46 +08:00
parent ca544d9763
commit ef36c50881
18 changed files with 900 additions and 6 deletions

View File

@@ -21,6 +21,7 @@
<StreamGeometry x:Key="Icons.Close">M523 398 918 3l113 113-396 396 397 397-113 113-397-397-397 397-113-113 397-397L14 116l113-113 396 396z</StreamGeometry>
<StreamGeometry x:Key="Icons.Code">M853 102H171C133 102 102 133 102 171v683C102 891 133 922 171 922h683C891 922 922 891 922 853V171C922 133 891 102 853 102zM390 600l-48 48L205 512l137-137 48 48L301 512l88 88zM465 819l-66-18L559 205l66 18L465 819zm218-171L634 600 723 512l-88-88 48-48L819 512 683 649z</StreamGeometry>
<StreamGeometry x:Key="Icons.ColorPicker">M128 854h768v86H128zM390 797c13 13 29 19 48 19s35-6 45-19l291-288c26-22 26-64 0-90L435 83l-61 61L426 192l-272 269c-22 22-22 64 0 90l237 246zm93-544 211 211-32 32H240l243-243zM707 694c0 48 38 86 86 86 48 0 86-38 86-86 0-22-10-45-26-61L794 576l-61 61c-13 13-26 35-26 58z</StreamGeometry>
<StreamGeometry x:Key="Icons.Command">M325 312l-60 60L404 513 265 652l60 60 200-200L325 312zm194 345h236v97h-236v-97zM29 77v870h968V77H29zm870 774H125V173h774v678z</StreamGeometry>
<StreamGeometry x:Key="Icons.Commit">M0 512M1024 512M512 0M512 1024M796 471A292 292 0 00512 256a293 293 0 00-284 215H0v144h228A293 293 0 00512 832a291 291 0 00284-217H1024V471h-228M512 688A146 146 0 01366 544A145 145 0 01512 400c80 0 146 63 146 144A146 146 0 01512 688</StreamGeometry>
<StreamGeometry x:Key="Icons.CommitMessageGenerator">M796 561a5 5 0 014 7l-39 90a5 5 0 004 7h100a5 5 0 014 8l-178 247a5 5 0 01-9-4l32-148a5 5 0 00-5-6h-89a5 5 0 01-4-7l86-191a5 5 0 014-3h88zM731 122a73 73 0 0173 73v318a54 54 0 00-8-1H731V195H244v634h408l-16 73H244a73 73 0 01-73-73V195a73 73 0 0173-73h488zm-219 366v73h-195v-73h195zm146-146v73H317v-73h341z</StreamGeometry>
<StreamGeometry x:Key="Icons.Compare">M645 448l64 64 220-221L704 64l-64 64 115 115H128v90h628zM375 576l-64-64-220 224L314 960l64-64-116-115H896v-90H262z</StreamGeometry>

View File

@@ -477,6 +477,7 @@
<x:String x:Key="Text.Hotkeys.Repo.CommitWithAutoStage" xml:space="preserve">Stage all changes and commit</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Fetch" xml:space="preserve">Fetch, starts directly</x:String>
<x:String x:Key="Text.Hotkeys.Repo.GoHome" xml:space="preserve">Dashboard mode (Default)</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenCommandPalette" xml:space="preserve">Open command palette</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">Commit search mode</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Pull" xml:space="preserve">Pull, starts directly</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Push" xml:space="preserve">Push, starts directly</x:String>
@@ -510,6 +511,7 @@
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">Target Branch:</x:String>
<x:String x:Key="Text.IssueLinkCM.CopyLink" xml:space="preserve">Copy Link</x:String>
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">Open in Browser</x:String>
<x:String x:Key="Text.Launcher.Commands" xml:space="preserve">Commands</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ERROR</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">NOTICE</x:String>
<x:String x:Key="Text.Launcher.OpenRepository" xml:space="preserve">Open Repositories</x:String>

View File

@@ -481,6 +481,7 @@
<x:String x:Key="Text.Hotkeys.Repo.CommitWithAutoStage" xml:space="preserve">自动暂存全部变更并提交</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Fetch" xml:space="preserve">拉取 (fetch) 远程变更</x:String>
<x:String x:Key="Text.Hotkeys.Repo.GoHome" xml:space="preserve">切换左边栏为分支/标签等显示模式(默认)</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenCommandPalette" xml:space="preserve">打开快捷命令面板</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">切换左边栏为提交搜索模式</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Pull" xml:space="preserve">拉回 (pull) 远程变更</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Push" xml:space="preserve">推送本地变更到远程</x:String>
@@ -514,6 +515,7 @@
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">目标分支 </x:String>
<x:String x:Key="Text.IssueLinkCM.CopyLink" xml:space="preserve">复制链接地址</x:String>
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">在浏览器中访问</x:String>
<x:String x:Key="Text.Launcher.Commands" xml:space="preserve">命令列表</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">出错了</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系统提示</x:String>
<x:String x:Key="Text.Launcher.OpenRepository" xml:space="preserve">打开其他仓库</x:String>

View File

@@ -481,6 +481,7 @@
<x:String x:Key="Text.Hotkeys.Repo.CommitWithAutoStage" xml:space="preserve">自動暫存全部變更並提交</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Fetch" xml:space="preserve">提取 (fetch) 遠端的變更</x:String>
<x:String x:Key="Text.Hotkeys.Repo.GoHome" xml:space="preserve">切換左邊欄為分支/標籤等顯示模式 (預設)</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenCommandPalette" xml:space="preserve">開啟命令面板</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">切換左邊欄為歷史搜尋模式</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Pull" xml:space="preserve">拉取 (pull) 遠端的變更</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Push" xml:space="preserve">推送 (push) 本機變更到遠端存放庫</x:String>
@@ -514,6 +515,7 @@
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">目標分支:</x:String>
<x:String x:Key="Text.IssueLinkCM.CopyLink" xml:space="preserve">複製連結</x:String>
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">在瀏覽器中開啟連結</x:String>
<x:String x:Key="Text.Launcher.Commands" xml:space="preserve">命令列表</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">發生錯誤</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系統提示</x:String>
<x:String x:Key="Text.Launcher.OpenRepository" xml:space="preserve">開啟存放庫</x:String>

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class BlameCommandPalette : ObservableObject, IDisposable
{
public bool IsLoading
{
get => _isLoading;
private set => SetProperty(ref _isLoading, value);
}
public List<string> VisibleFiles
{
get => _visibleFiles;
private set => SetProperty(ref _visibleFiles, value);
}
public string Filter
{
get => _filter;
set
{
if (SetProperty(ref _filter, value))
UpdateVisible();
}
}
public string SelectedFile
{
get => _selectedFile;
set => SetProperty(ref _selectedFile, value);
}
public BlameCommandPalette(Launcher launcher, string repo)
{
_launcher = launcher;
_repo = repo;
_isLoading = true;
Task.Run(async () =>
{
var files = await new Commands.QueryRevisionFileNames(_repo, "HEAD")
.GetResultAsync()
.ConfigureAwait(false);
var head = await new Commands.QuerySingleCommit(_repo, "HEAD")
.GetResultAsync()
.ConfigureAwait(false);
Dispatcher.UIThread.Post(() =>
{
IsLoading = false;
_repoFiles = files;
_head = head;
UpdateVisible();
});
});
}
public void Dispose()
{
_launcher = null;
_repo = null;
_head = null;
_repoFiles.Clear();
_filter = null;
_visibleFiles.Clear();
_selectedFile = null;
}
public void ClearFilter()
{
Filter = string.Empty;
}
public void Launch()
{
if (!string.IsNullOrEmpty(_selectedFile))
App.ShowWindow(new Blame(_repo, _selectedFile, _head));
_launcher.CancelCommandPalette();
}
private void UpdateVisible()
{
if (_repoFiles is { Count: > 0 })
{
if (string.IsNullOrEmpty(_filter))
{
VisibleFiles = _repoFiles;
}
else
{
var visible = new List<string>();
foreach (var f in _repoFiles)
{
if (f.Contains(_filter, StringComparison.OrdinalIgnoreCase))
visible.Add(f);
}
VisibleFiles = visible;
}
}
}
private Launcher _launcher = null;
private string _repo = null;
private bool _isLoading = false;
private Models.Commit _head = null;
private List<string> _repoFiles = null;
private string _filter = string.Empty;
private List<string> _visibleFiles = [];
private string _selectedFile = null;
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class FileHistoryCommandPalette : ObservableObject, IDisposable
{
public bool IsLoading
{
get => _isLoading;
private set => SetProperty(ref _isLoading, value);
}
public List<string> VisibleFiles
{
get => _visibleFiles;
private set => SetProperty(ref _visibleFiles, value);
}
public string Filter
{
get => _filter;
set
{
if (SetProperty(ref _filter, value))
UpdateVisible();
}
}
public string SelectedFile
{
get => _selectedFile;
set => SetProperty(ref _selectedFile, value);
}
public FileHistoryCommandPalette(Launcher launcher, string repo)
{
_launcher = launcher;
_repo = repo;
_isLoading = true;
Task.Run(async () =>
{
var files = await new Commands.QueryRevisionFileNames(_repo, "HEAD")
.GetResultAsync()
.ConfigureAwait(false);
Dispatcher.UIThread.Post(() =>
{
IsLoading = false;
_repoFiles = files;
UpdateVisible();
});
});
}
public void Dispose()
{
_launcher = null;
_repo = null;
_repoFiles.Clear();
_filter = null;
_visibleFiles.Clear();
_selectedFile = null;
}
public void ClearFilter()
{
Filter = string.Empty;
}
public void Launch()
{
if (!string.IsNullOrEmpty(_selectedFile))
App.ShowWindow(new FileHistories(_repo, _selectedFile));
_launcher.CancelCommandPalette();
}
private void UpdateVisible()
{
if (_repoFiles is { Count: > 0 })
{
if (string.IsNullOrEmpty(_filter))
{
VisibleFiles = _repoFiles;
}
else
{
var visible = new List<string>();
foreach (var f in _repoFiles)
{
if (f.Contains(_filter, StringComparison.OrdinalIgnoreCase))
visible.Add(f);
}
VisibleFiles = visible;
}
}
}
private Launcher _launcher = null;
private string _repo = null;
private bool _isLoading = false;
private List<string> _repoFiles = null;
private string _filter = string.Empty;
private List<string> _visibleFiles = [];
private string _selectedFile = null;
}
}

View File

@@ -84,7 +84,7 @@ namespace SourceGit.ViewModels
else if (_selectedRepo != null)
_launcher.OpenRepositoryInTab(_selectedRepo, null);
_launcher.CancelCommandPalette();
_launcher?.CancelCommandPalette();
}
private void UpdateVisible()

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public record RepositoryCommandPaletteCmd(string Name, string Label, bool AutoCloseCommandPalette, Action Action);
public class RepositoryCommandPalette : ObservableObject, IDisposable
{
public List<RepositoryCommandPaletteCmd> VisibleCmds
{
get => _visibleCmds;
private set => SetProperty(ref _visibleCmds, value);
}
public RepositoryCommandPaletteCmd SelectedCmd
{
get => _selectedCmd;
set => SetProperty(ref _selectedCmd, value);
}
public string Filter
{
get => _filter;
set
{
if (SetProperty(ref _filter, value))
UpdateVisible();
}
}
public RepositoryCommandPalette(Launcher launcher, Repository repo)
{
_launcher = launcher;
_repo = repo;
_cmds.Add(new("File History", App.Text("FileHistory") + "...", false, () =>
{
var sub = new FileHistoryCommandPalette(_launcher, _repo.FullPath);
_launcher.OpenCommandPalette(sub);
}));
_cmds.Add(new("Blame", App.Text("Blame") + "...", false, () =>
{
var sub = new BlameCommandPalette(_launcher, _repo.FullPath);
_launcher.OpenCommandPalette(sub);
}));
_visibleCmds = _cmds;
}
public void Dispose()
{
_launcher = null;
_repo = null;
_cmds.Clear();
_visibleCmds.Clear();
_selectedCmd = null;
_filter = null;
}
public void ClearFilter()
{
Filter = string.Empty;
}
public void Exec()
{
if (_selectedCmd == null)
{
_launcher?.CancelCommandPalette();
return;
}
var autoClose = _selectedCmd.AutoCloseCommandPalette;
_selectedCmd.Action?.Invoke();
if (autoClose)
_launcher?.CancelCommandPalette();
}
private void UpdateVisible()
{
if (string.IsNullOrEmpty(_filter))
{
VisibleCmds = _cmds;
}
else
{
var visible = new List<RepositoryCommandPaletteCmd>();
foreach (var cmd in VisibleCmds)
{
if (cmd.Name.Contains(_filter, StringComparison.OrdinalIgnoreCase) ||
cmd.Label.Contains(_filter, StringComparison.OrdinalIgnoreCase))
visible.Add(cmd);
}
VisibleCmds = visible;
}
}
private Launcher _launcher = null;
private Repository _repo = null;
private List<RepositoryCommandPaletteCmd> _cmds = [];
private List<RepositoryCommandPaletteCmd> _visibleCmds = [];
private RepositoryCommandPaletteCmd _selectedCmd = null;
private string _filter;
}
}

View File

@@ -0,0 +1,116 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.BlameCommandPalette"
x:DataType="vm:BlameCommandPalette">
<Grid RowDefinitions="Auto,Auto">
<TextBox Grid.Row="0"
x:Name="FilterTextBox"
Height="24"
Margin="4,8,4,0"
BorderThickness="1"
CornerRadius="12"
Text="{Binding Filter, Mode=TwoWay}"
BorderBrush="{DynamicResource Brush.Border2}"
VerticalContentAlignment="Center"
v:AutoFocusBehaviour.IsEnabled="True">
<TextBox.InnerLeftContent>
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
<Border BorderThickness="0"
Background="{DynamicResource Brush.Badge}"
Height="18"
CornerRadius="4"
Margin="4,0" Padding="4,0">
<TextBlock Text="{DynamicResource Text.Blame}"
Foreground="Black"
FontWeight="Bold"/>
</Border>
</StackPanel>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Command="{Binding ClearFilter}"
IsVisible="{Binding Filter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<ListBox Grid.Row="1"
x:Name="FileListBox"
MaxHeight="250"
Margin="4,8,4,0"
BorderThickness="0"
SelectionMode="Single"
Background="Transparent"
Focusable="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding VisibleFiles, Mode=OneWay}"
SelectedItem="{Binding SelectedFile, Mode=TwoWay}"
IsVisible="{Binding VisibleFiles, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="8,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>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="x:String">
<Grid ColumnDefinitions="Auto,*" Background="Transparent" Tapped="OnItemTapped">
<Path Grid.Column="0"
Width="12" Height="12"
Data="{StaticResource Icons.File}"
IsHitTestVisible="False"/>
<TextBlock Grid.Column="1"
Margin="4,0,0,0"
VerticalAlignment="Center"
IsHitTestVisible="False">
<Run Text="{Binding Converter={x:Static c:PathConverters.PureFileName}, Mode=OneWay}"/>
<Run Text=" "/>
<Run Text="{Binding Converter={x:Static c:PathConverters.PureDirectoryName}, Mode=OneWay}" Foreground="{DynamicResource Brush.FG2}"/>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<v:LoadingIcon Grid.Row="1"
Width="48" Height="48"
Margin="0,12,0,8"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding IsLoading}"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,66 @@
using Avalonia.Controls;
using Avalonia.Input;
namespace SourceGit.Views
{
public partial class BlameCommandPalette : UserControl
{
public BlameCommandPalette()
{
InitializeComponent();
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (DataContext is not ViewModels.BlameCommandPalette vm)
return;
if (e.Key == Key.Enter)
{
vm.Launch();
e.Handled = true;
}
else if (e.Key == Key.Up)
{
if (FileListBox.IsKeyboardFocusWithin)
{
FilterTextBox.Focus(NavigationMethod.Directional);
e.Handled = true;
return;
}
}
else if (e.Key == Key.Down || e.Key == Key.Tab)
{
if (FilterTextBox.IsKeyboardFocusWithin)
{
if (vm.VisibleFiles.Count > 0)
{
FileListBox.Focus(NavigationMethod.Directional);
vm.SelectedFile = vm.VisibleFiles[0];
}
e.Handled = true;
return;
}
if (FileListBox.IsKeyboardFocusWithin && e.Key == Key.Tab)
{
FilterTextBox.Focus(NavigationMethod.Directional);
e.Handled = true;
return;
}
}
}
private void OnItemTapped(object sender, TappedEventArgs e)
{
if (DataContext is ViewModels.BlameCommandPalette vm)
{
vm.Launch();
e.Handled = true;
}
}
}
}

View File

@@ -0,0 +1,116 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.FileHistoryCommandPalette"
x:DataType="vm:FileHistoryCommandPalette">
<Grid RowDefinitions="Auto,Auto">
<TextBox Grid.Row="0"
x:Name="FilterTextBox"
Height="24"
Margin="4,8,4,0"
BorderThickness="1"
CornerRadius="12"
Text="{Binding Filter, Mode=TwoWay}"
BorderBrush="{DynamicResource Brush.Border2}"
VerticalContentAlignment="Center"
v:AutoFocusBehaviour.IsEnabled="True">
<TextBox.InnerLeftContent>
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
<Border BorderThickness="0"
Background="{DynamicResource Brush.Badge}"
Height="18"
CornerRadius="4"
Margin="4,0" Padding="4,0">
<TextBlock Text="{DynamicResource Text.FileHistory}"
Foreground="Black"
FontWeight="Bold"/>
</Border>
</StackPanel>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Command="{Binding ClearFilter}"
IsVisible="{Binding Filter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<ListBox Grid.Row="1"
x:Name="FileListBox"
MaxHeight="250"
Margin="4,8,4,0"
BorderThickness="0"
SelectionMode="Single"
Background="Transparent"
Focusable="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding VisibleFiles, Mode=OneWay}"
SelectedItem="{Binding SelectedFile, Mode=TwoWay}"
IsVisible="{Binding VisibleFiles, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="8,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>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="x:String">
<Grid ColumnDefinitions="Auto,*" Background="Transparent" Tapped="OnItemTapped">
<Path Grid.Column="0"
Width="12" Height="12"
Data="{StaticResource Icons.File}"
IsHitTestVisible="False"/>
<TextBlock Grid.Column="1"
Margin="4,0,0,0"
VerticalAlignment="Center"
IsHitTestVisible="False">
<Run Text="{Binding Converter={x:Static c:PathConverters.PureFileName}, Mode=OneWay}"/>
<Run Text=" "/>
<Run Text="{Binding Converter={x:Static c:PathConverters.PureDirectoryName}, Mode=OneWay}" Foreground="{DynamicResource Brush.FG2}"/>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<v:LoadingIcon Grid.Row="1"
Width="48" Height="48"
Margin="0,12,0,8"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding IsLoading}"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,66 @@
using Avalonia.Controls;
using Avalonia.Input;
namespace SourceGit.Views
{
public partial class FileHistoryCommandPalette : UserControl
{
public FileHistoryCommandPalette()
{
InitializeComponent();
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (DataContext is not ViewModels.FileHistoryCommandPalette vm)
return;
if (e.Key == Key.Enter)
{
vm.Launch();
e.Handled = true;
}
else if (e.Key == Key.Up)
{
if (FileListBox.IsKeyboardFocusWithin)
{
FilterTextBox.Focus(NavigationMethod.Directional);
e.Handled = true;
return;
}
}
else if (e.Key == Key.Down || e.Key == Key.Tab)
{
if (FilterTextBox.IsKeyboardFocusWithin)
{
if (vm.VisibleFiles.Count > 0)
{
FileListBox.Focus(NavigationMethod.Directional);
vm.SelectedFile = vm.VisibleFiles[0];
}
e.Handled = true;
return;
}
if (FileListBox.IsKeyboardFocusWithin && e.Key == Key.Tab)
{
FilterTextBox.Focus(NavigationMethod.Directional);
e.Handled = true;
return;
}
}
}
private void OnItemTapped(object sender, TappedEventArgs e)
{
if (DataContext is ViewModels.FileHistoryCommandPalette vm)
{
vm.Launch();
e.Handled = true;
}
}
}
}

View File

@@ -77,7 +77,7 @@
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Increase}}"
Margin="0,8"/>
<Grid RowDefinitions="20,20,20,20,20,20,20,20,20,20,20,20" ColumnDefinitions="150,*">
<Grid RowDefinitions="20,20,20,20,20,20,20,20,20,20,20,20,20" ColumnDefinitions="150,*">
<TextBlock Grid.Row="0" Grid.Column="0" Classes="bold" Text="{OnPlatform Ctrl+Shift+H, macOS=⌘+⇧+H}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.GoHome}" />
@@ -110,9 +110,12 @@
<TextBlock Grid.Row="10" Grid.Column="0" Classes="bold" Text="{OnPlatform Ctrl+Shift+Up, macOS=⌘+⇧+Up}"/>
<TextBlock Grid.Row="10" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.Push}" />
<TextBlock Grid.Row="11" Grid.Column="0" Classes="bold" Text="{OnPlatform Ctrl+Shift+P, macOS=⌘+⇧+P}"/>
<TextBlock Grid.Row="11" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.OpenCommandPalette}" />
<TextBlock Grid.Row="11" Grid.Column="0" Classes="bold" Text="F5"/>
<TextBlock Grid.Row="11" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.Refresh}" />
<TextBlock Grid.Row="12" Grid.Column="0" Classes="bold" Text="F5"/>
<TextBlock Grid.Row="12" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.Refresh}" />
</Grid>
<TextBlock Text="{DynamicResource Text.Hotkeys.TextEditor}"

View File

@@ -135,6 +135,15 @@
<DataTemplate DataType="vm:LauncherPagesCommandPalette">
<v:LauncherPagesCommandPalette/>
</DataTemplate>
<DataTemplate DataType="vm:RepositoryCommandPalette">
<v:RepositoryCommandPalette/>
</DataTemplate>
<DataTemplate DataType="vm:FileHistoryCommandPalette">
<v:FileHistoryCommandPalette/>
</DataTemplate>
<DataTemplate DataType="vm:BlameCommandPalette">
<v:BlameCommandPalette/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Border>

View File

@@ -232,6 +232,10 @@ namespace SourceGit.Views
repo.IsSearching = false;
e.Handled = true;
return;
case Key.P when e.KeyModifiers.HasFlag(KeyModifiers.Shift):
vm.OpenCommandPalette(new ViewModels.RepositoryCommandPalette(vm, repo));
e.Handled = true;
return;
}
}
else

View File

@@ -79,7 +79,7 @@
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
@@ -151,7 +151,7 @@
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>

View File

@@ -0,0 +1,103 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.RepositoryCommandPalette"
x:DataType="vm:RepositoryCommandPalette">
<Grid RowDefinitions="Auto,Auto,Auto">
<TextBox Grid.Row="0"
x:Name="FilterTextBox"
Height="24"
Margin="4,8,4,0"
BorderThickness="1"
CornerRadius="12"
Text="{Binding Filter, Mode=TwoWay}"
BorderBrush="{DynamicResource Brush.Border2}"
VerticalContentAlignment="Center"
v:AutoFocusBehaviour.IsEnabled="True">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Command="{Binding ClearFilter}"
IsVisible="{Binding Filter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<TextBlock Grid.Row="1"
Margin="6,12,0,8"
Text="{DynamicResource Text.Launcher.Commands}"
FontWeight="Bold"
Foreground="{DynamicResource Brush.FG2}"
IsVisible="{Binding VisibleCmds, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"/>
<ListBox Grid.Row="2"
x:Name="CmdListBox"
MaxHeight="250"
Margin="4,0"
BorderThickness="0"
SelectionMode="Single"
Background="Transparent"
Focusable="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding VisibleCmds, Mode=OneWay}"
SelectedItem="{Binding SelectedCmd, Mode=TwoWay}"
IsVisible="{Binding VisibleCmds, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="8,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>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:RepositoryCommandPaletteCmd">
<Grid ColumnDefinitions="Auto,*" Background="Transparent" Tapped="OnItemTapped">
<Path Grid.Column="0"
Width="12" Height="12"
Data="{StaticResource Icons.Command}"
IsHitTestVisible="False"/>
<TextBlock Grid.Column="1"
Margin="6,0,0,0"
VerticalAlignment="Center"
IsHitTestVisible="False"
Text="{Binding Label, Mode=OneWay}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>

View File

@@ -0,0 +1,66 @@
using Avalonia.Controls;
using Avalonia.Input;
namespace SourceGit.Views
{
public partial class RepositoryCommandPalette : UserControl
{
public RepositoryCommandPalette()
{
InitializeComponent();
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (DataContext is not ViewModels.RepositoryCommandPalette vm)
return;
if (e.Key == Key.Enter)
{
vm.Exec();
e.Handled = true;
}
else if (e.Key == Key.Up)
{
if (CmdListBox.IsKeyboardFocusWithin)
{
FilterTextBox.Focus(NavigationMethod.Directional);
e.Handled = true;
return;
}
}
else if (e.Key == Key.Down || e.Key == Key.Tab)
{
if (FilterTextBox.IsKeyboardFocusWithin)
{
if (vm.VisibleCmds.Count > 0)
{
CmdListBox.Focus(NavigationMethod.Directional);
vm.SelectedCmd = vm.VisibleCmds[0];
}
e.Handled = true;
return;
}
if (CmdListBox.IsKeyboardFocusWithin && e.Key == Key.Tab)
{
FilterTextBox.Focus(NavigationMethod.Directional);
e.Handled = true;
return;
}
}
}
private void OnItemTapped(object sender, TappedEventArgs e)
{
if (DataContext is ViewModels.RepositoryCommandPalette vm)
{
vm.Exec();
e.Handled = true;
}
}
}
}