feature: auto focus the next change after stage/unstage selected changes (#464)

This commit is contained in:
leo 2024-09-12 16:33:56 +08:00
parent dcddc5a2f2
commit ea3a6a4755
No known key found for this signature in database
4 changed files with 122 additions and 22 deletions

View file

@ -325,23 +325,24 @@ namespace SourceGit.ViewModels
PopupHost.ShowPopup(new StashChanges(_repo, _cached, true));
}
public void StageSelected()
public void StageSelected(Models.Change next)
{
StageChanges(_selectedUnstaged);
SelectedUnstaged = [];
StageChanges(_selectedUnstaged, next);
}
public void StageAll()
{
StageChanges(_unstaged);
SelectedUnstaged = [];
StageChanges(_unstaged, null);
}
public async void StageChanges(List<Models.Change> changes)
public async void StageChanges(List<Models.Change> changes, Models.Change next)
{
if (_unstaged.Count == 0 || changes.Count == 0)
return;
// Use `_selectedUnstaged` instead of `SelectedUnstaged` to avoid UI refresh.
_selectedUnstaged = next != null ? [next] : [];
IsStaging = true;
_repo.SetWatcherEnabled(false);
if (changes.Count == _unstaged.Count)
@ -362,23 +363,24 @@ namespace SourceGit.ViewModels
IsStaging = false;
}
public void UnstageSelected()
public void UnstageSelected(Models.Change next)
{
UnstageChanges(_selectedStaged);
SelectedStaged = [];
UnstageChanges(_selectedStaged, next);
}
public void UnstageAll()
{
UnstageChanges(_staged);
SelectedStaged = [];
UnstageChanges(_staged, null);
}
public async void UnstageChanges(List<Models.Change> changes)
public async void UnstageChanges(List<Models.Change> changes, Models.Change next)
{
if (_staged.Count == 0 || changes.Count == 0)
return;
// Use `_selectedStaged` instead of `SelectedStaged` to avoid UI refresh.
_selectedStaged = next != null ? [next] : [];
IsUnstaging = true;
_repo.SetWatcherEnabled(false);
if (_useAmend)
@ -499,7 +501,7 @@ namespace SourceGit.ViewModels
stage.Icon = App.CreateMenuIcon("Icons.File.Add");
stage.Click += (_, e) =>
{
StageChanges(_selectedUnstaged);
StageChanges(_selectedUnstaged, null);
e.Handled = true;
};
@ -823,7 +825,7 @@ namespace SourceGit.ViewModels
stage.Icon = App.CreateMenuIcon("Icons.File.Add");
stage.Click += (_, e) =>
{
StageChanges(_selectedUnstaged);
StageChanges(_selectedUnstaged, null);
e.Handled = true;
};
@ -917,7 +919,7 @@ namespace SourceGit.ViewModels
unstage.Icon = App.CreateMenuIcon("Icons.File.Remove");
unstage.Click += (_, e) =>
{
UnstageChanges(_selectedStaged);
UnstageChanges(_selectedStaged, null);
e.Handled = true;
};
@ -1086,7 +1088,7 @@ namespace SourceGit.ViewModels
unstage.Icon = App.CreateMenuIcon("Icons.File.Remove");
unstage.Click += (_, e) =>
{
UnstageChanges(_selectedStaged);
UnstageChanges(_selectedStaged, null);
e.Handled = true;
};

View file

@ -96,6 +96,7 @@ namespace SourceGit.Views
public ChangeCollectionView()
{
Focusable = true;
InitializeComponent();
}
@ -132,6 +133,69 @@ namespace SourceGit.Views
}
}
public Models.Change GetNextChangeWithoutSelection()
{
var selected = SelectedChanges;
var changes = Changes;
if (selected == null || selected.Count == 0)
return changes.Count > 0 ? changes[0] : null;
if (selected.Count == changes.Count)
return null;
var set = new HashSet<string>();
foreach (var c in selected)
set.Add(c.Path);
if (Content is ViewModels.ChangeCollectionAsTree tree)
{
var lastUnselected = -1;
for (int i = tree.Rows.Count - 1; i >= 0; i--)
{
var row = tree.Rows[i];
if (!row.IsFolder)
{
if (set.Contains(row.FullPath))
{
if (lastUnselected == -1)
continue;
else
break;
}
else
{
lastUnselected = i;
}
}
}
if (lastUnselected != -1)
return tree.Rows[lastUnselected].Change;
}
else
{
var lastUnselected = -1;
for (int i = changes.Count - 1; i >= 0; i--)
{
if (set.Contains(changes[i].Path))
{
if (lastUnselected == -1)
continue;
else
break;
}
else
{
lastUnselected = i;
}
}
if (lastUnselected != -1)
return changes[lastUnselected];
}
return null;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

View file

@ -42,7 +42,7 @@
Classes="icon_button"
Width="26" Height="14"
Padding="0"
Command="{Binding StageSelected}">
Click="OnStageSelectedButtonClicked">
<ToolTip.Tip>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Text.WorkingCopy.Unstaged.Stage}" VerticalAlignment="Center"/>
@ -64,6 +64,7 @@
<!-- Unstaged Changes -->
<v:ChangeCollectionView Grid.Row="1"
x:Name="UnstagedChangesView"
IsUnstagedChange="True"
SelectionMode="Multiple"
Background="{DynamicResource Brush.Contents}"
@ -81,7 +82,7 @@
<TextBlock Grid.Column="1" Text="{DynamicResource Text.WorkingCopy.Staged}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" FontWeight="Bold" Foreground="{DynamicResource Brush.FG2}" Text="{Binding Staged, Converter={x:Static c:ListConverters.ToCount}}"/>
<v:LoadingIcon Grid.Column="3" Width="14" Height="14" Margin="8,0,0,0" IsVisible="{Binding IsUnstaging}"/>
<Button Grid.Column="5" Classes="icon_button" Width="26" Height="14" Padding="0" Command="{Binding UnstageSelected}">
<Button Grid.Column="5" Classes="icon_button" Width="26" Height="14" Padding="0" Click="OnUnstageSelectedButtonClicked">
<ToolTip.Tip>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Text.WorkingCopy.Staged.Unstage}" VerticalAlignment="Center"/>
@ -98,6 +99,7 @@
<!-- Staged Changes -->
<v:ChangeCollectionView Grid.Row="3"
x:Name="StagedChangesView"
SelectionMode="Multiple"
Background="{DynamicResource Brush.Contents}"
ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode}"

