From dbedf4bc7beccf7cd03e6bf7ec70429691a695b6 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 22 Aug 2025 17:04:36 +0800 Subject: [PATCH] feature: support to open selected file with default editor in `CHANGES` tab (#1750) Signed-off-by: leo --- src/ViewModels/RevisionCompare.cs | 10 +++--- src/ViewModels/StashesPage.cs | 10 +++--- src/Views/BranchCompare.axaml | 3 +- src/Views/BranchCompare.axaml.cs | 20 +++++++++++ src/Views/ChangeCollectionView.axaml.cs | 32 +---------------- src/Views/CommitChanges.axaml | 3 +- src/Views/CommitChanges.axaml.cs | 24 +++++++++++++ src/Views/CommitDetail.axaml.cs | 47 +++++++++++++++++-------- src/Views/RevisionCompare.axaml | 3 +- src/Views/RevisionCompare.axaml.cs | 23 ++++++++++-- src/Views/StashesPage.axaml | 3 +- src/Views/StashesPage.axaml.cs | 23 ++++++++++-- src/Views/WorkingCopy.axaml.cs | 24 +++++++++++++ 13 files changed, 161 insertions(+), 64 deletions(-) diff --git a/src/ViewModels/RevisionCompare.cs b/src/ViewModels/RevisionCompare.cs index 4f79ae42..ca81a0dc 100644 --- a/src/ViewModels/RevisionCompare.cs +++ b/src/ViewModels/RevisionCompare.cs @@ -8,11 +8,6 @@ namespace SourceGit.ViewModels { public class RevisionCompare : ObservableObject, IDisposable { - public string RepositoryPath - { - get => _repo; - } - public bool IsLoading { get => _isLoading; @@ -127,6 +122,11 @@ namespace SourceGit.ViewModels Refresh(); } + public string GetAbsPath(string path) + { + return Native.OS.GetAbsPath(_repo, path); + } + public void SaveAsPatch(string saveTo) { Task.Run(async () => diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index 6dde16bf..3d18fff5 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -9,11 +9,6 @@ namespace SourceGit.ViewModels { public class StashesPage : ObservableObject, IDisposable { - public string RepositoryPath - { - get => _repo.FullPath; - } - public List Stashes { get => _stashes; @@ -146,6 +141,11 @@ namespace SourceGit.ViewModels SearchFilter = string.Empty; } + public string GetAbsPath(string path) + { + return Native.OS.GetAbsPath(_repo.FullPath, path); + } + public void Apply(Models.Stash stash) { if (_repo.CanCreatePopup()) diff --git a/src/Views/BranchCompare.axaml b/src/Views/BranchCompare.axaml index 8f338751..a407527a 100644 --- a/src/Views/BranchCompare.axaml +++ b/src/Views/BranchCompare.axaml @@ -128,7 +128,8 @@ EnableCompactFolders="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableCompactFoldersInChangesTree}" Changes="{Binding VisibleChanges}" SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}" - ContextRequested="OnChangeContextRequested"/> + ContextRequested="OnChangeContextRequested" + KeyDown="OnChangeCollectionViewKeyDown"/> diff --git a/src/Views/BranchCompare.axaml.cs b/src/Views/BranchCompare.axaml.cs index 9f11dfd0..886825b1 100644 --- a/src/Views/BranchCompare.axaml.cs +++ b/src/Views/BranchCompare.axaml.cs @@ -83,5 +83,25 @@ namespace SourceGit.Views e.Handled = true; } + + private async void OnChangeCollectionViewKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is not ViewModels.BranchCompare vm) + return; + + if (sender is not ChangeCollectionView { SelectedChanges: { Count: 1 } selectedChanges }) + return; + + var change = selectedChanges[0]; + if (e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) && e.Key == Key.C) + { + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + await App.CopyTextAsync(vm.GetAbsPath(change.Path)); + else + await App.CopyTextAsync(change.Path); + + e.Handled = true; + } + } } } diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 13110216..4e3092fa 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -33,7 +33,7 @@ namespace SourceGit.Views { protected override Type StyleKeyOverride => typeof(ListBox); - protected override async void OnKeyDown(KeyEventArgs e) + protected override void OnKeyDown(KeyEventArgs e) { if (SelectedItems is [ViewModels.ChangeTreeNode node]) { @@ -43,36 +43,6 @@ namespace SourceGit.Views this.FindAncestorOfType()?.ToggleNodeIsExpanded(node); e.Handled = true; } - else if (e.Key == Key.C && - e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control)) - { - var path = node.FullPath; - - if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) - { - do - { - var repoView = this.FindAncestorOfType(); - if (repoView is { DataContext: ViewModels.Repository repo }) - { - path = Native.OS.GetAbsPath(repo.FullPath, path); - break; - } - - var branchCompareView = this.FindAncestorOfType(); - if (branchCompareView is { DataContext: ViewModels.BranchCompare branchCompare }) - { - path = branchCompare.GetAbsPath(path); - break; - } - - // NOTE: if there is another window uses ChangeCollectionView, add it here! - } while (false); - } - - await App.CopyTextAsync(path); - e.Handled = true; - } } if (!e.Handled && e.Key != Key.Space && e.Key != Key.Enter) diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index 8e871a05..4656b6b4 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -50,7 +50,8 @@ EnableCompactFolders="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableCompactFoldersInChangesTree}" Changes="{Binding VisibleChanges}" SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}" - ContextRequested="OnChangeContextRequested"/> + ContextRequested="OnChangeContextRequested" + KeyDown="OnChangeCollectionViewKeyDown"/> diff --git a/src/Views/CommitChanges.axaml.cs b/src/Views/CommitChanges.axaml.cs index 3bfeaa79..9364be16 100644 --- a/src/Views/CommitChanges.axaml.cs +++ b/src/Views/CommitChanges.axaml.cs @@ -1,4 +1,7 @@ +using System; + using Avalonia.Controls; +using Avalonia.Input; using Avalonia.VisualTree; namespace SourceGit.Views @@ -36,5 +39,26 @@ namespace SourceGit.Views menu.Open(view); } } + + private async void OnChangeCollectionViewKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is not ViewModels.CommitDetail vm) + return; + + if (sender is not ChangeCollectionView { SelectedChanges: { Count: 1 } selectedChanges }) + return; + + var change = selectedChanges[0]; + if (e.Key == Key.C && + e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control)) + { + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + await App.CopyTextAsync(vm.GetAbsPath(change.Path)); + else + await App.CopyTextAsync(change.Path); + + e.Handled = true; + } + } } } diff --git a/src/Views/CommitDetail.axaml.cs b/src/Views/CommitDetail.axaml.cs index f1d073a7..3c800d9c 100644 --- a/src/Views/CommitDetail.axaml.cs +++ b/src/Views/CommitDetail.axaml.cs @@ -111,6 +111,16 @@ namespace SourceGit.Views ev.Handled = true; }; + var openWith = new MenuItem(); + openWith.Header = App.Text("OpenWith"); + openWith.Icon = App.CreateMenuIcon("Icons.OpenWith"); + openWith.IsEnabled = change.Index != Models.ChangeState.Deleted; + openWith.Click += async (_, ev) => + { + await vm.OpenRevisionFileWithDefaultEditorAsync(change.Path); + ev.Handled = true; + }; + var fullPath = Native.OS.GetAbsPath(repo.FullPath, change.Path); var explore = new MenuItem(); explore.Header = App.Text("RevealFile"); @@ -167,6 +177,7 @@ namespace SourceGit.Views var menu = new ContextMenu(); menu.Items.Add(openWithMerger); + menu.Items.Add(openWith); menu.Items.Add(explore); menu.Items.Add(new MenuItem { Header = "-" }); menu.Items.Add(history); @@ -293,24 +304,30 @@ namespace SourceGit.Views private async void OnCommitListKeyDown(object sender, KeyEventArgs e) { - if (DataContext is ViewModels.CommitDetail detail && - sender is ListBox { SelectedItem: Models.Change change } && + if (DataContext is not ViewModels.CommitDetail vm) + return; + + if (sender is not ListBox { SelectedItem: Models.Change change }) + return; + + if (e.Key == Key.C && e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control)) { - if (e.Key == Key.C) - { - var path = change.Path; - if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) - path = detail.GetAbsPath(path); + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + await App.CopyTextAsync(vm.GetAbsPath(change.Path)); + else + await App.CopyTextAsync(change.Path); - await App.CopyTextAsync(path); - e.Handled = true; - } - else if (e.Key == Key.D && e.KeyModifiers.HasFlag(KeyModifiers.Shift)) - { - detail.OpenChangeInMergeTool(change); - e.Handled = true; - } + e.Handled = true; + return; + } + + if (e.Key == Key.D && + e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) && + e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + { + vm.OpenChangeInMergeTool(change); + e.Handled = true; } } diff --git a/src/Views/RevisionCompare.axaml b/src/Views/RevisionCompare.axaml index 43e4902a..dd8237b5 100644 --- a/src/Views/RevisionCompare.axaml +++ b/src/Views/RevisionCompare.axaml @@ -100,7 +100,8 @@ EnableCompactFolders="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableCompactFoldersInChangesTree}" Changes="{Binding VisibleChanges}" SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}" - ContextRequested="OnChangeContextRequested"/> + ContextRequested="OnChangeContextRequested" + KeyDown="OnChangeCollectionViewKeyDown"/> diff --git a/src/Views/RevisionCompare.axaml.cs b/src/Views/RevisionCompare.axaml.cs index c52829f5..bb27e709 100644 --- a/src/Views/RevisionCompare.axaml.cs +++ b/src/Views/RevisionCompare.axaml.cs @@ -20,9 +20,8 @@ namespace SourceGit.Views if (DataContext is ViewModels.RevisionCompare { SelectedChanges: { Count: 1 } selected } vm && sender is ChangeCollectionView view) { - var repo = vm.RepositoryPath; var change = selected[0]; - var changeFullPath = Native.OS.GetAbsPath(repo, change.Path); + var changeFullPath = vm.GetAbsPath(change.Path); var menu = new ContextMenu(); var openWithMerger = new MenuItem(); @@ -105,5 +104,25 @@ namespace SourceGit.Views e.Handled = true; } + + private async void OnChangeCollectionViewKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is not ViewModels.RevisionCompare vm) + return; + + if (sender is not ChangeCollectionView { SelectedChanges: { Count: 1 } selectedChanges }) + return; + + var change = selectedChanges[0]; + if (e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) && e.Key == Key.C) + { + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + await App.CopyTextAsync(vm.GetAbsPath(change.Path)); + else + await App.CopyTextAsync(change.Path); + + e.Handled = true; + } + } } } diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index e5803367..b6742a17 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -131,7 +131,8 @@ EnableCompactFolders="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableCompactFoldersInChangesTree}" Changes="{Binding Changes}" SelectedChanges="{Binding SelectedChanges, Mode=TwoWay}" - ContextRequested="OnChangeContextRequested"/> + ContextRequested="OnChangeContextRequested" + KeyDown="OnChangeCollectionViewKeyDown"/> diff --git a/src/Views/StashesPage.axaml.cs b/src/Views/StashesPage.axaml.cs index 186176fc..6dc74f17 100644 --- a/src/Views/StashesPage.axaml.cs +++ b/src/Views/StashesPage.axaml.cs @@ -126,8 +126,8 @@ namespace SourceGit.Views if (DataContext is ViewModels.StashesPage { SelectedChanges: { Count: 1 } selected } vm && sender is ChangeCollectionView view) { - var repo = vm.RepositoryPath; var change = selected[0]; + var fullPath = vm.GetAbsPath(change.Path); var openWithMerger = new MenuItem(); openWithMerger.Header = App.Text("OpenInExternalMergeTool"); @@ -139,7 +139,6 @@ namespace SourceGit.Views ev.Handled = true; }; - var fullPath = Native.OS.GetAbsPath(repo, change.Path); var explore = new MenuItem(); explore.Header = App.Text("RevealFile"); explore.Icon = App.CreateMenuIcon("Icons.Explore"); @@ -192,5 +191,25 @@ namespace SourceGit.Views e.Handled = true; } + + private async void OnChangeCollectionViewKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is not ViewModels.StashesPage vm) + return; + + if (sender is not ChangeCollectionView { SelectedChanges: { Count: 1 } selectedChanges }) + return; + + var change = selectedChanges[0]; + if (e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) && e.Key == Key.C) + { + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + await App.CopyTextAsync(vm.GetAbsPath(change.Path)); + else + await App.CopyTextAsync(change.Path); + + e.Handled = true; + } + } } } diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index f8dcc738..a69d86bf 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -113,6 +113,18 @@ namespace SourceGit.Views vm.OpenWithDefaultEditor(vm.SelectedUnstaged[0]); e.Handled = true; } + else if (e.Key is Key.C && + e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) && + vm.SelectedUnstaged is { Count: 1 }) + { + var change = vm.SelectedUnstaged[0]; + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + await App.CopyTextAsync(Native.OS.GetAbsPath(vm.Repository.FullPath, change.Path)); + else + await App.CopyTextAsync(change.Path); + + e.Handled = true; + } } } @@ -134,6 +146,18 @@ namespace SourceGit.Views vm.OpenWithDefaultEditor(vm.SelectedStaged[0]); e.Handled = true; } + else if (e.Key is Key.C && + e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) && + vm.SelectedStaged is { Count: 1 }) + { + var change = vm.SelectedStaged[0]; + if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) + await App.CopyTextAsync(Native.OS.GetAbsPath(vm.Repository.FullPath, change.Path)); + else + await App.CopyTextAsync(change.Path); + + e.Handled = true; + } } }