Skip to content

Commit e6de365

Browse files
committed
feature: add Checkout Branch command palette (#1937)
Signed-off-by: leo <[email protected]>
1 parent c187480 commit e6de365

File tree

4 files changed

+283
-8
lines changed

4 files changed

+283
-8
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
5+
namespace SourceGit.ViewModels
6+
{
7+
public class CheckoutCommandPalette : ICommandPalette
8+
{
9+
public List<Models.Branch> Branches
10+
{
11+
get => _branches;
12+
private set => SetProperty(ref _branches, value);
13+
}
14+
15+
public Models.Branch SelectedBranch
16+
{
17+
get => _selectedBranch;
18+
set => SetProperty(ref _selectedBranch, value);
19+
}
20+
21+
public string Filter
22+
{
23+
get => _filter;
24+
set
25+
{
26+
if (SetProperty(ref _filter, value))
27+
UpdateBranches();
28+
}
29+
}
30+
31+
public CheckoutCommandPalette(Launcher launcher, Repository repo)
32+
{
33+
_launcher = launcher;
34+
_repo = repo;
35+
UpdateBranches();
36+
}
37+
38+
public override void Cleanup()
39+
{
40+
_launcher = null;
41+
_repo = null;
42+
_branches.Clear();
43+
_selectedBranch = null;
44+
_filter = null;
45+
}
46+
47+
public void ClearFilter()
48+
{
49+
Filter = string.Empty;
50+
}
51+
52+
public async Task ExecAsync()
53+
{
54+
_launcher.CommandPalette = null;
55+
56+
if (_selectedBranch != null)
57+
await _repo.CheckoutBranchAsync(_selectedBranch);
58+
59+
Dispose();
60+
GC.Collect();
61+
}
62+
63+
private void UpdateBranches()
64+
{
65+
var current = _repo.CurrentBranch;
66+
if (current == null)
67+
return;
68+
69+
var branches = new List<Models.Branch>();
70+
foreach (var b in _repo.Branches)
71+
{
72+
if (b == current)
73+
continue;
74+
75+
if (string.IsNullOrEmpty(_filter) || b.FriendlyName.Contains(_filter, StringComparison.OrdinalIgnoreCase))
76+
branches.Add(b);
77+
}
78+
79+
branches.Sort((l, r) =>
80+
{
81+
if (l.IsLocal == r.IsLocal)
82+
return l.Name.CompareTo(r.Name);
83+
84+
return l.IsLocal ? -1 : 1;
85+
});
86+
87+
Branches = branches;
88+
}
89+
90+
private Launcher _launcher;
91+
private Repository _repo;
92+
private List<Models.Branch> _branches = [];
93+
private Models.Branch _selectedBranch = null;
94+
private string _filter;
95+
}
96+
}

src/ViewModels/RepositoryCommandPalette.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,27 @@ public RepositoryCommandPalette(Launcher launcher, Repository repo)
4545
_launcher = launcher;
4646
_repo = repo;
4747

48-
_cmds.Add(new("OpenFile", () =>
48+
_cmds.Add(new("Blame", () =>
4949
{
50-
var sub = new OpenFileCommandPalette(_launcher, _repo.FullPath);
50+
var sub = new BlameCommandPalette(_launcher, _repo.FullPath);
5151
_launcher.OpenCommandPalette(sub);
5252
}));
5353

54-
_cmds.Add(new("FileHistory", () =>
54+
_cmds.Add(new("BranchCompare", () =>
5555
{
56-
var sub = new FileHistoryCommandPalette(_launcher, _repo.FullPath);
56+
var sub = new BranchCompareCommandPalette(_launcher, _repo);
5757
_launcher.OpenCommandPalette(sub);
5858
}));
5959

60-
_cmds.Add(new("Blame", () =>
60+
_cmds.Add(new("Checkout", () =>
6161
{
62-
var sub = new BlameCommandPalette(_launcher, _repo.FullPath);
62+
var sub = new CheckoutCommandPalette(_launcher, _repo);
63+
_launcher.OpenCommandPalette(sub);
64+
}));
65+
66+
_cmds.Add(new("FileHistory", () =>
67+
{
68+
var sub = new FileHistoryCommandPalette(_launcher, _repo.FullPath);
6369
_launcher.OpenCommandPalette(sub);
6470
}));
6571

@@ -69,9 +75,9 @@ public RepositoryCommandPalette(Launcher launcher, Repository repo)
6975
_launcher.OpenCommandPalette(sub);
7076
}));
7177

72-
_cmds.Add(new("BranchCompare", () =>
78+
_cmds.Add(new("OpenFile", () =>
7379
{
74-
var sub = new BranchCompareCommandPalette(_launcher, _repo);
80+
var sub = new OpenFileCommandPalette(_launcher, _repo.FullPath);
7581
_launcher.OpenCommandPalette(sub);
7682
}));
7783

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:m="using:SourceGit.Models"
6+
xmlns:vm="using:SourceGit.ViewModels"
7+
xmlns:v="using:SourceGit.Views"
8+
xmlns:c="using:SourceGit.Converters"
9+
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
10+
x:Class="SourceGit.Views.CheckoutCommandPalette"
11+
x:DataType="vm:CheckoutCommandPalette">
12+
<Grid RowDefinitions="Auto,Auto">
13+
<v:RepositoryCommandPaletteTextBox Grid.Row="0"
14+
x:Name="FilterTextBox"
15+
Height="24"
16+
Margin="4,8,4,0"
17+
BorderThickness="1"
18+
CornerRadius="12"
19+
Text="{Binding Filter, Mode=TwoWay}"
20+
BorderBrush="{DynamicResource Brush.Border2}"
21+
VerticalContentAlignment="Center">
22+
<TextBox.InnerLeftContent>
23+
<StackPanel Orientation="Horizontal">
24+
<Path Width="14" Height="14"
25+
Margin="6,0,0,0"
26+
Fill="{DynamicResource Brush.FG2}"
27+
Data="{StaticResource Icons.Search}"/>
28+
<Border BorderThickness="0"
29+
Background="{DynamicResource Brush.Badge}"
30+
Height="18"
31+
CornerRadius="4"
32+
Margin="4,0,0,0" Padding="4,0">
33+
<TextBlock Text="{DynamicResource Text.Checkout}"
34+
Foreground="Black"
35+
FontWeight="Bold"/>
36+
</Border>
37+
</StackPanel>
38+
</TextBox.InnerLeftContent>
39+
40+
<TextBox.InnerRightContent>
41+
<Button Classes="icon_button"
42+
Width="16"
43+
Margin="0,0,6,0"
44+
Command="{Binding ClearFilter}"
45+
IsVisible="{Binding Filter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
46+
HorizontalAlignment="Right">
47+
<Path Width="14" Height="14"
48+
Margin="0,1,0,0"
49+
Fill="{DynamicResource Brush.FG1}"
50+
Data="{StaticResource Icons.Clear}"/>
51+
</Button>
52+
</TextBox.InnerRightContent>
53+
</v:RepositoryCommandPaletteTextBox>
54+
55+
<ListBox Grid.Row="1"
56+
x:Name="BranchListBox"
57+
MaxHeight="250"
58+
Margin="4,8,4,0"
59+
BorderThickness="0"
60+
SelectionMode="Single"
61+
Background="Transparent"
62+
Focusable="True"
63+
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
64+
ScrollViewer.VerticalScrollBarVisibility="Auto"
65+
ItemsSource="{Binding Branches, Mode=OneWay}"
66+
SelectedItem="{Binding SelectedBranch, Mode=TwoWay}"
67+
IsVisible="{Binding Branches, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
68+
<ListBox.Styles>
69+
<Style Selector="ListBoxItem">
70+
<Setter Property="Padding" Value="8,0"/>
71+
<Setter Property="MinHeight" Value="26"/>
72+
<Setter Property="CornerRadius" Value="4"/>
73+
</Style>
74+
75+
<Style Selector="ListBox">
76+
<Setter Property="FocusAdorner">
77+
<FocusAdornerTemplate>
78+
<Grid/>
79+
</FocusAdornerTemplate>
80+
</Setter>
81+
</Style>
82+
</ListBox.Styles>
83+
84+
<ListBox.ItemsPanel>
85+
<ItemsPanelTemplate>
86+
<VirtualizingStackPanel Orientation="Vertical"/>
87+
</ItemsPanelTemplate>
88+
</ListBox.ItemsPanel>
89+
90+
<ListBox.ItemTemplate>
91+
<DataTemplate DataType="m:Branch">
92+
<Grid ColumnDefinitions="Auto,*" Background="Transparent" Tapped="OnItemTapped">
93+
<Path Grid.Column="0"
94+
Width="12" Height="12"
95+
Data="{StaticResource Icons.Branch}"
96+
IsHitTestVisible="False"/>
97+
<TextBlock Grid.Column="1"
98+
Margin="4,0,0,0"
99+
VerticalAlignment="Center"
100+
IsHitTestVisible="False"
101+
Text="{Binding FriendlyName, Mode=OneWay}"/>
102+
</Grid>
103+
</DataTemplate>
104+
</ListBox.ItemTemplate>
105+
</ListBox>
106+
</Grid>
107+
</UserControl>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using Avalonia.Controls;
2+
using Avalonia.Input;
3+
4+
namespace SourceGit.Views
5+
{
6+
public partial class CheckoutCommandPalette : UserControl
7+
{
8+
public CheckoutCommandPalette()
9+
{
10+
InitializeComponent();
11+
}
12+
13+
protected override async void OnKeyDown(KeyEventArgs e)
14+
{
15+
base.OnKeyDown(e);
16+
17+
if (DataContext is not ViewModels.CheckoutCommandPalette vm)
18+
return;
19+
20+
if (e.Key == Key.Enter)
21+
{
22+
await vm.ExecAsync();
23+
e.Handled = true;
24+
}
25+
else if (e.Key == Key.Up)
26+
{
27+
if (BranchListBox.IsKeyboardFocusWithin)
28+
{
29+
FilterTextBox.Focus(NavigationMethod.Directional);
30+
e.Handled = true;
31+
return;
32+
}
33+
}
34+
else if (e.Key == Key.Down || e.Key == Key.Tab)
35+
{
36+
if (FilterTextBox.IsKeyboardFocusWithin)
37+
{
38+
if (vm.Branches.Count > 0)
39+
{
40+
BranchListBox.Focus(NavigationMethod.Directional);
41+
vm.SelectedBranch = vm.Branches[0];
42+
}
43+
44+
e.Handled = true;
45+
return;
46+
}
47+
48+
if (BranchListBox.IsKeyboardFocusWithin && e.Key == Key.Tab)
49+
{
50+
FilterTextBox.Focus(NavigationMethod.Directional);
51+
e.Handled = true;
52+
return;
53+
}
54+
}
55+
}
56+
57+
private async void OnItemTapped(object sender, TappedEventArgs e)
58+
{
59+
if (DataContext is ViewModels.CheckoutCommandPalette vm)
60+
{
61+
await vm.ExecAsync();
62+
e.Handled = true;
63+
}
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)