View file

@ -46,7 +46,9 @@ namespace SourceGit.Views
{
if (DataContext is ViewModels.WorkingCopy vm)
{
vm.StageSelected();
var next = UnstagedChangesView.GetNextChangeWithoutSelection();
vm.StageSelected(next);
UnstagedChangesView.Focus();
e.Handled = true;
}
}
@ -55,7 +57,9 @@ namespace SourceGit.Views
{
if (DataContext is ViewModels.WorkingCopy vm)
{
vm.UnstageSelected();
var next = StagedChangesView.GetNextChangeWithoutSelection();
vm.UnstageSelected(next);
StagedChangesView.Focus();
e.Handled = true;
}
}
@ -66,7 +70,9 @@ namespace SourceGit.Views
{
if (e.Key is Key.Space or Key.Enter)
{
vm.StageSelected();
var next = UnstagedChangesView.GetNextChangeWithoutSelection();
vm.StageSelected(next);
UnstagedChangesView.Focus();
e.Handled = true;
return;
}
@ -84,11 +90,37 @@ namespace SourceGit.Views
{
if (DataContext is ViewModels.WorkingCopy vm && e.Key is Key.Space or Key.Enter)
{
vm.UnstageSelected();
var next = StagedChangesView.GetNextChangeWithoutSelection();
vm.UnstageSelected(next);
StagedChangesView.Focus();
e.Handled = true;
}
}
private void OnStageSelectedButtonClicked(object _, RoutedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm)
{
var next = UnstagedChangesView.GetNextChangeWithoutSelection();
vm.StageSelected(next);
UnstagedChangesView.Focus();
}
e.Handled = true;
}
private void OnUnstageSelectedButtonClicked(object _, RoutedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm)
{
var next = StagedChangesView.GetNextChangeWithoutSelection();
vm.UnstageSelected(next);
StagedChangesView.Focus();
}
e.Handled = true;
}
private void OnOpenAIAssist(object _, RoutedEventArgs e)
{
if (!Models.OpenAI.IsValid)